Reports showing how regularly the search is going #56

Closed
opened 2026-09-06 15:46:51 +00:00 by tiagoagueda · 2 comments
Owner

Observation

another 0.3.0 goal:
be able to generate reports to show regularity of job search

Why

Two different people ask for this, and they want the same document.

The first is an employment office. Unemployment benefit in most of Europe is conditional
on actually looking — a minimum number of applications in a period, evidenced. Portugal's
IEFP, France Travail, the Agentur für Arbeit and the rest each want a list: what was applied
for, where, when, and what came of it. Postulo already holds every one of those facts, and
right now the only way to hand them over is to copy them out by hand, which is precisely the
work that makes people stop keeping records at all.

The second is the person searching. A month of looking feels like nothing happened. A
page saying eleven applications across four weeks, none in the week of the 12th is the
difference between a feeling and a fact, and it is the thing that says where the gap was.

What exists today

Insights (applications/analytics.py) computes across everything — build(user) takes
no dates at all — so it can say what the response rate is but not what happened in March.
There is a full data export (core/export.py) and PDF rendering already in the codebase
(documents/pdf.py, WeasyPrint), so the machinery to produce a document exists; what is
missing is the period, the cadence, and the shape of the page.

Shape

  • Choose a period: a month, a quarter, a custom range, or the last n weeks. Everything
    in the report is scoped to it — no report ever quietly mixes in older activity.
  • Regularity is the point, so the cadence is the top of the page: applications per week
    across the period, the weeks with none marked plainly, the longest gap, the average per
    week, and the current run. A chart, not a paragraph.
  • The list underneath is the evidence: date applied, company, role, where it was found,
    the address of the posting, current status, and the date of the last thing that happened.
    One row per application, ordered by date, printable.
  • What else the period says: replies received, interviews attended, offers, rejections,
    and where the effort actually went — which sources, which kinds of role.
  • Out as a PDF for handing over, and as CSV for anybody who wants to do their own sums.
    The PDF carries the person's name, the period and the date it was produced, because a
    document with no date is not evidence of anything.
  • Say nothing that is not true. No "target" or "expected" number of applications
    invented by Postulo; a benefit regime's requirement is that regime's, not the software's.
    If somebody wants to record what they were told to do, that is a number they set.

Classification

Enhancement, for 0.3.0. Not breaking: it reads what is already recorded and writes nothing.

Depends on

Nothing hard. It shares its definitions with #44 — a widget and a report row are the same
numbers at two lengths — so whichever lands second should read the first's rather than
rewrite them.

Open questions

  1. Scheduled, or on request? A monthly report arriving on the first of the month is obvious
    once a scheduler and notifiers already exist. Proposal: on request first, scheduled as
    its own issue once the page is right.
  2. Does the report include applications drafted but never sent? Proposal: no, and say so on
    the page. An employment office is asking what was sent.
  3. A template per country? Proposal: no. One honest report, with what each office asks for
    written down in the wiki where a person can check it.
## Observation > another 0.3.0 goal: > be able to generate reports to show regularity of job search ## Why Two different people ask for this, and they want the same document. The first is **an employment office**. Unemployment benefit in most of Europe is conditional on actually looking — a minimum number of applications in a period, evidenced. Portugal's IEFP, France Travail, the Agentur für Arbeit and the rest each want a list: what was applied for, where, when, and what came of it. Postulo already holds every one of those facts, and right now the only way to hand them over is to copy them out by hand, which is precisely the work that makes people stop keeping records at all. The second is **the person searching**. A month of looking feels like nothing happened. A page saying *eleven applications across four weeks, none in the week of the 12th* is the difference between a feeling and a fact, and it is the thing that says where the gap was. ## What exists today Insights (`applications/analytics.py`) computes across **everything** — `build(user)` takes no dates at all — so it can say what the response rate is but not what happened in March. There is a full data export (`core/export.py`) and PDF rendering already in the codebase (`documents/pdf.py`, WeasyPrint), so the machinery to produce a document exists; what is missing is the period, the cadence, and the shape of the page. ## Shape - **Choose a period**: a month, a quarter, a custom range, or the last *n* weeks. Everything in the report is scoped to it — no report ever quietly mixes in older activity. - **Regularity is the point, so the cadence is the top of the page**: applications per week across the period, the weeks with none marked plainly, the longest gap, the average per week, and the current run. A chart, not a paragraph. - **The list underneath** is the evidence: date applied, company, role, where it was found, the address of the posting, current status, and the date of the last thing that happened. One row per application, ordered by date, printable. - **What else the period says**: replies received, interviews attended, offers, rejections, and where the effort actually went — which sources, which kinds of role. - **Out as a PDF** for handing over, and as CSV for anybody who wants to do their own sums. The PDF carries the person's name, the period and the date it was produced, because a document with no date is not evidence of anything. - **Say nothing that is not true.** No "target" or "expected" number of applications invented by Postulo; a benefit regime's requirement is that regime's, not the software's. If somebody wants to record what they were told to do, that is a number they set. ## Classification Enhancement, for 0.3.0. Not breaking: it reads what is already recorded and writes nothing. ## Depends on Nothing hard. It shares its definitions with #44 — a widget and a report row are the same numbers at two lengths — so whichever lands second should read the first's rather than rewrite them. ## Open questions 1. Scheduled, or on request? A monthly report arriving on the first of the month is obvious once a scheduler and notifiers already exist. Proposal: on request first, scheduled as its own issue once the page is right. 2. Does the report include applications drafted but never sent? Proposal: no, and say so on the page. An employment office is asking what was sent. 3. A template per country? Proposal: no. One honest report, with what each office asks for written down in the wiki where a person can check it.
tiagoagueda added this to the 0.3.0 milestone 2026-09-06 15:46:51 +00:00
Author
Owner

Reports are named as one of the document kinds in #133, which would make a report a thing Postulo renders, freezes and copies to a store like any other document.

That is worth knowing here, but this issue is deliberately not blocked by it. What #133 settles is delivery; what this issue settles is content — what an employment office actually needs on the page — and that question is answerable now. A report built as its own page and PDF can be adopted as a kind later; a report waiting on the document framework ships after it.

The one thing worth deciding early: a report is the first document whose content is computed rather than written, so "edit this document" never applies to it. Anything built here should assume the document is a snapshot of the record at a moment, which is also what makes it evidence.

Reports are named as one of the document kinds in #133, which would make a report a thing Postulo renders, freezes and copies to a store like any other document. That is worth knowing here, but this issue is **deliberately not blocked by it**. What #133 settles is delivery; what this issue settles is content — what an employment office actually needs on the page — and that question is answerable now. A report built as its own page and PDF can be adopted as a kind later; a report waiting on the document framework ships after it. The one thing worth deciding early: a report is the first document whose content is *computed* rather than written, so "edit this document" never applies to it. Anything built here should assume the document is a snapshot of the record at a moment, which is also what makes it evidence.
Author
Owner

Done in b2afbfb5. /applications/report/, reachable from the dashboard's
shortcuts, with a PDF and a CSV.

The three open questions, answered as proposed

  1. On request, not scheduled. Scheduling a page nobody has read yet would be scheduling
    a guess. Worth its own issue once the page has been used.
  2. Drafts never sent are not in it — and the page says so, plus how many are being left
    out when there are any, so the absence is stated rather than silent.
  3. No template per country. One honest report; what a particular office asks for belongs
    in the wiki, where a person can check it against that office.

Where it lives, and why not in analytics.py

The period is the whole reason. build(user) computes across everything and takes no dates,
so it can say what the response rate is and not what happened in March. reports.py sits
beside it and shares its definitions rather than restating them — RESPONSE_STATUSES is
imported, with a test asserting it is the same object, since a widget and a report row are
the same numbers at two lengths.

Two questions, kept apart

This is the decision the page turns on.

  • Sent in this period — the cadence and the evidence list — reads applications by when
    they went out.
  • What came back — replies, interviews attended, offers, rejections — reads the event
    log by when the event occurred. A reply arriving in September to an August application
    is September activity, and belongs there even though the application does not.

Folding them together would produce a figure that is true of neither question. The page says
which is which rather than leaving it to be guessed. For the same reason a reply is counted
once per application, not once per step: acknowledged → screened → interviewed in one
month is one reply, and three would report a busier month than happened.

Saying nothing that is not true

The issue asks for this twice, and three things follow from it:

  • The gap is measured inside the period. A silence that began in March is not carried
    into April's report.
  • A running period is only counted up to today. Counting the rest of the month as
    silence would report a gap nobody has had yet.
  • A week straddling either end is marked. A month rarely begins on the first day of a
    week, so the bars at each end cover fewer days — and a short bar that is short because
    the week was short
    misleads unless it says so. Nothing from outside the period is ever
    counted in it.

And no target anywhere, with a line on the page saying so.

The rest

  • The period is in the address — a question, not a preference, so a report for a
    particular month is a thing to bookmark and to send to somebody. No link into the future.
  • One date control serves the month and the quarter: choosing a quarter beside
    September gives July–September. A second control ignored three times out of four would be
    worse than none, and a quarter silently ignoring what was typed would be worse than that.
  • The chart is <progress>, so its value is an attribute rather than a style — the same
    reason the funnel widget uses one, and it is announced with its value. Empty weeks say
    none in words, so nothing depends on colour. The PDF draws its own bars with an inline
    width, which is exactly what the page may not do and the PDF may: nothing serves it to a
    browser.
  • The PDF is its own template, laid out for A4 with page numbers and logical sides
    throughout, so a report written in Arabic lays out from the other edge.
  • Weeks start on whichever day the reader's language starts a week on.

On #133

Nothing is stored. A report is computed when it is asked for, from records that are
already the truth. That is what makes it a snapshot of the record at a moment, and it means
"edit this document" never has to mean anything for this kind — which is the half of #133
this settles in advance rather than waiting on.

Also

tests/test_reports.py (48). Suite 4540 passed, 29 skipped; browser suite 86 passed — the
report page is in the axe-core walk in both themes and in the strict-CSP sweep. 61 new
strings, filled in all 39 European catalogues.
Wiki: a new Reports page, linked from
Home, the sidebar and Insights.

Shipped on 0.3.0, with main kept level.

Done in `b2afbfb5`. `/applications/report/`, reachable from the dashboard's shortcuts, with a PDF and a CSV. ## The three open questions, answered as proposed 1. **On request, not scheduled.** Scheduling a page nobody has read yet would be scheduling a guess. Worth its own issue once the page has been used. 2. **Drafts never sent are not in it** — and the page says so, plus how many are being left out when there are any, so the absence is stated rather than silent. 3. **No template per country.** One honest report; what a particular office asks for belongs in the wiki, where a person can check it against that office. ## Where it lives, and why not in `analytics.py` The period is the whole reason. `build(user)` computes across everything and takes no dates, so it can say what the response rate is and not what happened in March. `reports.py` sits beside it and **shares its definitions rather than restating them** — `RESPONSE_STATUSES` is imported, with a test asserting it is the same object, since a widget and a report row are the same numbers at two lengths. ## Two questions, kept apart This is the decision the page turns on. - ***Sent* in this period** — the cadence and the evidence list — reads applications by when they went out. - ***What came back*** — replies, interviews attended, offers, rejections — reads the event log by **when the event occurred**. A reply arriving in September to an August application is September activity, and belongs there even though the application does not. Folding them together would produce a figure that is true of neither question. The page says which is which rather than leaving it to be guessed. For the same reason a reply is counted **once per application, not once per step**: acknowledged → screened → interviewed in one month is one reply, and three would report a busier month than happened. ## Saying nothing that is not true The issue asks for this twice, and three things follow from it: - **The gap is measured inside the period.** A silence that began in March is not carried into April's report. - **A running period is only counted up to today.** Counting the rest of the month as silence would report a gap nobody has had yet. - **A week straddling either end is marked.** A month rarely begins on the first day of a week, so the bars at each end cover fewer days — and a short bar that is short *because the week was short* misleads unless it says so. Nothing from outside the period is ever counted in it. And **no target anywhere**, with a line on the page saying so. ## The rest - **The period is in the address** — a question, not a preference, so a report for a particular month is a thing to bookmark and to send to somebody. No link into the future. - **One date control serves the month and the quarter**: choosing *a quarter* beside September gives July–September. A second control ignored three times out of four would be worse than none, and a quarter silently ignoring what was typed would be worse than that. - **The chart is `<progress>`**, so its value is an attribute rather than a style — the same reason the funnel widget uses one, and it is announced with its value. Empty weeks say *none* in words, so nothing depends on colour. The PDF draws its own bars with an inline width, which is exactly what the page may not do and the PDF may: nothing serves it to a browser. - **The PDF is its own template**, laid out for A4 with page numbers and logical sides throughout, so a report written in Arabic lays out from the other edge. - Weeks start on whichever day the reader's language starts a week on. ## On #133 **Nothing is stored.** A report is computed when it is asked for, from records that are already the truth. That is what makes it a snapshot of the record at a moment, and it means "edit this document" never has to mean anything for this kind — which is the half of #133 this settles in advance rather than waiting on. ## Also `tests/test_reports.py` (48). Suite 4540 passed, 29 skipped; browser suite 86 passed — the report page is in the axe-core walk in both themes and in the strict-CSP sweep. **61 new strings, filled in all 39 European catalogues.** Wiki: a new **Reports** page, linked from Home, the sidebar and Insights. Shipped on `0.3.0`, with `main` kept level.
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#56
No description provided.