Commit graph

206 commits

Author SHA1 Message Date
DEls
dc25fb13cd feat(ask): long-term memory
Adds src/lib/agent/memory.ts (remember/forget/listMemories, dedupe by
normalised text, formatMemoriesForPrompt) and wires it in: a
"## What you remember" system-prompt section, the remember/forget/
list_memories tools (src/lib/agent/tools-actions.ts, a per-request
factory since these need the actor unlike the static read-only tool
set), a REST view for the UI (/api/ask/memories), and a Memory drawer
in chat-view.tsx (Mine / Shared with everyone, add, delete).

Adjusted src/lib/agent/read-only.test.ts: its blanket "no mutation
calls under src/lib/agent" contract now allowlists the specific write
seams the plan designates (memory.ts here; tools-actions.ts, gateway.ts
and images.ts to follow) instead of forbidding every file, since
memories/requests are specified to bypass the gateway and write
directly.

Live browser check skipped: ask_log doesn't exist yet in the target
PocketBase (pending the user's --apply of Task 1's schema). Covered by
mocked unit tests instead.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-26 18:13:39 +02:00
DEls
7c5df11b84 feat(ask): per-user persistent threads
Adds the "Who's using Ask?" picker (GET /api/ask/users), actor
resolution via x-ask-user (src/lib/agent/context.ts), and thread
persistence (src/lib/chat/threads.ts, /api/chat/threads routes).
/api/chat now accepts { threadId?, message, display?, images? } and
persists each turn as its own append-only ask_messages row; the old
{ history } body still works for one release. chat-view.tsx gained the
picker, a thread sidebar (new/select/rename/archive), and ?thread=<id>
in the URL.

Live browser check skipped: ask_threads/ask_messages/ask_log don't
exist yet in the target PocketBase (Task 1's schema is dry-run only,
pending the user's --apply). Covered by mocked unit tests instead.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-26 18:05:01 +02:00
DEls
af71b8534b feat(suppliers): green-row tint, period filter, and KPI tiles
Green when a supplier's ordered > 0 and outstanding === 0 -- the whole
row, not just the number, so a fully-paid-up supplier stands out at a
glance.

Period filter next to the search:
  All time, This week, This month,
  Week 1-4 (of current calendar month, last absorbs any remainder),
  Month 1 (current) / Month 2 (last) / Month 3 (month before),
  Custom range with two date inputs.

Window anchors on delivery_notes.date_received -- liability accrues when
the goods arrive, not when the PO is uploaded ('we only pay what we
received this month'). computeSupplierBalances gains lastDeliveryISO per
row; a pure helper in supplier-period.ts resolves windows in UTC so the
boundary does not drift with the server's timezone.

Four KPI tiles at the top of the page:
  - Total ordered / delivered / outstanding (cumulative, always).
  - Payable this period (delivery value inside the active window).
    Subtitle is honest: 'subtract anything already paid' -- we do not
    track supplier payments, so this is the upper bound.

ui/select.tsx exports a SelectLabel so the period dropdown can group
options.
2026-09-26 17:38:39 +02:00
DEls
534f3b6a85 feat(ask): slash shortcuts for base reports
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-26 09:56:21 +02:00
DEls
774eaca13f fix(ask): findings carry their own cross-site totals
A live /findings run showed the model adding per-site and per-item
counts itself. Rules that span sites now return one finding with the
total in the title and the per-site split in the detail, and the prompt
says not to add counts across findings.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-26 09:52:32 +02:00
DEls
89c73233cb feat(ask): business glossary and report rules in the prompt
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-26 09:44:37 +02:00
DEls
d81a089a86 feat(ask): rain_outlook tool
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-26 09:44:01 +02:00
DEls
648f30c36d feat(ask): plant_equipment_summary tool
Hours and value match the Equipment page (guarded hoursBetweenReadings,
isPlant bucket).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-26 09:41:43 +02:00
DEls
e5789f3371 feat(ask): delivery_note_queue tool
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-26 09:39:38 +02:00
DEls
4163970cc8 refactor(dn-inbox): move board columns and stage mapping to src/lib/dn-pipeline/board
Moved unchanged; the inbox renders identically.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-26 09:38:43 +02:00
DEls
10eba6f270 feat(ask): supplier_balances tool
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-26 09:37:27 +02:00
DEls
771f13967f refactor(suppliers): move supplier balance roll-up to src/lib/metrics
Moved unchanged; the Suppliers page renders identically.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 22:37:40 +02:00
DEls
30b449009b feat(ask): attendance_today tool
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 22:35:48 +02:00
DEls
db9e6ddfb2 refactor(on-site): move attendance grouping to src/lib/attendance
Moved unchanged; the On-site page renders identically.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 22:34:06 +02:00
DEls
e088f52cb2 feat(ask): improvement report
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 22:32:08 +02:00
DEls
6aa3837038 feat(ask): problem report
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 22:29:37 +02:00
DEls
7e506b46e1 feat(ask): site and portfolio report tools with charts
list_sites now reads computeSiteSummaries (moved verbatim, output pinned
by snapshot). The read-only guard also scans src/lib/reports.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 22:25:33 +02:00
DEls
b8e8054725 feat(ask): render report charts inline in answers
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 22:20:01 +02:00
DEls
f0736e0e62 feat(ask): chart spec and extraction from tool results
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 22:12:04 +02:00
DEls
3551e09170 fix(chat): compute today's date per request, not at boot
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 22:11:25 +02:00
DEls
e83a0d302c fix: overview counts, wrong-unit fuel limits, meter guard, en-ZA numbers, vehicle merge, docs
- Overview/Plant tiles and pages share one vehicle bucket rule (blank unit_type = fleet)
- Plant landing equipment figures were always 0; hour diffs now reject 0 baselines and >24h/day jumps
- km/L limits on plant (and L/h on vehicles) report unit-mismatch instead of false alerts
- formatPercent/formatDecimal replace display toFixed (en-ZA)
- POST /api/vehicles/[id]/merge + Merge duplicate dialog (dry run first)
- Attention links go to equipment vs vehicles by unit type; PO upload view link fixed
- Conditional hook fixed in DN document view; ESLint flat config (next lint removed in 16)
- Docs: current modules/routes/env vars; login + Ask reports plans

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 22:04:08 +02:00
DEls
a59a5a86d2 Merge branch 'codex/clock-daily-shifts' 2026-09-25 07:32:46 +02:00
DEls
5cdfaa3128 feat(clock): review missed scan-outs without changing phone flow 2026-09-25 07:27:12 +02:00
DEls
176b6180bf fix(clock): keep old unmatched scans out of today board 2026-09-25 07:06:24 +02:00
DEls
29edf2e5aa Revert "feat(clock): add named kiosk and supervisor sessions"
This reverts commit b163ce9f94.
2026-09-25 03:22:43 +02:00
DEls
b163ce9f94 feat(clock): add named kiosk and supervisor sessions 2026-09-25 03:19:25 +02:00
DEls
df201b2a1f feat(receipt-correction): add support for receipt correction form and enhance item editing
Introduced a new secret key for receipt correction in the environment configuration. Enhanced the API for handling item edits in delivery notes, allowing for more granular updates and preserving existing manual overrides. Updated the document detail view to display additional supplier and customer information, and integrated a toggle for skipping customer identity checks. Improved validation for item entries and added tests to ensure robust handling of various edge cases in item editing.
2026-09-25 02:59:16 +02:00
DEls
488eb2af99 feat(clock): define local workday and shift records 2026-09-25 02:03:46 +02:00
DEls
47ec4f1d1b feat(purchase-orders): enhance PO handling with improved validation and item management
Updated the purchase order processing to include enhanced validation for item edits, ensuring that all required fields are checked and properly formatted. Introduced new functions for computing item amounts, taking into account discounts and VAT. Refactored the PO detail view to streamline item management, including a new ItemsCard component for better organization of line items. Additionally, improved the handling of PO number sanitization to strip leading and trailing characters, enhancing matching accuracy. This update aims to improve the overall robustness and user experience of the purchase order system.
2026-09-23 23:37:37 +02:00
DEls
844ba8fe12 feat(workforce): enhance on-site dashboard with detailed employee status
Updated the on-site dashboard to provide a clearer overview of employee statuses. Renamed sections for better clarity, changing "On site now" to "On site today" and updated descriptions to reflect current employee activity. Refactored the data handling to categorize employees into three distinct groups: those currently on site, those clocked out today, and those who have not arrived. This change improves the visibility of employee attendance and enhances the overall user experience on the dashboard.
2026-09-23 00:25:33 +02:00
DEIs
87f75dd98f feat(plant): fleet map page with GPS-denormalised vehicles, clustering, and tile switcher
Adds /dashboard/plant/map: a Leaflet view of every vehicle scanned from
the MEGA Clock kiosk, plotted at its last known position.

Schema
- vehicles.last_latitude / last_longitude / last_scanned_at /
  last_location_accuracy_m denormalised off fuel_logs so the map page
  renders every vehicle in a single query. Same best-effort pattern as
  attention_dismissed_at; updated by /api/clock/vehicle-scan after every
  successful scan. PB init defaults new number cols to 0 (not null) —
  filter uses 'last_latitude > 0 || last_latitude < 0' to mean 'real fix'.
- Indexed via idx_vehicles_last_scanned.

Map UI
- Custom SVG markers per unit_type: car silhouette for fuel, tractor
  silhouette for plant, 24x24, drop-shadowed.
- Clustering via react-leaflet-cluster over Leaflet.markercluster.
  Cluster icons colour-ramp with size (green->sky->amber->rose) and
  scale 32->54px.
- Three-button base-layer switcher: Street (OSM), Dark (ESRI Dark
  Gray Canvas — CartoDB watermarks tiles without a key), Satellite
  (ESRI World Imagery). Last choice persists in localStorage.
- Site pins: small purple map-marker divIcon, click flies the map to
  zoom level 15.
- Empty-state is a floating chip on the map rather than a full-page
  card so the map is always visible.

APIs
- /api/clock/vehicle-scan: stores the GPS fix on the fuel_logs row
  AND denormalises onto the vehicle when present; missing-coord
  scan leaves last_* untouched.

Sidebar: new Map entry under Plant group.
2026-09-22 21:39:25 +02:00
DEls
a589b14ac9 feat(clock): enhance vehicle scan API with GPS support
Updated the vehicle scan API to include optional GPS coordinates (latitude, longitude) and location accuracy. The API now validates these fields, ensuring both latitude and longitude are provided together and within valid ranges. Enhanced error handling for invalid inputs and updated the API reference documentation accordingly. Additionally, modified the FleetScanner component to capture GPS data and reflect its status in the UI.
2026-09-22 19:59:44 +02:00
DEls
f1dbc96435 feat(dn): grab and move the order the same way as the slip
The order was a PDF in a frame, which meant the browser's viewer, not
ours: no drag, no shared zoom, controls in a different place. Two halves
of one comparison behaving differently.

Its pages are now rendered to PNGs through the same poppler path the
delivery note's pages use, so both sides go through one viewer and get
the same stepping, zoom, drag-to-pan and remembered width. Switching
sides resets to the first page at 100%, since the position held on one
document means nothing on the other.

The PDF route stays for "Full size". Its file lookup moved to lib and is
now shared with the page renderer, so the two cannot drift on which field
holds the file. Page count comes from pdfinfo rather than rendering, as
the viewer needs the count before it draws anything.

Verified against MEGCPO9239: a two-page order, both pages 200 image/png,
page three 404, no token 401.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-21 02:05:31 +02:00
DEls
8027b5a263 feat(dn): flip between the slip and the order it is checked against
Verifying a delivery means comparing two documents, and the order was not
one of them: reaching it meant leaving the note, finding the purchase
order and coming back having lost the comparison.

The viewer gains a toggle. The order is a PDF, so the browser's own
viewer handles it — it already paginates and zooms better than wrapping
it in ours would. The toggle only appears when an order is resolved and
carries a file; an unmatched note has nothing on the other side of it.

The document is proxied behind a signed token rather than linked from
PocketBase, which would put the admin JWT in the browser. The token is
namespaced per document, so one minted for a delivery-note page cannot be
replayed against an order, nor one order's against another's — verified
against a live file: valid token 200, absent 401, replayed 401.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-21 01:44:02 +02:00
DEls
db15e27bb1 fix(dn): let a hand-linked line settle the question it answers
Linking every line left the document blocked, and reloading asked the
same questions again.

resolve-item stores the link but not the checks, so the stale
product_matched failure survived with its original decision list — which
is what the screen re-read on reload. It also judged readiness against
those same stale checks, so no number of links could ever make a document
ready.

The runner owns the checks, but it could not see the links either:
resolveNoteLines matches on wording and alias alone, so a line linked
without saving an alias was matched away again and the answered question
reopened. Manual links now reach the matcher through the check context
and count as matched, which is what makes product_matched pass.

Re-check is therefore a real re-check rather than a page reload, and runs
once when the last line is answered instead of once per line.

Verified on a 16-line delivery note with three lines linked by hand: it
now reaches ready_to_post with 16/16 linked and no failing checks.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-21 00:21:12 +02:00
DEls
b8dcf20146 fix(dn): settle one product line at a time, and keep the link
Answering the second product question returned batch_locked. Resolving a
line fired a full re-run, which holds the batch lease for some twenty
seconds, so the next answer arrived while the lease was still taken. The
re-run achieved nothing either: resolve-item already links the line,
re-evaluates the document and writes its stage.

It was also quietly destructive. A run rebuilds `items` from the page
reads, and only a choice saved as an alias can be re-derived from the
descriptions — so a link made without ticking "remember this alias" was
discarded by the next run, including the tick a minute later. Manual
links now persist in manual_overrides alongside the order number, and are
honoured only while the linked item still belongs to the resolved order,
so correcting the order cannot leave lines pointing at the previous one.

Each line now has its own Save rather than committing the moment the
dropdown changes, and an answered line leaves the list instead of
reloading onto the same screen with the same questions.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-21 00:15:13 +02:00
DEls
14be7e7e21 fix(dn): keep an operator's correction through the next run
Picking the right order did nothing. The route stored the corrected
number, the re-run rebuilt the candidate from its page reads, and
po_number_raw went back to the original misread — so the operator
answered the same question again, having been told it was saved.

Nothing in dn_batch_notes distinguished a value a person chose from one
the model read, so the runner could not tell a correction from stale OCR
and overwrote both. A manual_overrides field now records the decision,
and the runner applies it before resolving rather than after, so the
corrected number is what the PO is matched on.

Only fields a run rebuilds are recorded this way. Quantity, unit and the
masses survive on their own; page ranges are the assembler's answer and
are corrected through the assembly route, which keeps the whole batch
consistent. An override must carry a value — clearing a box is not a
decision that a document has no number.

Verified end to end on a slip whose order number was read with a zero for
the letter O: the correction survived the run, resolved the order, moved
the match from fuzzy to exact, and left the document ready to post.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-20 23:55:56 +02:00
DEls
74a2a9de11 feat(dn): record quantity findings instead of refusing the delivery
A delivery note is a record of material that physically arrived. Refusing
to record it because its quantity cannot be reconciled with the order
leaves the yard's paperwork outside the system, so quantity findings are
now reported on the purchase order rather than gating ingestion.

qty_plausible and delivered_totals_available become informational: still
computed, still stored, never blocking. Only assembly and identity —
po_resolved, product_matched, units_convertible, not_duplicate — hold a
document back.

qty_plausible also stops guessing. Where no common unit exists it fell
back to the raw slip figure, comparing 30,260 kg against an order of 30
and reporting a 1,009x overage with "check the slip for a misread
decimal". The slip was right; the order had no unit. 61 of one site's 148
PO lines carry no unit, and units_convertible deliberately tolerates
that, so the two checks disagreed about the same gap. It now declines to
compare rather than produce a number that blames the wrong document.

The delivered-totals query OR-ed every relevant PO item id into one
filter sent as a GET parameter. Measured against production, 80 clauses
(3,436 chars) succeed and 85 (3,651) return a bare 400; that site's 148
lines put it permanently over, so no candidate could ever reach
ready_to_post. Chunked at 50.

Batch stage no longer counts candidates needing review. Grouping is a
batch question and matching is a document question, and folding them
together parked batches under "Check page grouping" with no grouping left
to check — holding settled notes behind one unresolved order.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-20 23:50:10 +02:00
DEls
da756f5224 feat(worker-qr): real PDF + CSV batch export
Replace the off-screen CSS grid + window.print() in the worker QR
batch dialog with two on-click exports:

  - Download PDF — A4 portrait, 2x5 grid of CR80 (85x54mm) cards per
    page, brand orange header, generated with pdf-lib (dynamically
    imported, same pattern as src/lib/photo-pdf.ts).
  - Download CSV — UTF-8 BOM, columns employee_no, name, position,
    worker_type, project_code, qr_payload via Papa.unparse.

The single-card print on the worker detail page still uses
window.print() — one card has no pagination problem, only the batch
view did.

Spec §4.4 updated to match the new output mechanism and explain the
mobile failure mode that prompted the change.

New tests (8) cover the CSV builder (header order, qr_payload ==
employee_no per spec §4.1, RFC 4180 quoting, empty input) and the PDF
builder (empty input throws, valid %PDF output, 11 workers paginate
onto 2 pages, fake PNG injection so layout is testable without qrcode).
2026-09-20 20:43:10 +02:00
DEls
3a0aedec0d Merge branch 'codex/hybrid-dn-ingestion'
Staged, photo-first delivery note ingestion: bulk JPEG/PDF intake, one
persisted vision read per page, deterministic assembly into candidate
documents, site-scoped supplier/PO resolution, split review, a
supplier-grouped work queue with manual line linking, and fenced
idempotent posting.

The new pipeline stays dormant behind DN_STAGED_INGESTION_ENABLED; the
tick route keeps running the legacy runner until the flag is set to
exactly "true". The PocketBase schema is already applied in production
(verified by a read-only dry run: every collection, field and index
present, no financial rows touched).

The local draft of the multi-file upload route is superseded by the
branch version, which parameterises its PocketBase filters, checks
duplicates before creating a batch, and rolls back a partially written
batch. That draft is kept in stash@{0}.

Verified on the merged tree: tsc --noEmit clean, 1200 tests across 106
files passed, next build passed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-19 21:56:23 +02:00
DEls
bb70f3339a fix(dn): scope staged ledger totals to PO items 2026-09-19 19:26:20 +02:00
DEls
199dfba195 fix(dn): align staged readiness with ledger totals 2026-09-19 19:23:22 +02:00
DEls
bbd7a7bb23 fix(dn): harden staged assembly and page identity 2026-09-19 19:17:46 +02:00
DEls
11bc1a4b6b fix: verify audit collisions and purchase orders 2026-09-19 18:58:07 +02:00
DEls
2914eefacf fix: harden delivery note posting review gates 2026-09-19 18:53:25 +02:00
DEls
81a4427fea test: cover correction transaction fencing 2026-09-19 18:38:02 +02:00
DEls
fe59c618e0 fix: fail closed on persisted correction checks 2026-09-19 18:35:55 +02:00
DEls
8f2bb1fc13 fix: harden delivery note correction gates 2026-09-19 18:31:48 +02:00
DEls
5c57dcbf5c fix(dn): harden split review and page proxy 2026-09-19 17:52:44 +02:00
DEls
b5ba219d10 fix(dn): transactionally fence protected writes 2026-09-19 17:33:38 +02:00