"""Deterministic scripted BUILD/FIX JSON for the MockBackend. Each petition maps to a category by keyword; each category emits a complete BUILD JSON string (parsed by mind/validate.py exactly like real backend output), with names and blurbs varied by a crc32 seed of the petition — same petition in, same permit out, so demos replay exactly. The category library exercises every building kind. fix_script reads the candidate ids and report numbers back out of the rendered fix prompt (the "- F1: desc (predicted index 41)" lines from mind/prompts.py — keep the two in lockstep) and picks the best predicted index, so mock fixes cite real numbers too. """ from __future__ import annotations import json import random import re import zlib NAME_MAX = 24 BLURB_MAX = 140 def _seed_for(petition: str) -> int: return zlib.crc32(" ".join(petition.lower().split()).encode("utf-8")) & 0x7FFFFFFF def _entry(kind: str, name: str, near=None, floors=None, hue=None) -> dict: return {"kind": kind, "name": name[:NAME_MAX], "near": near, "floors": floors, "hue": hue} def _plan(blurb: str, buildings: list[dict]) -> dict: return { "intent": "build", "blurb": blurb[:BLURB_MAX], "buildings": buildings[:3], "decline_reason": None, } _STOP_WORDS = { "a", "an", "the", "please", "build", "make", "made", "add", "want", "wants", "need", "needs", "near", "for", "with", "of", "in", "on", "to", "and", "my", "me", "us", "we", "i", "new", "some", "little", "big", } def _petition_name(petition: str, suffix: str = "House") -> str: words = [w for w in re.findall(r"[A-Za-z]+", petition) if w.lower() not in _STOP_WORDS] base = " ".join(w.capitalize() for w in words[:2]) name = f"{base} {suffix}".strip() return (name[:NAME_MAX].rstrip() or f"Petition {suffix}")[:NAME_MAX] _CAFES = ("Cafe Luna", "The Brass Kettle", "Marigold Coffee", "The Daily Grind", "Cafe Fig") _HOUSES = ("Wren Cottage", "Maple End", "The Little Blue", "Juniper House", "Quince Cottage") _APTS = ("Linden Court", "The Beacon Flats", "Harbor House", "The Gables", "Rookery Flats") _TOWERS = ("Meridian Tower", "The Spindle", "Apex Tower", "Cobalt Point", "The Needle") _PARKS = ("Dapple Green", "Whistle Park", "Petal Commons", "Old Elm Green") _STADIUMS = ("The Civic Bowl", "Tin Cup Stadium", "The Roaring Yard") _CHURCHES = ("The Quiet Spire", "St. Alder's", "Lantern Chapel") _SCHOOLS = ("Alder Primary", "Hilltop School", "The Brightwork School") _HOSPITALS = ("Riverside General", "St. Junia Hospital", "Bluebell General") _FACTORIES = ("The Tin Works", "Cobalt Works", "The Bottling Hall") _MARKETS = ("Penny Market", "The Mercantile", "Six Crows Market") _BANKS = ("The Counting House", "First Acorn Bank", "The Strongbox") _SHOPS = ("The Odd Shelf", "Tortoise & Finch", "The Blue Awning") _NEARS = ("downtown", "the park", "Main St", "1st Ave") def _district(petition: str, rng: random.Random) -> dict: flats, cafe, shop = rng.choice(_APTS), rng.choice(_CAFES), rng.choice(_SHOPS) buildings = [ _entry("apartments", flats, "downtown", rng.randint(4, 7), rng.choice((25, 35, 200))), _entry("cafe", cafe, "downtown", 1, rng.choice((30, 40, 50))), ] if rng.random() < 0.7: buildings.append(_entry("shop", shop, "downtown", 1, rng.choice((10, 180, 320)))) blurb = rng.choice(( f"Zoning approved: a new block downtown — {flats} rising with {cafe} on the corner.", f"A whole block takes shape: {flats}, {cafe}, and room for more.", )) return _plan(blurb, buildings) def _river(petition: str, rng: random.Random) -> dict: park, cafe = rng.choice(_PARKS), rng.choice(("The Ferry Light", "Pier Nine Cafe", "The Eddy")) blurb = rng.choice(( f"The waterfront gets its due: {park} on the bank, {cafe} watching the water.", f"Permits issued by the river — {park}, and {cafe} for the crossing crowd.", )) return _plan(blurb, [ _entry("park", park, "the river"), _entry("cafe", cafe, "the river", 1, rng.choice((35, 45))), ]) def _cafe(petition: str, rng: random.Random) -> dict: name = rng.choice(_CAFES) near = rng.choice(_NEARS) blurb = rng.choice(( f"{name} opens near {near} — pastries at dawn, lamplight at dusk.", f"Permit granted: {name}, sidewalk tables facing the street.", )) return _plan(blurb, [_entry("cafe", name, near, 1, rng.choice((30, 40, 50)))]) def _house(petition: str, rng: random.Random) -> dict: name = rng.choice(_HOUSES) blurb = rng.choice(( f"{name} gets its lot — a porch, a chimney, a light in the window.", f"A home is granted: {name}, keys cut while the paint dries.", )) return _plan(blurb, [_entry("house", name, rng.choice(_NEARS), rng.randint(1, 2), rng.choice((20, 35, 200, 350)))]) def _apartments(petition: str, rng: random.Random) -> dict: name = rng.choice(_APTS) blurb = rng.choice(( f"{name} approved — {rng.randint(4, 7)} floors of new neighbors.", f"Housing first: {name} will fill with families by the weekend.", )) return _plan(blurb, [_entry("apartments", name, rng.choice(_NEARS), rng.randint(4, 7), rng.choice((25, 200, 160)))]) def _tower(petition: str, rng: random.Random) -> dict: name = rng.choice(_TOWERS) blurb = rng.choice(( f"{name} cleared for the skyline — cranes by morning.", f"Downtown grows up: {name}, {rng.randint(8, 16)} floors against the horizon.", )) return _plan(blurb, [_entry("tower", name, "downtown", rng.randint(8, 16), rng.choice((210, 220, 35)))]) def _park(petition: str, rng: random.Random) -> dict: name = rng.choice(_PARKS) blurb = rng.choice(( f"Green space wins: {name}, benches and old trees included.", f"{name} is set aside — the city keeps a place to breathe.", )) return _plan(blurb, [_entry("park", name, rng.choice(_NEARS))]) def _stadium(petition: str, rng: random.Random) -> dict: name = rng.choice(_STADIUMS) blurb = rng.choice(( f"{name} approved — floodlights, roar, and a home team to be named.", f"Game on: {name} takes the big lot.", )) return _plan(blurb, [_entry("stadium", name, "downtown", 1, rng.choice((10, 210)))]) def _church(petition: str, rng: random.Random) -> dict: name = rng.choice(_CHURCHES) blurb = f"{name} is granted its steeple — bells on the hour." return _plan(blurb, [_entry("church", name, rng.choice(_NEARS), 1, rng.choice((40, 350)))]) def _school(petition: str, rng: random.Random) -> dict: name = rng.choice(_SCHOOLS) blurb = rng.choice(( f"{name} chartered — chalk dust and a morning bell.", f"The city invests in small citizens: {name} opens soon.", )) return _plan(blurb, [_entry("school", name, rng.choice(_NEARS), rng.randint(1, 2), 200)]) def _hospital(petition: str, rng: random.Random) -> dict: name = rng.choice(_HOSPITALS) blurb = f"{name} approved — the city will be looked after." return _plan(blurb, [_entry("hospital", name, rng.choice(_NEARS), rng.randint(3, 6), 200)]) def _bank(petition: str, rng: random.Random) -> dict: name = rng.choice(_BANKS) blurb = f"{name} chartered downtown — marble face, careful ledgers." return _plan(blurb, [_entry("bank", name, "downtown", rng.randint(2, 4), 210)]) def _factory(petition: str, rng: random.Random) -> dict: name = rng.choice(_FACTORIES) blurb = rng.choice(( f"{name} zoned for the edge of town — jobs by the shift.", f"Industry arrives: {name}, smokestack and all.", )) return _plan(blurb, [_entry("factory", name, "the river", rng.randint(1, 2), 30)]) def _market(petition: str, rng: random.Random) -> dict: name = rng.choice(_MARKETS) blurb = rng.choice(( f"{name} licensed — stalls up by Saturday.", f"Permit granted: {name}, the street will smell of bread.", )) return _plan(blurb, [_entry("market", name, rng.choice(_NEARS), 1, rng.choice((35, 15)))]) def _default(petition: str, rng: random.Random) -> dict: name = _petition_name(petition) blurb = f"City hall reads the petition twice, nods, and grants {name}." return _plan(blurb, [_entry("house", name, rng.choice(_NEARS), rng.randint(1, 2), rng.choice((20, 200, 320)))]) # Order matters: most specific first; "house" is the safety net before default. _CATEGORIES: tuple[tuple[re.Pattern[str], object], ...] = ( (re.compile(r"\b(districts?|downtown|neighbou?rhoods?|blocks?|quarters?|villages?|towns?|suburbs?|expand|grow)\b", re.I), _district), (re.compile(r"\b(rivers?|riverside|waterfront|bridges?|docks?|harbou?rs?|piers?|ferry)\b", re.I), _river), (re.compile(r"\b(caf[eé]s?|coffee|espresso|diners?|restaurants?|baker(?:y|ies)|bistros?|tea|ramen|pizz\w*|brunch|noodles?)\b", re.I), _cafe), (re.compile(r"\b(apartments?|condos?|flats?|hotels?|housing|lofts?)\b", re.I), _apartments), (re.compile(r"\b(towers?|offices?|skyscrapers?|high-?rises?|headquarters|startups?)\b", re.I), _tower), (re.compile(r"\b(stadiums?|arenas?|sports?|football|soccer|baseball|matches?)\b", re.I), _stadium), (re.compile(r"\b(church(?:es)?|temples?|chapels?|mosques?|cathedrals?|shrines?)\b", re.I), _church), (re.compile(r"\b(schools?|librar(?:y|ies)|universit(?:y|ies)|colleges?|academ(?:y|ies)|kids|children)\b", re.I), _school), (re.compile(r"\b(hospitals?|clinics?|doctors?|medical|health)\b", re.I), _hospital), (re.compile(r"\b(banks?|finance|credit|vaults?)\b", re.I), _bank), (re.compile(r"\b(factor(?:y|ies)|industr\w*|plants?|warehouses?|mills?|workshops?)\b", re.I), _factory), (re.compile(r"\b(markets?|malls?|grocer\w*|supermarkets?|shops?|stores?|boutiques?|bazaars?|pharmac\w*)\b", re.I), _market), (re.compile(r"\b(parks?|gardens?|greens?|trees?|playgrounds?|plazas?|squares?|fountains?)\b", re.I), _park), (re.compile(r"\b(houses?|homes?|cottages?|bungalows?|cabins?|townhouses?)\b", re.I), _house), ) def build_script(petition: str) -> str: """The full BUILD JSON string for a petition. Deterministic: same petition (modulo case/whitespace) always yields the same permit.""" petition = (petition or "").strip() or "a small building" rng = random.Random(_seed_for(petition)) for pattern, builder in _CATEGORIES: if pattern.search(petition): return json.dumps(builder(petition, rng), ensure_ascii=False) # type: ignore[operator] return json.dumps(_default(petition, rng), ensure_ascii=False) _CANDIDATE_LINE_RE = re.compile(r"-\s*(F\d+):\s*([^\n(]*)\(predicted index (\d+)\)") _TRAFFIC_INDEX_RE = re.compile(r'"traffic_index"\s*:\s*(\d+)') _WORST_RE = re.compile(r'"worst"\s*:\s*"([^"]{1,48})"') def fix_script(prompt: str) -> str: """The FIX JSON string, built from the candidates/numbers rendered into the fix prompt by mind/prompts.py. Picks the lowest predicted index.""" text = str(prompt or "") m = _WORST_RE.search(text) worst = m.group(1) if m else "the worst crossing" ti = _TRAFFIC_INDEX_RE.search(text) pairs = _CANDIDATE_LINE_RE.findall(text) if pairs: cid, desc, pred = min(pairs, key=lambda p: int(p[2])) desc = " ".join(desc.split()).rstrip(" ,") or "the engine's bypass" if ti: diagnosis = ( f"Index {ti.group(1)} with {worst} over capacity; " f"{cid} ({desc}) brings the predicted index to {pred}." ) else: diagnosis = f"{worst} is over capacity; {cid} ({desc}) brings the predicted index to {pred}." else: m = re.search(r"\b(F\d+)\b", text) cid = m.group(1) if m else "F1" diagnosis = f"{worst} runs over capacity; applying {cid}." blurb = f"CITY ENGINEER: relief ordered for {worst}." return json.dumps( {"diagnosis": diagnosis[:160], "choice": cid, "blurb": blurb[:120]}, ensure_ascii=False, )