From 7d02ef1e3aae1869e65cffc1855743eec00a589b Mon Sep 17 00:00:00 2001 From: DEls Date: Sat, 29 Aug 2026 20:25:39 +0200 Subject: [PATCH] feat(agent): add read-only PocketBase loaders The only module in the agent layer that touches the network. Kept separate from formatting so tool output is unit-testable offline. Co-Authored-By: Claude Opus 5 --- src/lib/agent/load.ts | 92 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 92 insertions(+) create mode 100644 src/lib/agent/load.ts diff --git a/src/lib/agent/load.ts b/src/lib/agent/load.ts new file mode 100644 index 0000000..d1133ed --- /dev/null +++ b/src/lib/agent/load.ts @@ -0,0 +1,92 @@ +/** + * PocketBase reads for the agent tool layer. + * + * The ONLY module here that touches the network. Every function is a + * read — no create/update/delete, enforced by read-only.test.ts. + * + * Deliberately returns raw-ish records; shaping lives in ./format so it + * can be unit-tested without a PocketBase instance. + */ +import { pbAdmin } from "@/lib/pb"; +import type { SiteRef } from "./resolve"; + +export async function loadSiteRefs(): Promise { + const pb = await pbAdmin(); + const sites = await pb.collection("sites").getFullList({ + fields: "id,name,project_code", + sort: "name", + }); + return sites.map((s) => ({ + id: s.id, + name: s.name ?? "", + projectCode: s.project_code ?? "", + })); +} + +/** Everything the site financial roll-up needs, for one site. */ +export async function loadSiteBundle(siteId: string) { + const pb = await pbAdmin(); + const [site, fixedCosts, timesheets, employees, purchaseOrders, fuelLogs, income] = + await Promise.all([ + pb.collection("sites").getOne(siteId), + pb.collection("fixed_costs").getFullList({ filter: `site='${siteId}'` }), + pb.collection("timesheets").getFullList(), + pb.collection("employees").getFullList({ + fields: "id,rate_per_hour,site,name", + }), + pb.collection("purchase_orders").getFullList({ filter: `site='${siteId}'` }), + pb.collection("fuel_logs").getFullList({ filter: `site='${siteId}'` }), + pb.collection("income_claims").getFullList({ filter: `site='${siteId}'` }), + ]); + return { site, fixedCosts, timesheets, employees, purchaseOrders, fuelLogs, income }; +} + +/** Every site plus the collections needed to roll all of them up at once. */ +export async function loadAllSitesBundle() { + const pb = await pbAdmin(); + const [sites, fixedCosts, timesheets, employees, purchaseOrders, fuelLogs, income] = + await Promise.all([ + pb.collection("sites").getFullList({ sort: "name" }), + pb.collection("fixed_costs").getFullList(), + pb.collection("timesheets").getFullList(), + pb.collection("employees").getFullList({ + fields: "id,rate_per_hour,site,name", + }), + pb.collection("purchase_orders").getFullList({ + fields: "id,site,total_value,is_verified", + }), + pb.collection("fuel_logs").getFullList({ + fields: "id,site,litres,date_logged", + }), + pb.collection("income_claims").getFullList({ + fields: "id,site,paid_on,period_end", + }), + ]); + return { sites, fixedCosts, timesheets, employees, purchaseOrders, fuelLogs, income }; +} + +/** Vehicles + fuel logs with vehicle expanded, for the attention roll-up. */ +export async function loadFleetBundle() { + const pb = await pbAdmin(); + const [vehicles, fuelLogs] = await Promise.all([ + pb.collection("vehicles").getFullList({ sort: "description" }), + pb.collection("fuel_logs").getFullList({ expand: "vehicle" }), + ]); + return { vehicles, fuelLogs }; +} + +/** Counts of records awaiting human verification. */ +export async function loadPendingVerification() { + const pb = await pbAdmin(); + const [pos, dns] = await Promise.all([ + pb.collection("purchase_orders").getFullList({ + filter: "is_verified=false", + fields: "id,purchase_order_number,supplier,total_value,site", + }), + pb.collection("delivery_notes").getFullList({ + filter: "is_verified=false", + fields: "id,delivery_note_number,processing_status,date_received,site", + }), + ]); + return { pos, dns }; +} \ No newline at end of file