← jason fetzerShift Investigatordocs
Back to the investigatorGolden evalsSynthetic data / read-only tools

Use the local learning interface

Install and run from the repository root:

python -m venv .venv
.\.venv\Scripts\python -m pip install -e . pytest
.\.venv\Scripts\python -m investigator.server

Open http://127.0.0.1:8765/ on the same computer. The server is local only. No API key, model call or email service is involved.

Use Python 3.11 or newer. Run the installation commands once; on subsequent visits run only the server command in the project virtual environment. Stop it with Ctrl+C in that terminal. Python changes require a restart; frontend changes require a page reload. To use another port, append --port 8766 and open http://127.0.0.1:8766/. Use the numeric loopback host, not localhost.

The page is the layout accepted in docs/design/2026-11-09-ask-and-answer/ (issue #63), arranged as a chat (issue #86): a left rail, a transcript of answer cards with the newest at the bottom, and the ask bar pinned under the transcript. The page itself never scrolls; only the transcript does.

  • The rail. Looking at holds the whole selection: a Production day date with ‹ › steppers (weekends are skipped), Shift as buttons (1 · 7–3 / 2 · 3–11 / 3 · 11–7 / All — "All" is the 24-hour scope) and Window as buttons (Day / Week / Month / 4-4-5). A plain-English scope sentence under them reads the selection back, for example "Monday 14 Sep 2026 · shift 2 · 15:00–23:00 · compared with Friday 11 Sep", and says when a comparison is withheld because nothing earlier exists in the store. Ask in one click lists the canned questions (seven, the rest under More questions; the per-shift matrices are hidden under Week, Month and 4-4-5, where the Period report button appears). A canned question always runs the offline planner: a fixed query, no model, no wait. Your rates is a static placeholder until the cost model (issue #31). Agent and advanced is a disclosure holding the agent select (Offline / free by default, Anthropic / your key, OpenAI / your key; on the hosted demo the select stays on Offline and the analyst uses the site's own key), the key field (loopback only), the "Include a bad trim record" toggle and Clear the thread.
  • The ask bar. Pinned at the bottom of the main column, always on screen. One box and Ask, which always runs the offline planner, whatever the agent select says. Enter sends, Shift+Enter starts a new line (the box grows to about six lines), focus stays in the box after a send, and / focuses it from anywhere outside another field. Ask the analyst is the only live path: it appears when the server has its own key for a live mode (or when you chose a provider and typed your key, locally), and sends the typed question to that model once. Clicking it twice while the question is out is the same card, not a second paid round.
  • The answer card. A title line — the question, then Shift 2 · Mon 14 Sep 2026 · offline · 6 queries · 39 ms, where the milliseconds are the measured wall time from the submit to the rendered card (the tooltip adds the server's own time in the queries), and the shift and window are the ones the first windowed query actually ran over: "How did September go versus August?" asked with the rail on Mon 14 Sep reads All shifts · September 2026 (2026-09-01 to 2026-09-30) — then the answer in its shape (issue #75): one headline with the asked figure in display size and what it is held against beside it, three to five findings as bullets with one bold figure each and a small link to the query and row it came from (past five, the rest fold under one "more"), the charts side by side with the chart for the answer's measure first, the window and caveats in small type, Where to look (two or three places in the records, each a link that asks its question offline), the query box as one line (6 queries · 15 ms · Show SQL), the KPI tiles, and a footer with the exports (Markdown, PDF) and "What came back, per query". A skeleton card appears in the same instant as the submit and the answer fills it in place, revealed in the order of the reasoning (issue #65): the query lines tick in about 80 ms apart (inside the one-line box), the headline and the findings type word by word at reading speed (faster for a long answer, so it is all there by about 1.5 s) with its first figure landing at once, inside 400 ms of the submit (the bar is counted from the submit, not the land: an answer that lands late shortens the wait before typing, and one that lands after 400 ms shows its first figure on its first frame), and the charts' bars grow from zero once the first sentence is down; the KPI tiles and tables are there from the first frame. The pacing only replays the finished result: each line shows a prefix of its own text and ends as exactly that text, its bold figure and evidence link restored with its last word; where to look arrives with the last word. A click or any key skips to the end, prefers-reduced-motion: reduce shows everything at once, a card reopened from the thread, or one that landed while another card was open, is shown settled, and the milliseconds on the title line are the time to the complete result, not to the end of the reveal; nothing on the page is disabled while a question is out. Questions asked in quick succession each get their card at once and are sent to the server one at a time, in order, so every follow-up carries the conversation the one before it left. At 1440×1040 the open card fits without scrolling. An offline answer's address goes in the address bar when it lands (URLS); back and forward reopen a card the page has shown without a request, and a pasted address sets the rail and lands in a new card.
  • The transcript (issue #86). The open card is the newest, at the bottom, right above the ask bar; earlier answers sit above it, oldest at the top, each still the whole card (headline and findings, charts, where to look, the one-line query box, tiles) under a title line with an Open button. Nothing is collapsed when a new question arrives: older cards move up. Open makes an earlier card the open one again, at the bottom, with its address, its exports and the full result of each query. While the open card fills (the skeleton, the landed answer, the reveal, a streamed analyst narrative) the pane follows it: its bottom stays at the pane's bottom while it fits, and once it is taller than the pane its title line stays at the top, so the answer is read from its first word. Any scroll up, however small, stops the following (the pane never pulls you back down); once you are more than about 80 px from the newest content and more arrives, a ↓ new pill appears, and clicking it, or scrolling back down to within 80 px, resumes. The pane never scrolls up on its own, and asking a question always brings the new card into view. The rail and the ask bar never move; the rail scrolls on its own only if it is taller than the window, which at 1280×720 it is not (on windows under 1080 px tall it packs tighter, and under 830 px the canned questions sit two to a row; between 851 and 999 px wide the header drops the mode badge, as on a phone, so it stays one row).
  • Reload. The thread survives a reload of the tab. The address bar's card comes back from its address as before; the cards above it are kept in this tab's sessionStorage (up to 24) as their own offline addresses and replayed as GETs of those addresses, never /api/ask, in their order and settled. Each replay counts against the visitor's rate limit like any answer, so a reload fetches only the newest few (a quarter of DEMO_RATE_LIMIT_PER_MIN when it is set, at most 8); older cards come back as their title lines ("not reloaded: Open loads it"). A card the analyst wrote comes back as the offline answer it was asked from, with the Ask the analyst button: a model's narrative is never stored or replayed. Clear the thread empties it.
  • Phone. One column: the header, the transcript, and the ask bar pinned at the bottom (above the on-screen keyboard where the browser supports interactive-widget=resizes-content); the rail opens as a sheet from the Questions button. The follow rule and the pill are the same.

The page is light by default, whatever the operating system prefers (issue #36); the Dark / Light button in the header pins a theme in this browser's localStorage, and that choice wins on later visits.

  1. Keep the defaults (Day, shift 2) and ask "Why was second shift slow? Chart downtime by reason."
  2. Read the charts and the tiles. A day question shows the shift's KPI row on seven tiles (speed with percent of target, maintenance, operator, dry-end waste, trim with shear, setups with paper changes, throughput with lineal feet per setup). Before any question, and for period questions, the strip shows the dashboard tiles for the selected window with the previous window beside; each tile's delta chip is colored by the tool's own change label (better / worse / same / neutral — the direction comes from the tool's better map, not from the sign) and reads "withheld" when the comparison is incomplete or the previous window is unavailable. Twelve tiles are visible by default; the rest sit behind "more". Grade target markers on the speed chart use each wet-end run's grade.
  3. Open the query box under the answer with Show SQL, or by clicking any finding's (query N · row M), which opens it at that query (issue #40 put the box under the title line; #75 folded it to one line under the answer, so the headline, findings and chart come first): one line per tool call, in order — tool(arguments) · rows · ms — with the exact parameterized SQL and its bound values and a Copy SQL button. The first step is open; tapping another opens it instead, and at desktop width the open step's SQL sits in a pane beside the list. Steps past the fourth (past the first on a phone) fold behind N more queries. Every figure in the answer names its query and row, the way Datasette links a value to the SQL that produced it, and the box is read-only by design: every query is a parameterized tool (agent contract), so visitors cannot edit and re-run it here; copying it is how you re-run it yourself. Each box links to "What came back", the full result of that step (window, coverage, excluded records and the result JSON) under the card's footer, and every chart and table links back to the query that produced it. "How these tiles were produced" under the KPI strip shows the same for the tiles. All of it is execution evidence, not model reasoning.
  4. Choose All under Shift, then ask "Compare that with first shift". With a single shift selected that comparison is deliberately refused ("Selected shift only"). Changing the scope keeps the cards already on the page (each carries its own scope in its title line) and starts a new server conversation; follow-ups retain history while the controls stay unchanged.
  5. Open Advanced, enable "Include a bad trim record" and repeat. The affected metrics read Incomplete, the rejected source value shows in the trail, and rankings are withheld for that shift.
  6. Save a text report or download the PDF. Both are local files; nothing is emailed.

The date selector covers the record store's production days: a rolling year, Monday to Friday from 365 days before the window's last day to that day (INVESTIGATOR_AS_OF, default yesterday; about 262 days), generated when the server starts (about twelve seconds; the image loads a prebuilt folder in about 200 ms) as the record families described in RECORDS. Each day comes from its own date-keyed draws, so a day reads the same in every window. Weekends are plant-down; selecting one is refused with the reason. A year of one machine with three seeded effects to find (in August and September 2026); this is a demonstration dataset, not a calibrated statistical sample.

To materialize and validate all XML days:

.\.venv\Scripts\python -m investigator.archive

This writes the legacy one-day XML fixture for 30 days / 90 shifts under ignored artifacts/month, with a file/hash/seed manifest. It is the older generator kept for its own tests; it is not the dataset in the day selector, which comes from the rolling-year record store (write those files with synth.write_jsonl, see RECORDS.md).

The offline planner is deliberately limited. Unsupported questions return a clear error (asking questions lists what it answers and what it refuses). A model is used only for Ask the analyst: Anthropic by default, OpenAI as an optional provider (the analyst). Application code formats the calculated summaries either way; the analyst adds a separate interpretation. See AGENT_CONTRACT.md for permissions and limits.

Ask about a shift, week or month versus the previous one

These plain phrasings run the period tools over the whole year (offline, no model call) and chart the result. With Window set to Week, Month or 4-4-5, a question that names no window gets the selected one ("Why was this shift slow?" becomes the week's KPIs, downtime Pareto and waste breakdown for the selected shift; "Show speed to target" the KPIs and totals by grade), the example chips say "this week", "this month" or "this period", and a window the question names still wins. The setup matrix, orders, notes and rankings are per shift and per day, so under a period they ask you to switch back to Day. With Period on Day the same phrasings work by naming their window:

  • "Compare this month to last" or "How did this week go versus the previous one?" — the dashboard tiles (volume MSF, lineal feet, speed with and without stops, downtime, stops, minutes per stop, trim, shear, dry-end with its bond/warp/misalignment split, bale, unexplained, maintenance + PM, operator, setups, paper changes) with the previous window, the delta and whether lower or higher is better.
  • "How did September go versus August?" — the same table for two named months.
  • "How did shift 2 go compared with last week?" — a named shift filters the window; with a single shift selected in the Shift segment, that shift is used.
  • "Which downtime reason cost the most minutes this month?" — a Pareto with share and cumulative share and last month's minutes beside; "where did we stop most often this week" groups by place and counts.
  • "Is it bond or warp?" — the waste breakdown (day window unless you name a week or month).
  • "Stops per day this week" — per production day plus the Monday-to-Friday profile.
  • "Trend of trim over eight weeks" — a weekly line for one metric (trim, shear, dry-end, warp, bond, misalignment, moisture, speed, downtime, stops, minutes per stop, MSF per day, lineal per setup, lineal per paper change).
  • "Compare this period to the previous period" — the same tiles over 4-4-5 accounting periods; any comparison with unequal production days says so and points at MSF per day and the ratio tiles.
  • "Volume by grade this month", "How much 275-BC did we run this week?", "Which grade is eating the most downtime?" — totals by board grade with speed against target and allocated downtime.
  • "How much paper did we consume this month?", "Rollstock consumed this week" — paper consumed from the wet-end records as lineal feet and MSF per grade and web width, with paper runs and feet per run, against the previous window.
  • "Over/under-run by knife this week", "Are we inside the cut range?" — nominal versus actual cuts per knife, drawn as a bullet chart against the ±2 % band.
  • "Where is the speed opportunity this month?", "Which crews run faster, and does it cost rejects?" — typical versus best-fifth speed by grade and width or crew, in hours, with the dry-end rate of the fast setups beside it.
  • "Which warp are we seeing this month, up warp or twist warp?" — the waste breakdown with the reported sub-reasons.
  • "What were the top 3 contributors to speed variation?", "What drove speed this week versus last week?", "Why did speed differ on shift 3 versus shift 1?" — the speed gap against each grade's trailing-year baseline (or the previous window, or another shift) split into stops, setups, running under baseline and grade mix, with planned maintenance and external stops (a power outage) as buckets of their own, which add up to it, each marked controllable, schedule-driven (the mix, planned maintenance) or external; only the controllable ones are priced as machine time; drawn as a waterfall.

The window is anchored on the selected day: a week is its Monday–Friday production week, a month its calendar month. Windows outside the rolling year are unavailable, never zero, and deltas are withheld unless both windows are complete. A month named without a year is the latest such month on or before the selected day ("December" asked in September 2026 is December 2025). "Yesterday" is the production day before the selected one and "last week" the week before (URLS); "today" asks you to use the day control, and a bare "Compare last week" is refused as ambiguous. Every percentage is a ratio of sums over the window (METRICS.md).

Optional model: Ask the analyst

Every question is answered offline first. When a provider is available (the hosted demo's own Anthropic key, or a provider chosen in the Agent control with your key on the loopback workbench), an offline answer carries an Ask the analyst button for its question, and the ask bar has one for the typed question. Either opens the analyst's own card in the thread, whose title line reads … · analyst · 2 model calls · 1.8 s (the provider is on its tooltip), and which fills from the stream at the model's own pace: the queries the model planned appear in its query box with their progress as they run, then the interpretation is written paragraph by paragraph with every value substituted from a query row. A paragraph that fails validation is withdrawn. A refused plan shows a one-line note and keeps the offline evidence it was asked from on the card; a stream that fails says so and fabricates nothing. One analyst round per offline answer.

What the analyst adds

The analyst's card carries a short interpretation above the calculated evidence: two to six lines whose every value is substituted from a query row, with citations that identify the result, row and field. The writer sees up to twelve preview rows per result and must disclose missing evidence. Period reports never call a model.

Use Summary + charts for an overview of speed, downtime and waste. With Shift on All, the scope covers all three shifts; an explicitly named shift can narrow a daily question. The overview supplies three supporting charts per shift. Offline mode provides the same calculated overview for free; the analyst adds a model-written interpretation.

Narrative validation withdraws a line that types a digit outside a placeholder, cites an unknown reference or an unavailable value, states a performance finding from incomplete evidence, or asserts an ordering its own figures contradict. It cannot prove that prose is relevant, causal or correct; hypotheses are unverified. A refused plan, a failed provider call, an exhausted budget or an invalid narrative keeps the calculated answer and charts with an explicit notice.

On the loopback workbench, choose a provider under Agent and advanced and paste a key to use your own account: Anthropic (Anthropic in the hosted demo covers both the hosted key and yours) or OpenAI (OpenAI, the optional provider). Live calls incur API charges; offline use remains free.

For “How can we be better?”, first choose an objective: fewer downtime minutes, less recorded waste, or better grade-target attainment within the selected day and scope. Ask for the relevant breakdown and review the events with the crew. A model supplies no missing plant knowledge, proven root cause, or savings estimate. Findings are investigation leads; target gaps are not guaranteed recoverable output. Read asking questions before interpreting broad improvement requests. Model tool selection remains fallible.

Calendar, matrices and reports

Inspect an order

Choose Window Day, then the Orders example. Each row is an order fragment on a knife. Width and length are sheet dimensions in inches; outs multiply cuts to get sheets. Requested and planned totals repeat when an order continues. Compare remaining before/after to see what each fragment produced, and planned overrun to see cutoff rounding. The same order keeps its dimensions and grade across shift boundaries. Completed gross production includes rejects; it is not accepted delivery. See METRICS.md for the simulation choices and time/area audits.

Use the production day and the Window buttons for the production days of the rolling year (Monday to Friday; the page footer names the first and last day). A single shift is the default: both the offline parser and the model tool dispatcher enforce it. Choose All shifts explicitly to compare shifts. Changing day, period, shift or mode resets the visible conversation. Question wording cannot override the selected shift, and under Week, Month or 4-4-5 a daily tool is refused whatever the planner proposes.

The Setup matrix lists setup ID, wet-end ID, grade, timestamps, width, footage, speed/target, stop count and allocated downtime. Stops use half-open intervals: overlap seconds = max(0, min(stop end, setup end) - max(stop start, setup start)). A stop crossing a setup boundary counts in both intersected setups, but its minutes are divided, not duplicated. Invalid setups are excluded with incomplete coverage.

Paper changes equal wet-end runs (owner confirmed). The record generator lays out wet ends of one to four setups (every fifth a single order across the web) with sheet widths of 12–60 inches and cutoffs of 20–92 inches, one order per knife position; a continuing order keeps its number across setups and across a shift boundary. The exact paper-change total therefore varies by shift instead of always equaling half the setup count. The year ending 30 September 2026 holds 31,488 setup records over 262 production days (about 120 per day). Counts include split records when a setup crosses the 15:00 or 23:00 shift boundary (change_code S); nothing crosses into the next production day. This distribution is synthetic and editable; the records carry no order quantity ledger, so requested and remaining sheets read Unavailable in the order matrix. Shift speed attainment is footage / sum(grade target x elapsed minutes) x 100; it is not an unweighted average of percentages. Hover or keyboard-focus a speed-chart value for grade, target and attainment. The percentage is also visible without hovering.

Rank losses shows the ten lowest wet-end speed attainment values and the largest downtime and quality categories. Ties share a rank. Incomplete shifts are withheld from rankings. Period shift rankings use separate speed, maintenance and waste ranks, with 1 = best; there is no fabricated combined score.

Read notes shows the crew label for the shift (the record families carry no free-text shift note) and each individual stop record with its place and reason. Notes are synthetic reported observations, never proof of causes or executable instructions. Reject lines are per knife position and reason (bond, warp, misalignment); each still reconciles sheets to square feet. Three seeded effects are planted in August and September 2026 (RECORDS.md names them); the answer keys are read by the generator only, so an investigation has to find them in the records, for example with the weekly trend, the Pareto and the stops profile.

A week is the Monday–Friday production week containing the selected date; the preceding Monday–Friday week is the comparison. Calendar month follows ordinary month boundaries, so September compares with August. The 4-4-5 view uses 52-week years of four 4-week, 4-week, 5-week quarters; the demo accounting year starts on Monday 3 August 2026 pending owner confirmation, and the prior year's periods are labelled so. The exact windows are displayed. The dataset is a rolling year: windows reaching before its first day or after its last show Unavailable, never zero. Weekend cells are plant-down and hatched; a missing weekday produces partial results and no ranking. The Period report button (shown under Week, Month and 4-4-5) uses free deterministic aggregation, not a live model; the question box beside it stays live and is answered by the period tools, offline or with a model. The report's daily shift matrices, recurring stops and notes remain inspectable.

The period heatmap shades production days by estimated good square feet: gross area minus recorded trim, shear and dry-end rejects. It is an estimate, not a customer-acceptance claim. Hover or focus a cell to see gross and estimated-good output, speed attainment, total/maintenance/operator downtime, waste KPIs, setup and paper-change counts, plus that day's largest recorded downtime contributor. Missing days are hatched and excluded from the color scale.

The card's PDF button exports the investigation on the card without another model call (Markdown is on every card). For an offline answer it is a link to the answer's own address with format=pdf (the address bar shows that address, URLS), so the link can be saved or shared, and it is offered on every such card; for a live answer it is offered on the card of the session's latest answer only, and the server requires its session and run reference plus the same origin/token controls, so a stale or foreign run reference is rejected. Every chart the investigation produced is drawn in the PDF as a native vector drawing from the same tool rows the browser rendered (pdf_charts.py: horizontal bars with the previous window as a thin bar, the weekly trend line with hollow markers for partial weeks, the over/under-run bullet with its shaded band; the calendar heatmap stays a table), followed by its data table. When a speed-opportunity result is in the investigation, an Opportunities (from evidence) section lists the largest gaps with the reject penalty beside each, worded as what the evidence shows, never as a prediction. PDFs are summaries: any shortened evidence tables explicitly state how many rows are shown. Save text remains available.

Email me this report is designed but not built. It will exist only behind the invited login, never on the public demo: a page that takes a typed address and sends mail is a relay waiting to be abused, and "send the report to <address>" stays a refusal golden. Behind Cloudflare Access the app already knows the verified email of the person logged in, so the button sends the PDF to that address only, through a transactional mail provider whose key lives in Secret Manager, rate-limited per user, with one action-audit row per send. Until then there is no send-success simulation.

Period PDF export passes regression checks for week, month and accounting views. Charts are vector drawings built from the evidence rows, never images of the screen; a test builds the PDF for every chart kind (bar, paired bar, line, range) and the opportunities section.

Try the calendar step by step

  1. Select September 14, 2026, shift 2, Window Month, then Period report.
  2. Read the scope sentence and the KPI tile strip (September against August; delta chips colored by change). This report has 22 shift records (one per production day); All has 66. The August comparison is real data.
  3. Hover, Tab to, or click a day. Its detail box gives the actual quantities; a dark day is high output within this report, not necessarily high efficiency.
  4. Choose Window 4-4-5. September 14 belongs to September 1–28. September 29 belongs to September 29–October 26 and is partial because only two days exist; its tiles read "withheld".
  5. To investigate a heatmap date, switch Period back to Day and set the date. Clicking a cell only updates its details.

For formulas and why colors cannot be compared directly across reports, read METRICS. For status messages and recovery, read TROUBLESHOOTING. For a repeatable browser checklist and live-model question rubric, read TESTING.

The Docs link in the header opens /docs, this documentation in the app, for the process definitions, data generation, tools, the loop, calendar math, chart evidence and safety controls. See AGENT_EXECUTION.md for the original technical query walkthrough and DEPLOYMENT.md for the public-release gate.

Rendered from docs/WORKBENCH.md in the repository.