Skip to content

fix: derive activity from observed print state, not equipment_status - #1

Open
cyrilcaoyang wants to merge 1 commit into
mainfrom
fix/activity-from-print-state
Open

fix: derive activity from observed print state, not equipment_status#1
cyrilcaoyang wants to merge 1 commit into
mainfrom
fix/activity-from-print-state

Conversation

@cyrilcaoyang

Copy link
Copy Markdown
Collaborator

Why

The v1.2 migration (e2ca7ff) computed activity from equipment_status:

activity=("idle" if state == "ready" else "unknown"),
activity_since=now if state == "ready" else None,

STATUS_SPEC §2.3 forbids exactly that ("MUST derive activity from observed hardware state, never from equipment_status"). Live consequence, observed on both printers after deploying that commit: equipment_status: busy with activity: "unknown" while printing — which also violates the §2.3 invariant busy ⇒ running. And activity_since was the status-build instant, so it reset every poll and could never express a duration.

What changed

activity now maps the printer's own observed gcode state, independently of the health mapping:

observed gcode state equipment_status activity
IDLE, FINISH ready idle
PREPARE, RUNNING, PAUSE busy running
FAILED error idle
unrecognized, MQTT down, no telemetry yet, stale unknown unknown
  • PAUSErunning — the job is in flight and the printer cannot take another; keeps busy ⇒ running true. Sub-state stays legible in components["print_job"].state and message.
  • FAILEDidle — the job stopped; §2.3 permits any activity under error.
  • Untrustworthy observation → unknown, never a stale idle/running.
  • activity_since is detected in the background poll (routes stay pure cache reads per AGENTS.md) and is null when the transition was never observed — restart mid-print, or telemetry that went stale and recovered. Stamping first-observation time would report a far-too-short duration for the very in-progress operation the field exists to measure.
  • activity is evaluated at request time so it cannot contradict equipment_status within one response; activity_since is emitted only when the request-time answer matches the tracked one.

README now defines the primary operation (running a print job) and documents the mapping, closing that §9 v1.2 checklist item. Also fixes the ruff I001 left in models.py.

Verification

  • uv run pytest -q → 17 passed (was 11); new tests/test_monitor.py covers cold-start-null, observed transition stamped and stable across later polls, stale-clears-mid-job, and monitor-failure.
  • uv run ruff check . → clean.
  • Deployed on the dashboard host and verified live: both printers report busy + activity: running, activity_since: null (both jobs predate the restart), and the dashboard aggregator polls all three ids with fetch_error: null.

🤖 Generated with Claude Code

STATUS_SPEC §2.3 requires `activity` to come from observed hardware state
and forbids computing it from `equipment_status` — the v1.2 migration did
exactly that (`"idle" if state == "ready" else "unknown"`), so both live
printers reported `busy` with `activity: "unknown"` while printing, which
also breaks the §2.3 invariant `busy ⇒ running`.

Map the printer's own gcode state on its own axis instead. PAUSE counts as
running: the job is in flight and the printer cannot take another one, and
that keeps `busy ⇒ running` true (the sub-state stays legible in
`components["print_job"]` and `message`). FAILED is idle — the job stopped,
and §2.3 permits any activity under `error`. MQTT down, telemetry not yet
arrived, stale telemetry, and a monitor exception all yield `unknown`
rather than a stale idle/running.

`activity_since` was `now` on every status build, so it reset on every
poll and could never yield a duration. The transition is now detected in
the background poll (routes stay pure cache reads, per AGENTS.md), and it
is null whenever the transition itself was never observed — a restart
mid-print, or telemetry that went stale and recovered. The span began
before this service could see it, so stamping first-observation time would
report a far-too-short duration for exactly the in-progress operation the
field exists to measure.

`activity` is evaluated at request time so it cannot contradict
`equipment_status` within one response (staleness is the only input that
moves between polls); `activity_since` is emitted only when the
request-time answer matches the tracked one.

README documents the primary operation (running a print job) and the state
mapping, closing that §9 v1.2 checklist item. Also fixes the ruff I001 the
migration commit left in models.py.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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