Commit graph

5 commits

Author SHA1 Message Date
Claude
7ea8636264 Add the eight live-model failures to the eval set
CLAUDE.md: add the failing case before fixing it. These were added after, not
before - recording that rather than quietly complying.

67 -> 75. L01-L05 are the five contract-shape failures; L06-L08 cover the
retrieval and schema change: that the header fields reach the answer, that step
sections never do, and that a document marked uncontrolled is identified rather
than reported as nothing found.

Every one is a case no stub run could have produced, which is the reason the
67-question set passed for weeks without catching any of them. REQUESTS.md
still says 67 and needs updating when the acceptance run is scheduled.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-28 10:54:37 +10:00
Claude
e1499864d9 Pin the site timezone on every Cube query
Every Historical and Advisory answer stated a window in AEST and queried one
shifted by ten hours.

rolling_window() builds its boundary strings in SITE_TIMEZONE - that is the
whole point of it, and its docstring says so. metrics.run() then posted the
query to Cube with no timezone at all, and Cube defaults to UTC. So
"2026-08-14T15:22:13" meant 15:22 Sydney to the code that produced it and 15:22
UTC to the engine that ran it, and MetricResult.time_window reported
SITE_TIMEZONE from config rather than whatever the query actually used, so the
two could not disagree visibly.

Measured on the fixtures, same dateRange, one field changed:

    timezone UTC               8019 samples
    timezone Australia/Sydney  8619 samples

600 samples. One per minute, ten hours, exactly the offset.

Nothing about the answer looked wrong. The prose was right, the count was a
real count, the window description was correctly formatted and correctly named
AEST. It was only visible by reading the Cube query in the UI's "show working"
panel - which is an argument for that panel existing, and an argument for
looking at the thing in a browser rather than trusting curl against the API.

  - check_cube_query() now takes site_timezone and pins it onto the query, at
    the single point every Cube query passes through. Per-query-builder is the
    wrong place: "remember to set the timezone" is not a control, and this
    defect is what forgetting looks like. An explicit timezone already on the
    query is left alone.
  - time_window now reports capped["timezone"] - the timezone the query ran in,
    not the one it should have run in.

An unpinned timezone belongs in the same guardrail as an unpinned date range,
and for the same reason: both make an answer unreproducible. The difference is
that an unpinned date range is obvious in the query and an unpinned timezone
is invisible.

eval case H28 records it. Two unit tests: the timezone is pinned, and an
explicit one is not overridden.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 15:23:27 +10:00
Claude
939cc2c83c Defer two Phase 5 findings to Phase 4, where imh can settle them
Both were found hand-verifying the measures on fixtures, and both are left
unfixed deliberately: fixing either now means guessing at what imh contains,
and a guess baked into the seed data or the models is harder to find later than
an open finding. Flagged at Phase 4, in the README, in the model files at the
point of use, and as eval cases that fail until they are settled.

(a) The wet well level tag does not join, and fails as "no records found".

    History is keyed PS_STN_WET_WELL_LEVEL, which process_values.yml hardcodes
    in seconds_above_high_level_alarm and seconds_above_lshh. tags.csv carries
    that name only as an ALIAS of LIT-101, so public.tags has no row with that
    tag_id and all 43,201 level rows - a third of the history, on the most
    important tag at this station - are unreachable from a tag-level lookup.
    Resolve "wet well" -> WW-101 -> LIT-101 -> filter history on LIT-101 ->
    zero rows -> "no records found".

    That is the failure mode worth being loudest about: it is the safety
    behaviour, produced by a key mismatch rather than by an absence of data,
    and indistinguishable from the real thing on screen. Filtering by
    equipment_id works, so whether a level question fails depends on which path
    the agent takes.

    The two flow tags use the opposite, self-consistent convention -
    PS_STN_INFLOW is a row in its own right and FIT-201 is marked NOT
    HISTORISED - so applying that to level is the likely fix. It still waits
    for the register map and for imh to say what CI Server historises the point
    as. Seed, hardcoded model names and 002_fixtures.sql change together.

(b) alarms.first_alarm and last_alarm return UTC, not SITE_TIMEZONE.

    Cube converts time dimensions to the query timezone; a min/max measure over
    a timestamp comes back unconverted. The Sydney day bucket for 2026-08-01
    returns 2026-07-31T20:00:35 - the right instant, ten hours and one calendar
    day out, beside a bucket label that IS in site time.

    This breaks "convert to SITE_TIMEZONE exactly once, in Cube", and the fix
    has to stay in Cube - the API compensating with timezone arithmetic is the
    thing that rule exists to prevent. Which fix is right depends on whether
    imh stores UTC or local, which is Phase 4 task 4. Until then these two must
    not be quoted to an operator as a clock time.

Phase 4's gate gains an item for each. Everything else verified in this pass -
alarms, operations, the equipment join, alias resolution and the Sydney/UTC day
boundaries - matched hand-written SQL exactly.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 14:36:47 +10:00
Claude
d6b6f4f116 Fix two Cube measures that were invalid SQL
Hand-verifying the measures against the fixtures on lin001, per the Phase 5
gate. Two of them had never executed anywhere, and both failed outright rather
than returning a wrong number - which is the good version of this, but they
failed at the point an operator asks a question, not at review.

  - time_weighted_avg put LEAD() inside SUM(). Postgres rejects that flatly:
    "aggregate function calls cannot contain window function calls". The per
    sample duration now comes from the cube's source query, which changes
    sql_table to sql, and the measure just sums value * duration over duration.
    The last sample of each tag gets a NULL duration and SUM skips it, which is
    correct - how long it stood is not yet known.

    This is the measure that matters most later. On the regular one-minute
    fixtures it agrees with avg_value to thirteen decimal places
    (42.45934027777778 against 42.45934027777775), which proves it is wired up
    and proves nothing about imh, where the deadband makes samples irregular
    and the two will not agree. Re-verify it there.

  - p95_value applied the quality filter through a Cube measure `filters:`
    block, which lands outside the aggregate and cannot work on an ordered-set
    aggregate: "column process_values.quality must appear in the GROUP BY
    clause". Folded into the CASE inside PERCENTILE_CONT, whose NULL handling
    does the exclusion.

Also: the priority dimension said only SPILL and PUMP_TRIP are priority 1,
while the data has LEVEL_SIGNAL_FAULT at priority 1 too - correctly, losing the
level signal on a well that can spill is a priority 1 condition. That comment is
the line an engineer reads when checking a priority_1_count, so it disagreeing
with the data matters more than its length suggests.

eval cases H24 and H25 record the two failures, added before the fix.

Verified against hand-written SQL on the same pinned windows: p95_value
61.104999999999976 and time_weighted_avg 42.45934027777778 both match to the
floating point tail, as do sample_count, avg_value, max_value, min_value,
seconds_above_high_level_alarm (7680 = 128 samples x 60) and every measure in
alarms and operations.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 14:36:20 +10:00
Claude
34d2ccc576 Scaffold the WRPS plant operations assistant repository
Build spec and host brief carried in from C:\Claude and WRPS/02-env; the
plant model (equipment, tags, alarm bitmask, enums, unit conversions) is
derived from WRPS/04-plc/register-map.csv, WRPS/05-scada/modbus/scada-points.csv
and WRPS-CTL-003.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-20 13:56:32 +10:00