feat(cassettes): swap the count-publish endpoint for operation endpoints
The operator can no longer write a count. POST .../cassettes/ops records one operation — refill, empty, recount, set_denomination — and publishes the machine's recent window; GET .../cassettes/ops lists them newest first with acked_at, so the dashboard can tell a delivered operation from one merely sent. POST .../cassettes/publish is gone, along with update_cassette_config and UpsertCassetteConfigData. Nothing in the operator can now set a count, which is the point: a value with one writer cannot be clobbered. Under the old endpoint a dashboard form loaded before a dispense silently discarded that dispense on publish, and neither side could detect it — addressable events order by created_at at second granularity and a relay returns OK for an event it then drops, so the losing writer is never told. The op is recorded before the publish and is deliberately not rolled back when the publish fails. It records something that physically happened; notes went into a bay whether or not a relay was reachable. The window carries recent operations rather than just the newest, so an op that missed its own publish rides out with the next one. Validation rejects an unpaired machine and a position the machine has not reported. Bay count stays hardware-determined.
This commit is contained in:
parent
3d8368bcc4
commit
2ac3e2064e
5 changed files with 89 additions and 214 deletions
42
crud.py
42
crud.py
|
|
@ -36,7 +36,6 @@ from .models import (
|
|||
UpdateDepositStatusData,
|
||||
UpdateMachineData,
|
||||
UpdateSuperConfigData,
|
||||
UpsertCassetteConfigData,
|
||||
UpsertDcaLpData,
|
||||
)
|
||||
|
||||
|
|
@ -1446,10 +1445,11 @@ async def upsert_fleet_snapshot(
|
|||
# Row lifecycle per #29:
|
||||
# - First population for a (machine_id, position) pair → apply_reported_state
|
||||
# (consumer reading the ATM's one-shot bitspire-cassettes-state event)
|
||||
# - Operator edit of denomination or count → update_cassette_config
|
||||
# (refuses to create new rows; the slot count is hardware-determined)
|
||||
# - Row creation/deletion for a new position → admin only, via ATM
|
||||
# re-provisioning + new bootstrap event (not exposed in v1 here)
|
||||
# - The operator does NOT write these rows. It records operations
|
||||
# (cassette_ops) and the machine keeps the running count; these columns
|
||||
# hold what the machine last reported.
|
||||
# - Rows appear and disappear only as the machine reports its bay set —
|
||||
# the slot count is hardware-determined.
|
||||
|
||||
|
||||
def _as_unix(value) -> float | None:
|
||||
|
|
@ -1521,38 +1521,6 @@ async def list_cassette_configs_for_machine(
|
|||
)
|
||||
|
||||
|
||||
async def update_cassette_config(
|
||||
machine_id: str,
|
||||
position: int,
|
||||
data: UpsertCassetteConfigData,
|
||||
*,
|
||||
updated_by: str | None = None,
|
||||
) -> CassetteConfig | None:
|
||||
"""Operator-driven row update: change denomination and/or count for a
|
||||
single cassette slot. Refuses to create new rows — those only land via
|
||||
apply_reported_state() consuming an ATM bootstrap event (per #29 row
|
||||
lifecycle: hardware-determined slot count, not operator-creatable).
|
||||
Returns None if the (machine_id, position) row doesn't exist.
|
||||
"""
|
||||
existing = await get_cassette_config(machine_id, position)
|
||||
if existing is None:
|
||||
return None
|
||||
update_data: dict = {k: v for k, v in data.dict().items() if v is not None}
|
||||
if not update_data:
|
||||
return existing
|
||||
update_data["updated_at"] = datetime.now()
|
||||
update_data["updated_by"] = updated_by
|
||||
set_clause = ", ".join(f"{k} = :{k}" for k in update_data)
|
||||
update_data["mid"] = machine_id
|
||||
update_data["pos"] = position
|
||||
await db.execute(
|
||||
f"UPDATE spirekeeper.cassette_configs SET {set_clause} "
|
||||
"WHERE machine_id = :mid AND position = :pos",
|
||||
update_data,
|
||||
)
|
||||
return await get_cassette_config(machine_id, position)
|
||||
|
||||
|
||||
async def apply_reported_state(
|
||||
machine_id: str,
|
||||
event_id: str,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue