Skip to content

feat(well-show): generate the annual water quality report from well details - #354

Open
jirhiker wants to merge 6 commits into
stagingfrom
feature/bdms-1189-chemistry-report-button
Open

feat(well-show): generate the annual water quality report from well details#354
jirhiker wants to merge 6 commits into
stagingfrom
feature/bdms-1189-chemistry-report-button

Conversation

@jirhiker

Copy link
Copy Markdown
Member

Closes BDMS-1189.

What this does

Adds a report-type select to the PDF button group on the well details page, per the ticket's acceptance criteria. Both actions in the group act on the selection:

  • Field sheet — the existing well PDF, unchanged, still the default.
  • Chemistry report — an owner-facing annual water quality report.

Preview follows the selection too: the field sheet keeps its existing preview route, and the chemistry report opens the exporter page, which already renders a full preview and now accepts thing_id and year so it lands on the report the well page asked for.

Also included: the standalone Chemistry Report Exporter (sandbox), which is where the report layout is being iterated on.

Requires the API side first

Depends on GET /chemistry/results from OcotilloAPI#870, already merged to staging. The report reads the legacy NMA chemistry tables through that route. The refactored observation/water-chemistry endpoint holds no water chemistry at all, so a report built on it came back empty for every well.

Notable decisions

The reporting year is the most recent one sampled, not the current year. A well last sampled in 2019 would otherwise hand back an empty report, which reads as a broken button. A well with nothing on file still generates, marked as having no results — the same thing the exporter does, and better than a dead-end control.

The chemistry table shows each parameter once at its most recent value in the year. A well is rarely sampled for everything on one day: majors from one trip, trace metals from another. Keying the table to a single sample date dropped most of the record and left total dissolved solids called out as over its limit with no row to show for it.

Rows are curated to parameters measured against a standard, plus hardness. Sixty-odd unregulated trace metals sitting at their detection limits buried the ~20 a reader can act on; the rest are counted in a footnote.

Gated on AMP.Staging while the layout is under review — users without the group are not offered the option at all. This also corrects the group's spelling from AMP.staging: groups arrive as literal strings in the token's groups claim, so the old spelling matched nothing and the feature was unreachable outside dev, where the test identity carried the same typo.

No lineHeight anywhere in the report styles. react-pdf 4.4.0 renders any explicit value — 1.0 and 1.55 alike — at roughly double the leading, which double-spaced every wrapped paragraph. Its defaults are correct.

Verification

  • npx tsc --noEmit clean; 85 tests pass
  • Exercised end to end against AR-0102 (2019): 87 results across 8 sampling visits, correctly split 20 major / 62 minor / 5 field, with a real secondary-standard exceedance (TDS 618 mg/L against a 500 mg/L guideline) flagged in both the callout and the table
  • Well with no chemistry (EB-339) generates a no-results report rather than disabling

Not included

  • Nearby-wells comparison — the reference layout's third page and its percentile strips need a backend aggregation (same aquifer, radius, median, 10th–90th, count above limit, suppressed under 5 wells). Not attempted here.
  • Hydrograph and box-and-whisker plots — react-pdf cannot draw them without a chart image pipeline.
  • QR code, report ID, supersedes line — no URL scheme or report registry exists to back them, so they are omitted rather than invented.

🤖 Generated with Claude Code

jirhiker and others added 6 commits August 21, 2026 14:35
Adds a sandbox page that renders an owner-facing annual water quality
report for a single well as a PDF: pick a well and a calendar year,
preview it inline, download it.

The report compares each result against EPA primary (MCL) and secondary
(SMCL) drinking water standards so exceedances are called out rather than
left for the reader to look up. Results print at the lab's precision —
rounding an arsenic result of 0.012 mg/L to 0.01 would hide that it sits
at the limit it is being compared against.

Gated behind a new AMP.staging group while the report layout is under
review. AMP.staging is deliberately not a rung on the
Viewer -> Editor -> Admin ladder: holding AMP.Admin does not imply it, so
the report cannot leak to admins before it is signed off. The local test
identity holds it explicitly or the page is unreachable in dev.

Refs BDMS-1189

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Puts the annual water quality report one click from the well it describes,
instead of only in the standalone exporter where the well has to be looked
up again.

The report covers a single calendar year, so the button has to pick one.
It reports on the most recent year the well was actually sampled rather
than the current year — a well last sampled in 2024 would otherwise hand
back an empty report, which reads as a broken button. Finding that year
costs one row (newest sample, sorted descending), and the same row answers
whether the well has any chemistry at all: with none, the button is
disabled and says so on hover.

The year's results are not fetched until the button is pressed. Most
visits to a well page are not after a report, and the well and its
contacts are already loaded by the page, so nothing extra is pulled for
the common case.

Generating and downloading the PDF is shared with the exporter's button
rather than copied, and the reporting-window and sort rules now live in
one place so the two entry points cannot drift into disagreeing about
what a reporting year is.

Gated on AMP.staging, matching the exporter.

Closes BDMS-1189

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Reworks the well details PDF actions into a report-type select plus the
existing preview and download, replacing the standalone chemistry report
button added in the previous commit. The button group grows a select
offering "Field sheet" and "Chemistry report"; both actions then operate
on whichever is chosen, so the group reads as one control rather than a
row of per-report buttons that would grow with every new report type.

Preview follows the selection. The field sheet keeps its existing preview
route, and the chemistry report reuses the exporter page, which already
renders a full preview — it now accepts thing_id and year so it opens on
the report the well page asked for instead of an empty picker. A linked
year outside the picker's five-year window is added to its options, since
a well last sampled in 2019 must still be selectable on arrival.

The chemistry report stays on AMP.staging: users without the group are not
offered the option at all, and for a well with no chemistry on file both
actions are disabled with the reason given, rather than generating an empty
report.

Refs BDMS-1189

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Selecting the chemistry report for a well with nothing on file disabled
both preview and download, leaving the option a dead end. The exporter
already generates that report and marks it as having no results, and a
report stating a well has no chemistry on record is a legitimate thing to
hand an owner, so the well page now does the same.

A well that has never been sampled falls back to the current calendar year
rather than having no year to report on. The actions are held back only
while the reporting year is still being looked up, since acting early
would report on the wrong year. The no-results case is called out on the
controls up front instead of being discovered after opening the PDF.

Refs BDMS-1189

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The report was built on `observation/water-chemistry`, which holds no
water chemistry at all -- the data is in the legacy NMA tables. Every well
therefore looked like it had never been sampled, which is why selecting
the chemistry report on a well with decades of analyses offered nothing to
generate.

Points both entry points at `chemistry/results`, the new REST route over
those tables. The response is flat and its ids are text, so
`ChemistryObservation` (an alias for the generated type of an endpoint that
serves none of this) becomes a hand-written `ChemistryResult`.

Field parameters were split from laboratory results on `parameter_type`,
which the legacy data does not carry. That split now follows `result_kind`,
which reports the legacy table a result came from -- a field reading came
off the wellhead, anything else came from a lab.

Also corrects the staging group to `AMP.Staging`, the way Authentik and the
API both spell it. Groups arrive as strings in the token's `groups` claim
and are matched literally, so the old `AMP.staging` matched nothing: the
report was unreachable everywhere except dev, where the test identity was
seeded with the same wrong spelling.

Verified against AR-0102, whose 2019 analyses come back as 20 major, 62
minor and 5 field results with a real secondary-standard exceedance (total
dissolved solids 618 mg/L against a 500 mg/L guideline).

Refs BDMS-1189

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Rebuilds the PDF against the reference report: navy masthead, letterspaced
teal section rules with a source note on the right, bordered at-a-glance
tiles, tinted exceedance callouts, a four-across well information grid,
zebra tables with monospaced numerics, and status pills carrying each
result's verdict. Three pages, matching the reference.

Adds the water level section the reference leads with, so the report says
what the water table did as well as what was in the water. Readings come
from the groundwater level observations, and the newest reading from before
the reporting year is carried in as well: a single year in isolation has
nothing to compare against, and the at-a-glance change stat needs two
points. Water elevation is the land surface elevation less the depth to
water, printed only where a surveyed elevation is on file.

The chemistry table keeps each parameter once, at its most recent value in
the year, rather than the newest sample's rows. A well is rarely sampled
for everything on one day -- majors from one trip, trace metals from
another -- so keying the table to a single date dropped most of the record
and left total dissolved solids called out as over its limit with no row to
show for it. Rows are the ones measured against a standard, plus hardness,
which has no standard but drives softener decisions; a hundred unregulated
trace metals sitting at their detection limits buried the handful a reader
can act on, so the rest are counted in a footnote instead.

Legacy symbols with no lexicon term (CF, DO, ORP) are relabelled for print
only. The API is right to leave them alone -- inventing a parameter name
would put vocabulary in the record that nothing else knows -- but "CF" on a
page handed to a well owner is noise, and none of them carry a standard, so
relabelling cannot misapply a limit.

No style sets lineHeight. react-pdf 4.4.0 renders any explicit value --
1.0 and 1.55 alike -- with roughly double the leading, which double-spaced
every wrapped paragraph. Its default metrics are correct. Hardness prints
CaCO3 rather than a subscript for the same class of reason: Helvetica has
no U+2083 and substitutes an italic f.

The QR code, report ID, and supersedes line are left out rather than
faked; no URL scheme or report registry exists to back them. Contact is
aquifermapping@nmt.edu, (575) 835-5327.

Refs BDMS-1189

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant