-- ============================================================================= -- 008 - public.shift_log, the operator shift log. -- -- ############################################################ -- ## DEMO DATA. NOT A REAL SHIFT LOG. DO NOT QUOTE. ## -- ############################################################ -- -- WHY THIS IS A TABLE AND NOT A DOCUMENT, AND NOT THE HISTORIAN -- ------------------------------------------------------------- -- A shift log is continuously updated, which rules out both of the stores we -- already have: -- -- * NOT doc_chunks. That store is built on doc_number + revision + -- effective_date, and nothing in it is citable until a human has confirmed -- that header. A record that changes every shift would need re-confirming -- every shift, and a confirmation gate asked that often stops being a gate. -- It is the same mechanism that makes a procedure citation trustworthy, so -- wearing it out here would cost us something real elsewhere. -- -- * NOT the historian. Three reasons and the first is decisive: we hold -- READ-ONLY on imh and may not write to it at all. Beyond that, the -- historian is keyed on the CI Server item and only the item - a log entry -- has no item, no register and no scan interval, and inventing one is what -- produced all three Phase 5 findings. And its retention is seven days, -- so the one source that could outlive the historian would expire with it. -- -- So it is a third evidence source alongside the historian and the document -- library: free text, at irregular times, by a named author. -- -- WHAT THIS DELIBERATELY DOES NOT DO -- ---------------------------------- -- Entries are NOT citations. Citation means a controlled document with a -- confirmed revision, and an operator note is not that however true it is. -- Shift-log evidence is returned as rows on the historical lane, labelled as -- an operator record, and the Citation contract is untouched. Extending -- Citation to cover a second kind of source is a real design decision and is -- not being made here. -- -- There is no retention limit on this table. The seven-day query window in -- tools/shiftlog.py is there to match the historical lane's window, not -- because anything here expires. When the query window becomes variable -- (REQUESTS.md), this table can answer questions the historian cannot. -- -- WRITE PATH: none. There is no UI and no API that inserts here. The seed -- below is the whole content, and it is demo fiction. An operator writing a -- real entry is Phase 9 work that has not been scoped. -- ============================================================================= CREATE TABLE IF NOT EXISTS shift_log ( id BIGSERIAL PRIMARY KEY, entry_time TIMESTAMPTZ NOT NULL, -- UTC. Converted to site time once, on the way out. shift TEXT NOT NULL, author TEXT NOT NULL, equipment_id TEXT REFERENCES equipment (equipment_id), entry_text TEXT NOT NULL, -- Mirrors fixture.is_fixture and the DEMO- document numbering: an entry -- nobody wrote must never be indistinguishable from one somebody did. -- tools/shiftlog.py carries this out to used_fixture_data on the answer. is_demo BOOLEAN NOT NULL DEFAULT FALSE, CONSTRAINT shift_log_shift_ck CHECK (shift IN ('day', 'night')) ); CREATE INDEX IF NOT EXISTS shift_log_entry_time ON shift_log (entry_time DESC); CREATE INDEX IF NOT EXISTS shift_log_equipment ON shift_log (equipment_id); -- ----------------------------------------------------------------------------- -- Demo entries. -- -- Anchored to the START OF TODAY IN SITE LOCAL TIME, then placed at fixed -- local times on fixed days back. Two properties matter and the first is not -- obvious: -- -- * The shift label has to match the clock. An earlier version offset every -- entry by a whole number of hours from now(), which is relative and -- therefore never goes stale - but it slid every entry's local hour as the -- day wore on, and produced night-shift entries timestamped 10:00. Demo -- data that contradicts itself on the face of it is worse than none. -- -- * Relative, not absolute, so re-running always lands entries inside the -- rolling seven days. The alarm fixtures are absolute and have gone stale; -- this does not. -- -- ONE anchor for the whole load, not a per-statement now(), for the reason -- 002_fixtures.sql gives: a statement-by-statement now() drifts within a load. -- -- The site timezone is named here because this seed places entries at local -- clock times and there is no other way to do that. It must match -- SITE_TIMEZONE in ~/ai/api.env. The conversion still happens exactly once, -- and never in a prompt. -- -- Entries in the future are dropped rather than inserted: a load run at 03:00 -- would otherwise write a day-shift entry that has not happened yet. -- -- Re-runnable: demo rows are deleted and reinserted. Only rows marked is_demo -- are touched, so a real entry could never be removed by a re-run. -- ----------------------------------------------------------------------------- DELETE FROM shift_log WHERE is_demo; INSERT INTO shift_log (entry_time, shift, author, equipment_id, entry_text, is_demo) SELECT entry_time, shift, author, equipment_id, entry_text, TRUE FROM ( SELECT ((a.today - days_back * interval '1 day' + local_time) AT TIME ZONE 'Australia/Sydney') AS entry_time, v.shift, v.author, v.equipment_id, v.entry_text FROM (SELECT date_trunc('day', now() AT TIME ZONE 'Australia/Sydney') AS today) a, (VALUES (6, time '02:40', 'night', 'demo:J. Whitmore', 'PU-302', 'PU-302 tripped on overload at 02:20 and was reset from the panel at 03:05. ' 'Ran up clean afterwards. Mechanical to look at the strainer next day shift.'), (6, time '13:15', 'day', 'demo:A. Ngata', 'PU-302', 'Strainer on PU-302 cleared - heavy rag. No repeat of the overload trip through the shift.'), (5, time '09:55', 'day', 'demo:A. Ngata', 'WW-101', 'Wet well level instrument LIT-101 dropped out briefly around 09:40. ' 'Reading recovered on its own. Logged for instrument tech to check the cable gland.'), (4, time '01:30', 'night', 'demo:J. Whitmore', 'PU-303', 'PU-303 seal leak alarm came in and returned. Small weep at the seal, not running to drain. ' 'Left on duty rotation, flagged for maintenance.'), (3, time '15:20', 'day', 'demo:R. Patel', 'PU-301', 'PU-301 vibration alarm during the afternoon peak. Settled once flow came off. ' 'Nothing obvious on inspection.'), (2, time '04:05', 'night', 'demo:J. Whitmore', NULL, 'Quiet shift. Station on auto throughout, no alarms, no operator intervention.'), (1, time '11:40', 'day', 'demo:R. Patel', 'WW-101', 'Heavy rain from mid-morning. Wet well worked hard but stayed well under the weir. ' 'All three pumps called at times.'), (0, time '06:10', 'day', 'demo:A. Ngata', NULL, 'Handover: station on auto, no outstanding alarms. ' 'PU-302 strainer still to be signed off by mechanical.') ) AS v(days_back, local_time, shift, author, equipment_id, entry_text) ) placed WHERE entry_time <= now(); DO $$ DECLARE n INT; newest TIMESTAMPTZ; oldest TIMESTAMPTZ; BEGIN SELECT count(*), min(entry_time), max(entry_time) INTO n, oldest, newest FROM shift_log WHERE is_demo; RAISE NOTICE 'shift_log: % demo entries, % to %', n, oldest, newest; -- Every entry must be inside the rolling seven days the historical lane -- asks for, or the demo shows an empty log. IF n < 6 THEN RAISE EXCEPTION 'expected at least 6 demo shift log entries, loaded %', n; END IF; IF oldest <= now() - interval '7 days' THEN RAISE EXCEPTION 'oldest demo entry % is outside the 7 day query window', oldest; END IF; IF newest > now() THEN RAISE EXCEPTION 'demo entry % is in the future', newest; END IF; END $$;