"""Builds predictions.json: the answer for the 849 unlabelled jobs (the ones Metcor marks), in the same shape as labelled_examples.json, from the final run of the frozen system (analysis/quote_run_sonnet_final.json). Code only, no model calls. Prices come from the rate card; job_type uses the vocabulary of labelled_examples.json. Extra fields: confidence (green/yellow/red), estimator_checks, possible_duplicate_of.""" import json, os from collections import defaultdict ROOT = os.path.dirname(os.path.abspath(__file__)) P = lambda *a: os.path.join(ROOT, *a) J = {j["item"]: j for j in json.load(open(P("jobs.json")))["jobs"]} LABELLED = {e["item"] for e in json.load(open(P("labelled_examples.json")))["jobs"]} RC = json.load(open(P("rate_card.json"))); RC = RC["lines"] if isinstance(RC, dict) and "lines" in RC else RC R = {l["code"]: l for l in RC} RUN = json.load(open(P("analysis", "quote_run_sonnet_final.json")))["records"] # job_type in the labelled vocabulary (reproduces all 65 manhole labels of labelled_examples.json from their quote lines) SHORT = {c: R[c]["description"].replace("Cover & frame, solid top, ", "") for c in R if c.startswith("CVR")} SHORT.update({"CVR-REC": "recessed (paving infill)", "CVR-PLATE": "chequer plate", "CVR-OTHER": "other / non-standard size"}) ORDER = ["CVR-600-H", "CVR-PLATE", "CVR-600-L", "CVR-600x450-L", "CVR-OTHER", "CVR-600x450-H", "CVR-750-H", "CVR-REC", "CVR-675-H", "CVR-450-L", "CVR-450-H"] NON = {"channel": "not manhole: channel drain (ACO-type) repair or replacement", "gully": "not manhole: gully grate / gully repair"} def job_type(is_mh, jt, codes): if not is_mh: return NON.get(jt, "not manhole: other civils or drainage work") cov = [c for c in ORDER if c in codes] if not cov: return "manhole: rebed / reset existing cover and frame (no new cover)" s = "manhole: cover and frame replacement, " + " / ".join(SHORT[c] for c in cov) if any(c.startswith("CHN") for c in codes): s += " (plus channel drain work)" elif any(c.startswith("GUL") for c in codes): s += " (plus gully work)" return s # possible duplicates: an identical form (issue type + all fields) appears in another job key = lambda f: json.dumps([f.get("issue_type"), f.get("fields")], sort_keys=True) where = defaultdict(set) for it, j in J.items(): for f in j["remedial_forms"]: if f.get("fields"): where[key(f)].add(it) dup = defaultdict(set) for s in where.values(): for it in s: dup[it] |= s - {it} out = [] for it in sorted(RUN): a = RUN[it]["answer"]; lines = [] for l in a["lines"]: rc = R[l["code"]]; q = float(l["qty"]) lines.append({"code": l["code"], "description": rc["description"], "unit": rc.get("unit"), "qty": q, "unit_price": rc["unit_price"], "total": round(q * rc["unit_price"], 2), "rule": l.get("rule")}) rec = {"item": it, "is_manhole_job": bool(a.get("is_manhole")), "job_type": job_type(bool(a.get("is_manhole")), a.get("job_type"), [l["code"] for l in lines]), "quote_lines": lines, "quote_total": round(sum(l["total"] for l in lines), 2), "confidence": a.get("colour"), "estimator_checks": a.get("check", [])} if dup[it]: rec["possible_duplicate_of"] = [{"item": d, "labelled": d in LABELLED} for d in sorted(dup[it])] out.append(rec) assert len(out) == 849 and not (set(RUN) & LABELLED) about = ("Predictions for the 849 jobs without a label. System: claude-sonnet-5-5 reads the engineer forms (text only) and writes the quote " "with the quoting rules, the rate card and three worked examples in the prompt (frozen in analysis/frozen_v5). Code checks the codes " "against the rate card and adds up the totals. confidence: green = the rules give one answer from what the form states; yellow = " "a default or estimate was used; red = conflicting or missing information. possible_duplicate_of: another job has an identical form, " "so the same work may be quoted twice.") json.dump({"about": about, "jobs": out}, open(P("predictions.json"), "w"), indent=1) print(f"Saved predictions.json: {len(out)} jobs, {sum('possible_duplicate_of' in r for r in out)} flagged as possible duplicates")