When somebody applied is lost: applied_at is set only on "Applied", and no form lets you say the date #222

Closed
opened 2026-09-15 21:23:23 +00:00 by tiagoagueda · 0 comments
Owner

Backfilled or after-the-fact applications disappear from Insights and from the employment-office report, or land in the wrong month. Found in the 2026-09-15 code audit.

1. applied_at is set only on the literal Applied status

  • applications/services.py:96-98 sets it only when new_status == APPLIED.
  • apply_to_listing creates a DRAFT and jumps straight to the chosen status (services.py:144-157). Choosing Interviewing, Rejected, Offer and so on at intake (forms.py:115) or through the API (api/schemas.py:203) leaves applied_at null.
  • _catch_up does the same when an interview is held on a draft (services.py:389-394).
  • The CSV import works around it by forcing applied first (core/csv_import.py:841), which shows the intent.

What goes wrong:

  • These applications drop out of ever_applied (analytics.py:223): the sent count, reply and interview times, sources and industries.
  • The funnel still counts them (analytics.py:228-236), so a stage can exceed 100% of Applied.
  • They are missing from the report's evidence list and cadence (reports.py:404-412), from by-month figures, from "sent recently", and from the API since filter.

2. No way to say when you applied

  • There is no applied on date in ApplicationDetailsForm, ApplicationForm, capture review's "already applied" ("marked as applied today", jobs/capture_views.py:200-206), or the API.
  • The wiki calls recording after the fact "the common case" (Listings). Only the CSV import can backdate.

3. CSV import decides by the date, not by the status

  • ParsedRow.becomes depends only on applied_at (csv_import.py:673-677).
  • A row saying Rejected or Interviewing but with no date becomes a New listing, dropping status, channel, tags and deadline (:818-824).
  • A dated draft or wishlist row becomes Applied (:841-843).

Proposal

  • In change_status, set applied_at on the first move out of DRAFT to any sent status, using occurred_at.
  • A data migration that backfills applied_at from each application's earliest non-draft to_status event.
  • An optional Applied on date, defaulting to today, on intake, Apply, capture review and the API, passed as occurred_at. Correcting it later goes through a service that writes a timeline entry.
  • CSV import: a non-draft status without a date becomes an application with an unknown date (or the mapping page asks for a fallback date), and a draft row stays a listing.
Backfilled or after-the-fact applications disappear from Insights and from the employment-office report, or land in the wrong month. Found in the 2026-09-15 code audit. ## 1. `applied_at` is set only on the literal *Applied* status - `applications/services.py:96-98` sets it only when `new_status == APPLIED`. - `apply_to_listing` creates a DRAFT and jumps straight to the chosen status (`services.py:144-157`). Choosing *Interviewing*, *Rejected*, *Offer* and so on at intake (`forms.py:115`) or through the API (`api/schemas.py:203`) leaves `applied_at` null. - `_catch_up` does the same when an interview is held on a draft (`services.py:389-394`). - The CSV import works around it by forcing *applied* first (`core/csv_import.py:841`), which shows the intent. What goes wrong: - These applications drop out of `ever_applied` (`analytics.py:223`): the sent count, reply and interview times, sources and industries. - The funnel still counts them (`analytics.py:228-236`), so a stage can exceed 100% of *Applied*. - They are missing from the report's evidence list and cadence (`reports.py:404-412`), from by-month figures, from "sent recently", and from the API `since` filter. ## 2. No way to say when you applied - There is no *applied on* date in `ApplicationDetailsForm`, `ApplicationForm`, capture review's "already applied" ("marked as applied today", `jobs/capture_views.py:200-206`), or the API. - The wiki calls recording after the fact "the common case" (*Listings*). Only the CSV import can backdate. ## 3. CSV import decides by the date, not by the status - `ParsedRow.becomes` depends only on `applied_at` (`csv_import.py:673-677`). - A row saying *Rejected* or *Interviewing* but with no date becomes a *New* listing, dropping status, channel, tags and deadline (`:818-824`). - A dated *draft* or *wishlist* row becomes *Applied* (`:841-843`). ## Proposal - In `change_status`, set `applied_at` on the first move out of DRAFT to any sent status, using `occurred_at`. - A data migration that backfills `applied_at` from each application's earliest non-draft `to_status` event. - An optional *Applied on* date, defaulting to today, on intake, *Apply*, capture review and the API, passed as `occurred_at`. Correcting it later goes through a service that writes a timeline entry. - CSV import: a non-draft status without a date becomes an application with an unknown date (or the mapping page asks for a fallback date), and a draft row stays a listing.
tiagoagueda added this to the 0.3.0 milestone 2026-09-15 21:33:20 +00:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
Postulo/postulo#222
No description provided.