Listings: a discarded listing moves to the Closed tab when its closing date passes, still labelled Discarded #529

Closed
opened 2026-10-01 10:47:25 +00:00 by tiagoagueda · 0 comments
Owner

Found on 2026-09-29 in a read-only review of postulo at 95348908d, comparing the three places that decide a listing's derived state.

Checked again on 2026-10-01 against main at 0a5cad587: still present. The code is now at jobs/models.py:723-744 (state_condition), jobs/models.py:798-809 (with_state_order) and jobs/models.py:1003-1011 (derived_state); the callers in jobs/listing_views.py, api/routers/listings.py and applications/analytics.py have not moved.

Postulo decides twice, with two different orders, whether a discarded listing whose closing date has passed is discarded or closed. The word in the State cell and the State column's sort say discarded; the tabs, their counts, the API filter and the dashboard's count of listings let go say closed.

  • Where: jobs/models.py:720-741 (state_condition: closed is no_application & shut with no exclusion of discarded rows, and discarded is Q(state=state) & no_application & ~shut); against jobs/models.py:795-806 (with_state_order, discarded before closed) and jobs/models.py:1000-1008 (derived_state, discarded before closed). state_condition feeds in_state and tab_counts, and through them jobs/listing_views.py:100 and :115, api/routers/listings.py:81-86 and :121, and applications/analytics.py:405.
  • What happens: a listing closing on Friday is discarded with The pay. On Saturday it has left the Discarded tab and appears under Closed, where its State cell reads Discarded. In the snapshot, a discarded listing with closes_at yesterday has derived_state == "discarded", in_state("discarded").count() == 0, in_state("closed").count() == 1 and tab_counts() of discarded: 0, closed: 1. The same listing is returned by GET /api/v1/listings?state=closed with "state": "discarded", and not by ?state=discarded. The insights figure for listings discarded falls day by day as closing dates pass.
  • Why it matters: the tab a person looks in for what they let go loses entries on its own, the figure about how choosy they have been is wrong, and an API client gets rows whose state contradicts the filter it asked for. state_condition's docstring says it exists so that the places deciding closed agree (#231); derived_state is the one it does not agree with.
  • Fix: use derived_state's precedence everywhere (a decision outlives a date): in state_condition, make closed no_application & shut & ~Q(state=ListingState.DISCARDED) and discarded Q(state=ListingState.DISCARDED) & no_application. Correct the with_state_order docstring, which lists the order as "to decide, applied, closed, discarded" while the code tests discarded first.

Done when

  • A test in tests/test_listings.py that, for every combination of stored state (new, shortlisted, discarded), open or past its closing date or closed, and with or without an application, checks that derived_state, the one tab whose in_state contains the row, and the with_state_order value agree.
Found on 2026-09-29 in a read-only review of postulo at 95348908d, comparing the three places that decide a listing's derived state. Checked again on 2026-10-01 against `main` at `0a5cad587`: still present. The code is now at `jobs/models.py:723-744` (`state_condition`), `jobs/models.py:798-809` (`with_state_order`) and `jobs/models.py:1003-1011` (`derived_state`); the callers in `jobs/listing_views.py`, `api/routers/listings.py` and `applications/analytics.py` have not moved. Postulo decides twice, with two different orders, whether a discarded listing whose closing date has passed is *discarded* or *closed*. The word in the State cell and the State column's sort say discarded; the tabs, their counts, the API filter and the dashboard's count of listings let go say closed. - **Where:** `jobs/models.py:720-741` (`state_condition`: `closed` is `no_application & shut` with no exclusion of discarded rows, and `discarded` is `Q(state=state) & no_application & ~shut`); against `jobs/models.py:795-806` (`with_state_order`, discarded before closed) and `jobs/models.py:1000-1008` (`derived_state`, discarded before closed). `state_condition` feeds `in_state` and `tab_counts`, and through them `jobs/listing_views.py:100` and `:115`, `api/routers/listings.py:81-86` and `:121`, and `applications/analytics.py:405`. - **What happens:** a listing closing on Friday is discarded with *The pay*. On Saturday it has left the *Discarded* tab and appears under *Closed*, where its State cell reads *Discarded*. In the snapshot, a discarded listing with `closes_at` yesterday has `derived_state == "discarded"`, `in_state("discarded").count() == 0`, `in_state("closed").count() == 1` and `tab_counts()` of `discarded: 0, closed: 1`. The same listing is returned by `GET /api/v1/listings?state=closed` with `"state": "discarded"`, and not by `?state=discarded`. The insights figure for listings discarded falls day by day as closing dates pass. - **Why it matters:** the tab a person looks in for what they let go loses entries on its own, the figure about how choosy they have been is wrong, and an API client gets rows whose state contradicts the filter it asked for. `state_condition`'s docstring says it exists so that the places deciding *closed* agree (#231); `derived_state` is the one it does not agree with. - **Fix:** use `derived_state`'s precedence everywhere (a decision outlives a date): in `state_condition`, make `closed` `no_application & shut & ~Q(state=ListingState.DISCARDED)` and `discarded` `Q(state=ListingState.DISCARDED) & no_application`. Correct the `with_state_order` docstring, which lists the order as "to decide, applied, closed, discarded" while the code tests discarded first. ## Done when - A test in `tests/test_listings.py` that, for every combination of stored state (new, shortlisted, discarded), open or past its closing date or closed, and with or without an application, checks that `derived_state`, the one tab whose `in_state` contains the row, and the `with_state_order` value agree.
tiagoagueda added this to the 0.5.0 milestone 2026-10-01 10:47:25 +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#529
No description provided.