Skip to content

Agent-authored code-health week — for review, not for merge - #110

Open
AlexisJanin wants to merge 11 commits into
mainfrom
code-health
Open

AlexisJanin wants to merge 11 commits into
mainfrom
code-health

Conversation

@AlexisJanin

Copy link
Copy Markdown
Collaborator

What this is

An experiment, not a feature branch. Eleven commits written by autonomous Claude Opus agents across eleven separate sessions, under a fixed protocol, on a branch that was never intended to merge as-is.

The deliverable was never the code. It was a capability map: which kinds of code-health task can an agent be trusted with unsupervised in this repo, and where is the ceiling? Committed code was defined up front as evidence, not as the goal.

This PR is a review surface, not a merge request. See What this PR is not.

How it was written

The protocol lived in .scratch/agent-week/ (gitignored — see Where the rest of it lives). Every run followed the same loop:

  1. Read the standing instructions, the protocol, the log of prior runs, and the backlog.
  2. Take the top human-approved, unblocked task. One run = one task.
  3. Spawn an implementer subagent.
  4. Spawn a reviewer subagent with fresh context — handed the diff and the ticket, never the implementer's reasoning. An implementer reviewing itself proves nothing.
  5. Exactly one fix round. The implementer fixes or defends; then it is settled either way.
  6. Verify: ruff format --check ., ruff check ., pytest — all three, all green.
  7. One task, one commit. The commit message is the human's review index, since there were no PRs during the week.
  8. Append a log entry after the git operation, so the log never describes a state that was rolled back.
  9. Stop. Do not start a second task.

Run 1 was an audit instead: four subagents in parallel (comments / coverage / depth / robustness), merged into a ranked backlog of 12 one-session tasks, then stopped at a human approval gate. No code changes in that run — the gate was the point.

The guardrails

  • Behaviour-preserving by default. Comments, docstrings, tests, internal refactors.
  • Behaviour changes allowed only when both held: a test pins the new behaviour, and the reviewer explicitly signs off. This is a clinical data pipeline — new validation that rejects a file which used to load is a regression even under a green suite, because the suite does not contain the user's files.
  • HALT list (stop and wait for a human): any diff under tests/expected_results/ or any use of --update-snapshots; the __init__.py exports; annotation and extraction return shapes; README.md and docs/user_guide/**; any decided ADR; example/** and tests/data/**.
  • ABANDON rules (revert, log, move on): suite still red after 2 fix attempts, reviewer rejects after the one fix round, task turns out bigger than one session.
  • Anything out of envelope went to a findings file rather than being implemented.

Why it was run

To answer three questions with evidence rather than intuition:

  1. Can an agent make safe, reviewable changes to this codebase without a human in the loop per-step?
  2. Which axes (comments / coverage / depth / robustness) does that hold for, and which need a human?
  3. What does an agent notice that a focused human pass does not — particularly on error paths that no one exercises by hand?

The honest answer to (3) turned out to be the most valuable output, and it is not in this diff.

What landed

11 commits, suite green at 1459 passed, ruff clean.

Kind Count Which
New user-visible behaviour 2 unknown-database_options-section warning; duplicate-timestamp drop warning
Internal deduplication 3 ValidationIssue reporter; other's per-file read tail; wrapper's datasource dispatch
Docstrings / comments only 3 six legacy sources/ packages; DataSourceBase's stale subclass contract; renderer docstring
Tests only 3 failure isolation; config/parsing.py; inspect's reader failures

+227/−116 in src/, +1383 in tests/ (97 new test functions). ~86% of the diff is test code. That is the honest shape of the week.

Both behaviour changes are warnings on paths that were already degrading — neither rejects input that loads today.

The tests were mutation-checked, not taken on faith

1,383 lines of new test could easily be coverage theatre. Eight independent mutations were applied to src/ and the new suites run against each:

Mutation Result
dedup warning downgraded to logger.debug 4 failed
dedup warning loses its file-name context 3 failed
validation.py: stacklevel=2 removed exactly 1 failed
validation.py: logger re-parented to this module exactly 4 failed
unknown-section warning removed 6 failed
wrapper: progress total counts the registry, not the request 4 failed
renderer: subplot domain collapses to paper 5 failed
renderer: time-window height 1 → 0.5 1 failed

Every mutation reddened. Two results — "removing stacklevel=2 reddens exactly one, re-parenting the logger reddens four" — reproduce verbatim what the agent's own log claimed at the time, which is a datum about the log's reliability as much as the tests'.

What the experiment actually found

The audit and the eleven runs produced 39 findings deliberately left unimplemented — too large for one session, or needing a product decision. Several were verified by hand against HEAD. The ones that matter most, none of which this branch fixes:

  • extract_* returns unformatted data when _format fails. datasource/base.py catches, logs, and falls back to df_raw — handing the caller a normal-looking DataFrame in which the timezone, the time shift and the datetime window were all never applied, under a green success banner. The same frame is what save_path writes to disk.
  • quick_load never checks the cache still matches the source. No mtime comparison, no schema check. A source file added after the cache was written is invisible; a corrupt cache is a permanent trap that nothing bypasses or deletes.
  • patient_options is validated in dash_api and nowhere else. The library entry points accept the dict raw; a hand-edited time_shift: null yields an all-NaT time axis under a green status line.
  • tzdata is not a declared dependency and is not in the PyInstaller spec — unverified on Windows, but if correct it would break every timezone in the standalone Windows build. Flagged as the single entry most worth checking by hand.
  • to_float_seconds is dead and wrong. On pandas 3 the naive-DatetimeIndex branch returns epoch seconds 1000× too small, and the tz-aware branch raises before reaching its own code. Blast radius today is nil — the one production caller takes the safe branch.
  • ruff.toml excludes tests, so no test file has ever been linted — including in CI — and the [lint.extend-per-file-ignores] "tests/*" block is dead config describing ignores for files ruff never reaches.
  • pd.concat(..., axis=1) in mindray_scope has a pandas 4 deadline. The sort default flips; this is the only finding with a clock on it.

What the experiment says about agents in this repo

Recorded plainly, including where it is unflattering:

  • 11 committed, 0 abandoned, 0 halted. The protocol states that an abandoned task is the most informative entry the log can hold. None occurred, so the capability map has a floor and no ceiling — we learned these eleven task shapes are safe, not where agents break.
  • Across 11 reviews, the fresh-context reviewer never caught a logic error. Both rejections were on prose and test-strength. Read one way the implementers wrote no logic bugs; read another, the reviewer's demonstrated value is "catches weak tests and false docstrings", which is worth having but is not what a second Opus pass is usually budgeted for.
  • One task was independently beaten by a human. Task 02 found that OtherDataSource.inspect discarded the real exception. The same bug was found and fixed on main in a better form (prefixes the exception type, keeps only the first line — pyarrow appends the whole schema). The rebase collapsed that commit to tests-only and its message was rewritten to say so.
  • The backlog rotted mid-week. The audit measured against main @ 4944467; a later rebase onto Annotations: move, hide, and nudge an existing annotation #100 made task 08's stated premise void (renderer.py measured 95%, not the ticket's 29%). Caught by a pre-commission check, not by design — the remaining coverage premises are still unverified.
  • One run was interrupted after committing and before logging. Its entry was reconstructed from git show by a later run, which marked the reviewer and fix-round columns **not recorded** rather than guessing them — the right call, since that column is the deliverable.

Where the rest of it lives

.scratch/ is gitignored, so the protocol, the 704-line run log, the 12-task backlog, the four audit reports and all 39 findings are not in this branch — they exist only on one machine. The highest-value findings are transcribed above so they survive; the full set still needs filing as issues.

What this PR is not

  • Not a merge candidate. The protocol requires that approved commits be cherry-picked onto a fresh branch off main and PR'd from there. The per-task human verdict column is still blank on all eleven entries.
  • Not a claim that the branch is finished. Backlog task 12 (validate_and_collect, the gate on every Process and Inspect run) remains unworked and its premise should be re-measured before it is.
  • Not a substitute for filing the findings. The code in this diff is the smaller half of what the week produced.

It is open against main so the eleven commits can be read in a review UI, and so CI runs the 3.11 / 3.13 matrix — which this branch has never exercised, since CI does not run on it.

🤖 Generated with Claude Code

alexisj-inria and others added 11 commits September 18, 2026 10:55
These six packages were the last stratum of src/ still commenting the
what. They are also the files a contributor reads first when adding a
datasource, so narration here teaches the wrong habit.

8 comments deleted, 3 tightened, 24 kept — the majority are kept, which
is the expected shape: most of these comments already state a why.

Deleted: three __init__.py banners restating their own directory path,
and five comments restating the statement below them ("Compare extensions
by preference order", "Read the data starting from the row after units").

Docstrings: eight tightened from restating the method name to stating a
contract — the sample-time formula in _format_xml_waveform_data, the
column-naming rule in both mindray _load methods, and why parse_file's
signature is odd (only the first Servo U file carries the header, so its
start_time and rename_map must be fed into every later call).

Two corrections fall out of that: _is_float was documented as never
raising, but OverflowError propagates through its except clause; and
_remove_polluted_columns named two of the four device markers its regex
actually matches.

fluxmed_signals' `units_line_index + 6` is deliberately left unexplained.
What those six lines contain is not established by the code or by the
fixtures, so the comment now records the uncertainty and the asymmetry a
reader needs (under-skipping is caught by the numeric-row filter,
over-skipping silently drops data) rather than inventing a reason.

Comments and docstrings only: for all 18 files the AST with docstring
Expr nodes stripped is identical before and after, so no executable code
changed. Left untouched by instruction: the mindray_respi_waves
vectorisation and int64-ns rationales, and mindray_scope's "seems tz
aware" hedge, which belongs to a separate ticket.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This started as a fix: the per-file guard in the 'other' override reported
error_message="Unexpected error processing <file>" and discarded the
exception, so the actionable half of the diagnosis never reached the
Inspect modal. That fix landed on main first, in a better form -- it
prefixes the exception type and keeps only the first line, which matters
because pyarrow appends the file's whole schema to its message.

What survives is the coverage. Main's own tests reach the generic except
through a parquet with duplicate column names; these two reach it through
the failures a clinician actually hits -- an empty CSV ("No columns to
parse from file") and a file that is not parquet at all ("Parquet magic
bytes not found in footer"). A third pins status, datasource_name and
file_path unchanged on that path.

The earlier version of this commit also put the file name in the message,
reasoning that other::<stem> carries no extension. The inspect card
renders "File: <full path>" on the line directly above the error, so the
prefix only repeated what was already on screen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
"One corrupt file must not cost the whole run" is the most load-bearing
resilience promise in the app, and not one of its except branches was
exercised. Every site is a bare `except Exception: log; continue`, which
is exactly the shape an innocent refactor breaks silently, two ways: the
guard stops catching and one corrupt file takes down a visualization the
clinician was mid-way through, or it catches too much and a datasource
that loaded nothing is reported as fine.

Six tests, no production code. A DataSourceBase subclass whose _load,
_format or inspect raises is appended to DataSource.AVAILABLE for one
test at a time, alongside a healthy neighbour, and the four halves of the
contract are pinned: wrapper.main skips the raising source and still
returns the neighbour's signals; wrapper.inspect reports load_error
carrying the real exception text; DataSourceBase.extract answers None
rather than propagating; and a _format failure keeps format_error
distinct from load_error, which is what lets a caller tell "could not
open the file" from "opened it and the contents made no sense".

Each guard was verified to be genuinely pinned by neutering it in a
throwaway copy of src/ and confirming exactly one test goes red. One
test is noted in its own docstring as double-guarded: DataSourceBase
.inspect and wrapper.inspect each catch the same failure onto the same
status, so neutering either alone leaves it green. The two tests beside
it are what pin each layer on its own.

The registry is duck-typed — wrapper.main reads NAME/MAIN_MODULE and
wrapper.inspect reads NAME/DATASOURCE_CLASS — so a fake entry needs no
decorator. ALLOW_QUICK_LOAD = False on every fake keeps the parquet
cache out of it, so the file writes nothing to disk; monkeypatch is
function-scoped because AVAILABLE is also read by the session-scoped
default_database_options fixture. Statuses are asserted as independent
string literals, never cst.InspectionStatus.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The severity to log-level fan-out was byte-identical in three modules in
three different layers -- wrapper.py, config/parsing.py and the Dash
data_callbacks.py -- and the "database_options [%s]: %s" prefix had nine
independent copies, three in each. validation.py already owned one
cross-tier phrasing (ValidationIssue.unknown_keys), so it was the
established home for how an issue is worded but not for how one is
reported. A fourth severity, or a change of prefix, was three edits with
nothing to catch a missed one.

log_database_options_issues(issues, logger) now owns both. The name says
database_options because the body hardcodes that prefix: every
ValidationIssue producer in src/ is a database_options validator today,
and a generic name over a specific message is a trap for the first
caller that is not.

Two things the extraction had to preserve and neither is visible in the
suite's output. The logger is a parameter, never a module-level one
here: a getLogger(__name__) inside validation.py would silently rewrite
every record's origin from clinical_scope.wrapper to
clinical_scope.validation. stacklevel=2 carries the rest of that origin,
because the emitting frame moved into this module while funcName,
filename and lineno should still name the tier that found the issue --
unobserved today, since both formatters in logger_config.py print only
name, level, time and message, but a formatter is one edit from reading
them. And the table lookup keeps the old else branch's catch-all: a
severity outside the Literal reports at INFO rather than raising, since
severity binds type-checkers and not runtime.

Verified behaviour-preserving rather than argued: all three call sites
were driven against a scratch copy of HEAD's src/ and the emitted
(logger name, level, funcName, filename, message) tuples diffed
identical. Each of the two guards is pinned by its own test -- removing
stacklevel=2 reddens exactly one, re-parenting the logger reddens four.
The three caller tests pass against unmodified HEAD as well, which is
what makes them characterization tests rather than tests fitted to this
implementation.

Deliberately not done: no exported SEVERITY_* string constants and no
sweep of the ~19 severity= construction sites in
database_options_parser.py and plot_types/. A named constant at three of
nineteen sites is a half-migration that leaves the vocabulary less
consistent than either extreme. data_callbacks.py's severity filters and
colour map are untouched for the same reason.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
load_database_options_from_path is what README.md tells library users to
call and what all three scripts/ entry points import, and no test had
ever executed it. Its .xlsx branch is the only route to
xlsx_to_database_options; the suite reached that module solely through
the bytes variant the Dash upload uses. So the file-path route into the
primary clinician-editable config format was untested end to end,
build_patient_options and load_options had no coverage anywhere, and the
sidecar the .xlsx branch writes into the user's own directory had never
been observed by anything.

Thirteen tests, no production code. Both formats load through the path
API, an unknown suffix raises, a missing file raises, and .JSON is
accepted because the dispatch lowercases -- a Windows-cased spreadsheet
is the realistic way that branch is reached.

Three behaviours are pinned deliberately rather than incidentally. The
<stem>_from_xlsx.json sidecar is asserted to land beside its input, to
parse back to the returned dict, and not to appear in a fixed location:
a load that writes into the caller's data folder is surprising enough
that it should be a decision on record, not a discovery. Its failure
path is pinned too -- a read-only directory still yields the parsed
config, and the test asserts the logged warning, because otherwise a
sidecar write that was never attempted passes as one that was attempted
and forgiven. And load_options answering {} for a missing file while
load_database_options_from_path raises FileNotFoundError is now a test
with the asymmetry in its name: an absent patient_options file is
optional, a config the caller named by path is not.

Each of those is verified load-bearing by neutering one guard at a time
in a throwaway copy of src/ and confirming which tests go red -- reading
the greens, not the reds. Two came back green on the first pass and both
were fixed rather than explained. Dropping the Path() coercion in
build_patient_options reddened nothing, because every test happened to
pass a Path while the signature promises str | Path; one test now passes
a str, and the neutering reddens it. Deleting the sidecar write call
outright left the read-only test green, since it passed on the file's
absence -- the state a removed call also produces; the caplog assertion
is what tells those two apart.

Fixtures are copied into tmp_path before loading, never read in place,
because of that sidecar write. Representative keys are asserted rather
than whole-dict json/xlsx equality: that parity oracle already lives in
test_example_assets.py, and a second copy would make both files fail
together on an unrelated config edit. Worth knowing for anyone extending
this file -- tests/data/option_files/example_database_options.{json,xlsx}
are not a parity pair despite the shared stem. The .json carries 13
top-level sections including edf; the .xlsx has two sheets and parses to
five, with different group and loop names. Nothing enforces parity there,
unlike the shipped example/demo_database pair.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
validate_database_options checked the keys inside a section but never the
section name, so "servo-u" or "mindray_respi_wave" discarded a whole
device's loading configuration in total silence: no issue, no log line,
just a run that reports 0 datasource(s). The one question a clinician
could ask — "why is this device missing?" — had no answer on any surface
the app offers. The Dash UI already renders validation issues in orange
on config upload, so a warning lands where they are looking, at the
moment they choose the file.

A top-level key that is not global, does not start with other::, and is
not a registered datasource name now yields exactly one warning naming
the key and the three spellings a section name may take. The valid names
are derived from DataSource.AVAILABLE rather than restated, so a new
datasource cannot make a valid config warn. The stem after other:: is a
user file name and stays unchecked — the validator never sees the patient
folder, so a misspelled stem is a separate problem for a later fix.

The message says no data is loaded for the section, and deliberately does
not say the section is ignored: plot_assembly._flatten_config iterates
every top-level section regardless of name, so a mis-named section's
grouped_fields and derived-plot entries do still reach assembly and can
still resolve. What is actually lost is the loading half — wrapper.main,
wrapper.inspect and extract_datasource each gate on membership of
DataSource.AVAILABLE.

The check runs before the isinstance guard, so {"servo-u": "hello"} warns
too, where it used to be silent, and continues past _validate_section, so
a section no datasource reads gets one message about its name instead of
a cascade about its keys. Nothing gates on the returned issues; every
caller only logs or renders them, so no file that loads today can stop
loading, and all four shipped configs were confirmed to stay warning-free
in both formats.

The demo config's guard lives in test_example_assets.py, the module that
already owns example/demo_database/ assertions — tests/README reserves
the reach out of tests/ for demo_patient/ alone. Each guard was verified
by neutering it in a throwaway copy of src/ and reading which tests stay
green; the accept-case tests are regression guards and are not counted as
coverage of the new behaviour.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
OtherDataSource is the only datasource that reads its own files, and it
spelled the same normalization tail twice: set_datetime_index, the
to_numeric coercion loop, then deduplicate_then_sort_index, once in
main and once in inspect, 150 lines apart. The two had already drifted
(df.columns against list(df.columns)), and the suite exercises main far
more heavily than inspect, so the second copy is the one that would rot
unnoticed. _normalize_on_time_axis now holds the sequence once.

The read deliberately stays at each call site rather than moving into
the helper. Every way the readers fail is a ValueError subclass:
EmptyDataError for an empty CSV, pyarrow's ArrowInvalid for a corrupt
parquet, and _load_single_file's own ValueError for an unsupported
extension. A read pulled inside the helper would land in the caller's
except ValueError, which exists to mean "this file carries no time
axis", so an unreadable file would be reported as a missing datetime
column: the inspect entry would lose the file-name prefix that
"Preserve the real exception text in OtherDataSource.inspect" added,
and the log line would name the wrong cause. Confirmed by neutering,
not argued: moving the read into the caller's try reddens both
TestInspectUnreadableFile cases and logs "no datetime index in
'corrupt_file.parquet'".

main's loaded_files.append moves from between set_datetime_index and
the coercion to just after the helper, still ahead of _format so a file
the datetime window later empties is still recorded as read. That list
drives the source symlink, which is other's only provenance. The move
can change behaviour only if the coercion or the dedup raises, and
neither can: to_numeric(errors="coerce") raised nothing over every
column dtype these readers produce, its one raiser needs a duplicate
column label (making df[column] a DataFrame) which raises TypeError and
is unreachable anyway since read_csv mangles duplicate headers and
read_parquet rejects duplicate fields, and deduplicate_then_sort_index
only reads index predicates on the DatetimeIndex set_datetime_index
guarantees.

The no-time-axis path this change moves was untested in both callers.
Four tests now pin it: main skips the file, still loads its sibling and
leaves the skipped file out of the symlink set; inspect reports
load_error carrying the reader's bare message with no file-name prefix,
and still describes the sibling. All four pass against unmodified src/
at HEAD, so they pin the old contract rather than this implementation.
Both callers were also driven over a fixture folder before and after
and produce a byte-identical observable.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
renderer.py decides which subplot a clinician's mark lands on and at what
clock time; either being wrong looks entirely plausible on screen until
someone re-reads the study. Its lines were already 95% executed by the
callback tests, but almost nothing asserted what they built: no test in
the repo called annotation_to_shapes, build_figure_overlays or
normalize_annotation_for_display by name, and the pre-existing subplot
fixtures carry no yaxis key at all, so the "y3 domain" branch was never
once evaluated with a real axis.

Adds 47 tests over shape geometry (type, coordinates, axis refs), subplot
resolution including the removed-subplot skip, and timezone normalization
asserted as literals across three zones plus a January/July pair that a
fixed-offset implementation would fail.

Mutation testing, not the coverage number, drove what to assert. Hardcoding
the event or window xref, detaching the point dot's coordinates from its
data, or making the window label ignore its subplot each left every test in
the repo green beforehand; each now reddens the test that names it. The
point case mattered most: Annotation.create hides a point's label, so the
dot is the whole rendering of a default point, and none of it was pinned.

The module docstring claimed the x-axis reference "is always x for
time-series plots because all subplots share the same time axis". Both
halves were wrong. Figures are built with shared_xaxes=False and time rows
are linked with plotly's matches, so they pan and zoom together while
keeping distinct refs, and every annotation stores the ref of the trace its
click landed on. Corrected to what the code does; docstring only, no
behaviour change.

Assertions are limited to placement fields, so restyling an annotation
cannot redden the file.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
deduplicate_then_sort_index kept the first row per timestamp and said
nothing about the rest. A CSV whose timestamp column has lower resolution
than its sample rate — a second-resolution export of a 100 Hz channel,
which is the tidy clinician CSV the other datasource exists for — lost
everything but one sample per second in silence. Measured on a 6000-row
file: 60 plotted points, 99% discarded, no message from main, from
inspect, or in the figure, and the curve it draws looks entirely
plausible at 1 Hz.

inspect made it worse rather than catching it. other deduplicates before
building its column table, so raw_point_count reported 60 for that file:
the one surface a clinician would consult to check for data loss confirmed
the loss had not happened.

The warning goes in the function rather than at the call sites, so one
line covers all eight and every datasource. other threads the file name
through _normalize_on_time_axis, which since the per-file read was
collapsed is the single dedup call site serving both main and inspect, so
both paths name the file that lost rows. The other seven pass nothing and
report counts only; naming their source is a separate change, since
several concatenate many files before deduplicating and could not name one
file if they wanted to.

The dedup and sort logic is untouched. Verified rather than asserted: the
returned frame is byte-identical across sorted, unsorted, duplicated,
empty, single-row, all-identical and NaT-indexed frames, including the
no-copy fast path and the input never being mutated.

Noise was measured before the change, not predicted. Instrumenting every
binding of the function and running the full demo patient and
Patient_difficult_format through main against two option sets produced
zero row drops, and a handler teed onto the logger across the whole suite
catches nothing but the new tests. No file that loads today can stop
loading: this adds a log call and a keyword-only parameter with a default.

For a log line the prose is the whole contract — there is no return value
or state to check it against — so the message's meaning is pinned, not
just its numbers. The test fixture makes dropped (5), kept (3) and
colliding stamps (2) three different numbers so "5 of 8" can only be the
intended pair, and two assertions pin the direction of the count and the
identity of the survivors: rewriting "Dropped" as "Kept", or describing
the survivors as the rows that had no duplicate, each left the entire
suite green beforehand and now reddens the test that names it. Word order,
voice and phrasing stay free to change.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The class docstring told a new-datasource author to implement `_find()`.
It is not abstract, has a working default over `find_files`, and no
datasource in the repo overrides it, so the instruction bought a method
nobody needs. `_load()` is the only abstract method; the real unstated
requirement is setting `OPTIONS_MODULE`, which `__init_subclass__` reads
for the datasource name, the cache file name and the capability flags.

Eight further docstrings restated their own method name. They now carry
what the signature cannot: that a failed cache write is swallowed on
purpose, that `_apply_time_shift` rebinds the index in place and returns
the same frame, that `main` returns [] for absent data but raises on a
genuine failure, that `filter_date=False` reads the bounds as times of
day, and that `_find` yields one file per stem rather than every match.

Also corrects the comment above inspect's field_display strip, which
named `_load()` as the thing being narrowed. `_load()` takes only a path
and cannot see field_display (ADR-0010); it is the parquet cache read
that would otherwise be column-pruned, and `configured_columns_only`
puts that narrowing back.

Docstrings and comments only: the AST with docstring nodes stripped is
byte-identical to HEAD's.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
"Which datasources does this run touch" was answered three times: an
identical `requested_sources` comprehension in main, inspect and
extract_patient, each followed by a loop over the whole registry with an
`if name not in database_options_global: continue` guard re-applying the
same filter. The copies already differed in whether they reported
progress, so a reader could not tell which differences were intentional.

`_requested_datasources` answers it once and the three loops iterate its
result, which makes the in-loop guards dead. `AVAILABLE` is named in one
place. The two remaining `"data_folder"` subscript literals become
`cst.PatientOptions.PathDataFolder.NAME`, which the file already used
twice.

Behaviour-preserving, checked rather than assumed: HEAD and this change
were probed over 6 configs x {main, inspect} x {callback, none},
capturing the full (current, total, name) sequence and the return shape.
Identical throughout, including nothing requested, unregistered keys
only, and an `other::<stem>` key.

tests/unit/test_progress_reporting.py is new and pins what nothing
covered. `progress_callback` had no test anywhere in the suite, so a
refactor that counted over the registry instead of the requested set --
a three-datasource run stalling at "3/9" -- would have committed green.
It pins the count, the registry order, and the placement: the progress
bar labels the datasource being worked on, so the call has to precede
the read.

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

This branch has not been deployed

No deployments
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.

2 participants