"""Generate docs/CYPHER.md from the queries the tools actually execute. cd backend && python ../scripts/gen_cypher_doc.py Every tool is run once with representative arguments; the Cypher it reports (`out["cypher"]`) is written verbatim, so the catalog cannot drift from the code. """ from __future__ import annotations import json import pathlib import re from bidbrain.graph.client import Graph from bidbrain.ingest.load_graph import PROJECTION_SIG, PROJECTIONS, TENDER_STATUS from bidbrain.tools import TOOLS, call ROOT = pathlib.Path(__file__).resolve().parents[1] SAMPLES = { "resolve_entity": {"text": "phed odisha", "kind": "client"}, "get_entity": {"key": "Ring Road — Uttar Pradesh Pkg-113", "kind": "work"}, "find_portfolio": {"via_work": "Pkg-107", "exclude_categories": ["irrigation"]}, "aggregate": {"work_keys": ["ringroaduttarpradeshpkg113"], "op": "sum"}, "credential_timeline": {"person": "Sunita Joshi"}, "date_span": {"work": "ROB — Jharkhand Pkg-29", "credential_kind": "pmp"}, "receivables": {"client": "Arunodaya Infrastructure", "measure": "outstanding"}, "collection_rate": {"work": "Ring Road — West Bengal Pkg-128"}, "awarded_vs_billed": {"client": "Jal Nigam, Jharkhand"}, "prove_absence": {"evidence": "reference_letter", "client": "Trishakti Power Generation Corporation"}, "qualify_clause": {"kind": "similar_works", "params": {"categories": ["expressways"], "estimated_cost": 2500000000}}, "key_people": {}, "who_knows": {"client": "National Expressway Development Authority"}, "explain_path": {"a": "Sunita Joshi", "a_kind": "person", "b": "Rahul Menon", "b_kind": "person"}, "isolated_entities": {"scope": "corpus"}, "communities": {}, "trace_from_credential": {"person": "Sunita Joshi", "credential": "Six Sigma", "work": "Pkg-107"}, "compliance_check": {"tender": "RFP-132000970"}, "depends_on": {"doc_id": "DOC-CERT-001"}, "fact_history": {"key": "Pkg-113", "kind": "work"}, "change_impact": {"doc_id": "DOC-CC-151"}, "fact_as_of": {"key": "anganwadicentregujaratpkg151", "predicate": "value", "as_of": "2026-10-17"}, "vector_search": {"text": "client praised the safety record", "k": 3}, "fulltext_search": {"text": "Anganwadi"}, "graph_schema": {}, "cypher_readonly": {"query": "MATCH (w:Work) RETURN count(w) AS works"}, "precedents": {"category": "expressways"}, "audit_bid": {}, "blast_radius": {"label": "Person", "key": "rahulmenon", "rerun": False}, "case_get": None, } EXTRA = { "Conflict detection & resolution (bidbrain/conflicts/engine.py)": [ ("Claims about the same fact", "MATCH (c:Claim)-[:ABOUT]->(s) WHERE c.status <> 'superseded' AND c.status <> 'rejected'\n" " AND ($keys IS NULL OR c.subject_key IN $keys)\nRETURN c.id, c.subject_key, c.predicate, c.value, c.source_kind, c.authority, c.issuer, c.observed_day"), ("Record a contradiction", "MATCH (a:Claim {id:$a}), (b:Claim {id:$b}) WHERE NOT (a)-[:CONTRADICTS]-(b)\n" "CREATE (a)-[:CONTRADICTS {rule:'value_mismatch', detected_at:$t}]->(b)"), ("Write a Decision (actor label is Agent or User)", "MERGE (a:Agent {key:$actor}) ON CREATE SET a.name = $actor\n" "CREATE (d:Decision {id:$id, key:$id, conflict_id:$cid, subject_key:$sk, predicate:$pred,\n" " policy:$policy, rationale:$why, at:timestamp(), chosen_value:$val})\n" "CREATE (d)-[:DECIDED_BY]->(a)\nWITH d MATCH (w:Claim {id:$win}) CREATE (d)-[:SELECTS]->(w)\n" "WITH d UNWIND $ids AS cid MATCH (c:Claim {id:cid}) CREATE (d)-[:BASED_ON]->(c)"), ("A newer decision supersedes older ones on the same fact", "MATCH (old:Decision {subject_key:$sk, predicate:$pred}) WHERE old.id <> $d\n" " AND NOT ()-[:SUPERSEDES]->(old)\nWITH old MATCH (d:Decision {id:$d}) CREATE (d)-[:SUPERSEDES]->(old)"), ("Learn a rule from a human decision (procedural memory)", "MERGE (r:PolicyRule {id:$id}) SET r.predicate = $p, r.prefer_kind = $pk, r.over_kind = $ok\n" "WITH r MATCH (d:Decision {id:$d}) MERGE (r)-[:LEARNED_FROM]->(d)"), ], "Accountable actions (bidbrain/company/actions.py)": [ ("Create an action idempotently (MERGE on a hash of kind, case, trigger, target)", "MERGE (a:Action {idem:$idem})\n" "ON CREATE SET a.id = $id, a.kind = $kind, a.status = 'pending', a.title = $title, a.detail = $detail,\n" " a.owner = $owner, a.case_id = $case, a.target = $target, a.payload = $payload,\n" " a.created_at = timestamp(), a.updated_at = timestamp(), a._new = true\n" "WITH a, coalesce(a._new, false) AS created SET a._new = null\nRETURN a.id AS id, a.status AS status, created"), ("Link the action to its case, trigger, evidence and owner", "MATCH (a:Action {id:$a}), (c:Case {id:$c}) MERGE (a)-[:FOR_CASE]->(c)\n" "MATCH (a:Action {id:$a}), (t:Decision {id:$t}) MERGE (a)-[:TRIGGERED_BY]->(t) -- or (t:Finding)\n" "MATCH (a:Action {id:$a}), (d:Document {doc_id:$d}) MERGE (a)-[:CITES]->(d)\n" "MATCH (a:Action {id:$a}), (p:Person {key:$p}) MERGE (a)-[:ASSIGNED_TO]->(p)"), ("Lifecycle transition as compare-and-set (no row back = stale click, HTTP 409)", "MATCH (a:Action {id:$a}) WHERE a.status IN $frm\n" "SET a.status = $to, a.updated_at = timestamp(), a.decided_by = $by RETURN a.id AS id"), ("Audit trail", "MATCH (a:Action {id:$a})\n" "CREATE (a)-[:HAS_EVENT]->(:ActionEvent {at:timestamp(), from:$f, to:$t, by:$by, note:$n})"), ("Executor: internal document request (nothing is emailed)", "MERGE (r:DocumentRequest {id:$id}) ON CREATE SET r.kind = 'reference_letter', r.status = 'requested',\n" " r.requested_at = timestamp(), r.requested_by = $by, r.channel = 'internal'\n" "WITH r MATCH (w:Work {key:$w}) MERGE (r)-[:FOR_WORK]->(w)\n" "WITH r MATCH (a:Action {id:$a}) MERGE (a)-[:CREATED]->(r)"), ], "Company record (bidbrain/company/record.py, graph bidbrain_org)": [ ("Publish a decided fact (idempotent per claim)", "MERGE (f:OrgFact {id:$id})\n" "ON CREATE SET f.subject_label = $sl, f.subject_key = $sk, f.predicate = $p, f.value = $v, f.claim = $claim,\n" " f.document = $doc, f.source_decision = $d, f.policy = $policy, f.source_session = $sess,\n" " f.approved_by = $by, f.published_at = timestamp(), f.status = 'active'"), ("A newer value supersedes, never deletes", "MATCH (old:OrgFact {subject_key:$sk, predicate:$p, status:'active'}) WHERE old.id <> $id\n" "SET old.status = 'superseded', old.superseded_by = $id, old.superseded_at = timestamp()"), ("Apply in a workspace: write the claim, then the conflict engine settles it (policy company_record)", "MATCH (s:Work {key:$sk})\nMERGE (c:Claim {id:$id})\n" "ON CREATE SET c += $props, c.status = 'active', c.company_record = $f, c.recorded_at = timestamp()\n" "MERGE (c)-[:ABOUT]->(s)\nWITH c MATCH (d:Document {doc_id:$doc}) MERGE (c)-[:FROM]->(d)\n" "MERGE (r:CompanyRecordRef {id:$f}) SET r.applied_at = timestamp() -- applied once per workspace"), ], "Tenancy & memory (bidbrain/api/sessions.py)": [ ("Per-visitor graph", "GRAPH.COPY bidbrain_base session_ -- Redis command, one isolated graph per visitor"), ("Conversation turn", "MERGE (c:Conversation {id:'main'}) WITH c OPTIONAL MATCH (c)-[:HAS_TURN]->(t:Turn)\n" "WITH c, count(t) AS n CREATE (c)-[:HAS_TURN]->(:Turn {i:n, role:$r, text:$t, at:timestamp()})"), ], "Analysis projections (bidbrain/ingest/load_graph.py)": [ ("Person <-> client projection for centrality", "MATCH (p:Person)<-[:LED_BY]-(w:Work)-[:FOR_CLIENT]->(c:Client)\n" "WITH p, c, count(w) AS n, sum(w.value) AS v\nCREATE (p)-[:WORKED_FOR {works:n, value:v}]->(c), (c)-[:ENGAGED {works:n, value:v}]->(p)"), *[(f"{name} projection (communities: shared clients >= 1.5x the configuration-model expectation)", "\n".join(l.strip() for l in q.strip().splitlines())) for name, q in PROJECTIONS.items()], ("Projection freshness: OVERLAPS_CLIENTS / WORKED_FOR are rebuilt in a session whenever their inputs change " "(ensure_projections, called by key_people, who_knows, explain_path and communities; the base graph stores " "the signature at load, so a current projection is never rewritten)", PROJECTION_SIG.strip() + "\n-- sha1 of the list != Meta.projection_sig -> project(g): delete and rebuild the projections"), ("Tender status as of the corpus snapshot (tender_status; case_open sets 'open' in a session with a live case)", "\n".join(l.strip() for l in TENDER_STATUS.strip().splitlines())), ("Workbook provenance (found by isolated_entities scope='corpus')", "UNWIND $rows AS r MATCH (n:Asset {key:r.key}), (d:Document {doc_id:r.doc})\n" "CREATE (n)-[:EVIDENCED_BY {kind:'asset_register'}]->(d) -- also TBLine, BOQItem, Measurement"), ("When each compliance claim was made", "UNWIND $rows AS r MATCH (t:Tender {key:r.tender})\n" "SET t.bid_date = r.bid_date, t.bid_date_basis = r.basis, t.bid_value = r.bid_value, t.matrix_doc = r.doc"), ("Indexes", "CREATE INDEX FOR (n:Work) ON (n.key) -- range index on every entity key\n" "CALL db.idx.fulltext.createNodeIndex('Chunk', 'ft')\n" "CREATE VECTOR INDEX FOR (c:Chunk) ON (c.embedding) OPTIONS {dimension:384, similarityFunction:'cosine'}"), ], } def norm(q: str) -> str: lines = [l.rstrip() for l in q.strip("\n").splitlines()] ind = min((len(l) - len(l.lstrip()) for l in lines[1:] if l.strip()), default=0) return "\n".join([lines[0].strip()] + [l[ind:] for l in lines[1:]]) def main(): g = Graph() case_id = call(g.copy_to("doc_tmp"), "case_open", {"title": "doc"}) # ensure case tools work on a copy Graph("doc_tmp").delete() out = ["# Cypher & graph algorithms BidBrain depends on", "", "Generated by `scripts/gen_cypher_doc.py`: every tool was executed once against `bidbrain_base` and the", "queries it actually ran are listed verbatim. FalkorDB dialect notes: no `EXISTS {}` subqueries (pattern", "predicates instead), `shortestPath` only in WITH/RETURN, dates compared as `*_day` integers.", ""] algos = set() for name in sorted(TOOLS): t = TOOLS[name] args = SAMPLES.get(name) if args is None: continue res = call(g, name, args) qs = [] for q in res.get("cypher", []): q = norm(q) if q not in qs: qs.append(q) algos |= set(re.findall(r"algo\.\w+|db\.idx\.\w+\.\w+", " ".join(qs))) out += [f"## `{name}`", "", " ".join(t.description.split()), "", f"Example call: `{json.dumps(args, ensure_ascii=False)}` — {res.get('ms', '?')} ms", ""] for q in qs: out += ["```cypher", q, "```", ""] for section, items in EXTRA.items(): out += [f"## {section}", ""] for title, q in items: out += [f"**{title}**", "", "```cypher", q, "```", ""] out[5:5] = ["", "**Built-in procedures used:** " + ", ".join(f"`{a}`" for a in sorted(algos)) + ", `GRAPH.COPY`, `GRAPH.RO_QUERY`", ""] (ROOT / "docs" / "CYPHER.md").write_text("\n".join(out)) print("wrote docs/CYPHER.md", len(out), "lines; procedures:", sorted(algos)) if __name__ == "__main__": main()