""" SyndemicGPS reasoning engine — deterministic, LLM-free. Implements §5-§21 of the SyndemicGPS outline: X_u -> individual syndemic graph G_I C_g -> geographic context graph G_G = [E_g, H_g, S_g, D_g, R_g] G_S = G_I (+) G_G (evidence-gated fusion) pi = f(X_u, C_g, G_S) (contextual support pathway) Every number an answer contains is produced here. The language model never invents regional epidemiology, service availability, or statistics — it only parses the request and verbalises this module's structured output. DATA NOTICE: the contextual values below are ILLUSTRATIVE PLACEHOLDERS, chosen to be order-of-magnitude plausible for the named indicator class. They are not retrieved statistics and must not be cited. Swap `LOCS` / `GLOBAL_BASE` for a live indicator service to make the pipeline real. """ from __future__ import annotations import copy import math import re from . import geo, worldbank from dataclasses import dataclass, field from typing import Any, Dict, List, Optional YEAR = 2026 STALE_AFTER = 4 TOP = 6 # --------------------------------------------------------------------------- # # INDIVIDUAL LAYER — X_u and G_I # --------------------------------------------------------------------------- # # A closed vocabulary of individual factors. The case is NOT hard-coded: any # person described in conversation is parsed into a subset of these nodes, with # severities, and G_I is rebuilt from that subset. Only edges whose endpoints # are both present in the case are drawn. COL_SOCIAL, COL_MENTAL, COL_BODY = "social", "mental", "body" NODE_CATALOG: Dict[str, Dict[str, Any]] = { # ---- social / structural ---- "financial_strain": dict(lab="Financial strain", col=COL_SOCIAL), "food_insecurity": dict(lab="Food insecurity", col=COL_SOCIAL), "crowded_housing": dict(lab="Crowded housing", col=COL_SOCIAL), "isolation": dict(lab="Social isolation", col=COL_SOCIAL), "displacement": dict(lab="Displacement", col=COL_SOCIAL), "caregiving": dict(lab="Caregiving load", col=COL_SOCIAL), "prior_trauma": dict(lab="Disruptive life event", col=COL_SOCIAL), "fam_hx": dict(lab="Family history", col=COL_SOCIAL), "fam_hx_mental": dict(lab="Family mental-health hx", col=COL_SOCIAL), # ---- mental ---- "academic_stress": dict(lab="Academic stress", col=COL_MENTAL), "work_stress": dict(lab="Work stress", col=COL_MENTAL), "sleep_short": dict(lab="Short sleep", col=COL_MENTAL), "depressive_sx": dict(lab="Depressive sx", col=COL_MENTAL), "anxiety_sx": dict(lab="Anxiety sx", col=COL_MENTAL), "burnout": dict(lab="Burnout", col=COL_MENTAL), "si_flag": dict(lab="Self-harm thoughts", col=COL_MENTAL, safety=True), # ---- physical / behavioural ---- "poor_diet": dict(lab="Poor diet", col=COL_BODY), "gi_symptoms": dict(lab="GI symptoms", col=COL_BODY), "metabolic_vuln": dict(lab="Metabolic vuln.", col=COL_BODY), "chronic_fatigue": dict(lab="Chronic fatigue", col=COL_BODY), "headache": dict(lab="Headache / migraine", col=COL_BODY), "back_pain": dict(lab="Back pain", col=COL_BODY), "asthma": dict(lab="Asthma / respiratory", col=COL_BODY), "recurrent_uri": dict(lab="Recurrent URI", col=COL_BODY), "diarrhoeal": dict(lab="Diarrhoeal illness", col=COL_BODY), "skin_infection": dict(lab="Skin infection", col=COL_BODY), "self_medication": dict(lab="Self-medication", col=COL_BODY), "sedentary": dict(lab="Sedentary", col=COL_BODY), "substance_use": dict(lab="Substance use", col=COL_BODY), } COLUMNS = [COL_SOCIAL, COL_MENTAL, COL_BODY] COL_X = {COL_SOCIAL: 88, COL_MENTAL: 235, COL_BODY: 382} # Interaction catalogue. An edge is drawn only when BOTH endpoints are in the case. EDGES_I = [ ("academic_stress", "sleep_short", "bi", "strong"), ("work_stress", "sleep_short", "bi", "strong"), ("sleep_short", "depressive_sx", "bi", "strong"), ("academic_stress", "depressive_sx", "dir", "moderate"), ("academic_stress", "anxiety_sx", "dir", "moderate"), ("anxiety_sx", "sleep_short", "bi", "moderate"), ("burnout", "academic_stress", "bi", "moderate"), ("depressive_sx", "si_flag", "dir", "strong"), ("fam_hx_mental", "depressive_sx", "dir", "moderate"), ("prior_trauma", "depressive_sx", "dir", "moderate"), ("isolation", "depressive_sx", "dir", "moderate"), ("caregiving", "academic_stress", "dir", "moderate"), ("financial_strain", "food_insecurity", "dir", "strong"), ("financial_strain", "academic_stress", "dir", "moderate"), ("displacement", "isolation", "dir", "moderate"), ("displacement", "food_insecurity", "dir", "moderate"), ("food_insecurity", "poor_diet", "dir", "strong"), ("food_insecurity", "gi_symptoms", "dir", "moderate"), ("poor_diet", "metabolic_vuln", "dir", "moderate"), ("poor_diet", "gi_symptoms", "dir", "moderate"), ("gi_symptoms", "poor_diet", "bi", "weak"), ("fam_hx", "metabolic_vuln", "dir", "strong"), ("sedentary", "metabolic_vuln", "dir", "moderate"), ("crowded_housing", "recurrent_uri", "dir", "strong"), ("crowded_housing", "skin_infection", "dir", "moderate"), ("crowded_housing", "diarrhoeal", "dir", "moderate"), ("asthma", "recurrent_uri", "dir", "moderate"), ("sleep_short", "chronic_fatigue", "dir", "strong"), ("depressive_sx", "chronic_fatigue", "dir", "moderate"), ("recurrent_uri", "chronic_fatigue", "dir", "moderate"), ("diarrhoeal", "chronic_fatigue", "dir", "moderate"), ("depressive_sx", "self_medication", "dir", "moderate"), ("depressive_sx", "sedentary", "dir", "moderate"), ("substance_use", "sleep_short", "dir", "moderate"), ("headache", "sleep_short", "bi", "moderate"), ("back_pain", "sedentary", "dir", "moderate"), ] # The GREC pilot student, offered as ONE example case among the scenarios — # it is loaded through the same path as any case the user describes. EXAMPLE_CASE = { "summary": "21-year-old university student, Bangladesh (GREC pilot cohort, n=32)", "nodes": { "academic_stress": dict(sev=3, sub="self-rated 5/5", st="reported", conf=.85), "sleep_short": dict(sev=3, sub="5 h/night", st="reported", conf=.85), "depressive_sx": dict(sev=2, sub="PHQ-9 = 14", st="reported", conf=.88), "food_insecurity": dict(sev=2, sub="budget-driven sourcing", st="reported", conf=.80), "financial_strain": dict(sev=2, sub="family-dependent", st="reported", conf=.80), "crowded_housing": dict(sev=2, sub="hall residence", st="reported", conf=.90), "fam_hx": dict(sev=2, sub="diabetes + hypertension", st="reported", conf=.75), "gi_symptoms": dict(sev=2, sub="recurrent, IBS-like", st="reported", conf=.75), "recurrent_uri": dict(sev=2, sub=">=3 episodes / 12 mo", st="reported", conf=.78), "self_medication": dict(sev=2, sub="avoids formal care", st="reported", conf=.80), "poor_diet": dict(sev=2, sub="inferred", st="inferred", conf=.55), "metabolic_vuln": dict(sev=2, sub="inferred", st="inferred", conf=.50), }, } # Edges whose target is inferred rather than reported: if the source is present # with real severity, the target is derived at reduced confidence. INFERENCE_RULES = [ ("food_insecurity", "poor_diet", "inferred from food insecurity"), ("poor_diet", "metabolic_vuln", "inferred from dietary risk"), ("fam_hx", "metabolic_vuln", "inferred from family history"), ] def layout(case_nodes: Dict[str, Dict[str, Any]]) -> Dict[str, Dict[str, Any]]: """Place the active case on the canvas: one column per factor family.""" out: Dict[str, Dict[str, Any]] = {} for col in COLUMNS: members = [k for k in NODE_CATALOG if k in case_nodes and NODE_CATALOG[k]["col"] == col] for i, k in enumerate(members): meta, node = NODE_CATALOG[k], case_nodes[k] out[k] = dict(lab=meta["lab"], col=col, sub=node.get("sub", ""), sev=int(node.get("sev", 0)), st=node.get("st", "reported"), conf=float(node.get("conf", .75)), safety=meta.get("safety", False), x=COL_X[col], y=60 + i * 100) return out def case_height(case_nodes: Dict[str, Dict[str, Any]]) -> int: rows = max((sum(1 for k in case_nodes if NODE_CATALOG.get(k, {}).get("col") == c) for c in COLUMNS), default=1) return 60 + max(rows, 1) * 100 # --------------------------------------------------------------------------- # # GEOGRAPHIC LAYER — C_g = [E_g, H_g, S_g, D_g, R_g] # dir=+1 -> a higher value is more adverse # max_res -> the coarsest level this factor may back off to (§12) # --------------------------------------------------------------------------- # FACTORS: Dict[str, Dict[str, Any]] = { "pm25": dict(lab="PM2.5", short="PM2.5", unit="ug/m3", dom="E", dir=+1, lo=5, hi=90, max_res="global", regional=True, x=600, y=70, wt="recurrent_uri"), "heat_days": dict(lab="Heat days >35C", short="Heat", unit="d/yr", dom="E", dir=+1, lo=0, hi=45, max_res="global", regional=True, x=720, y=70, wt="sleep_short"), "wash": dict(lab="Safely managed sanitation", short="WASH", unit="%", dom="E", dir=-1, lo=20, hi=100, max_res="national", regional=True, x=840, y=70, wt="gi_symptoms"), "primary_care": dict(lab="Primary-care access", short="Primary care", unit="idx", dom="H", dir=-1, lo=30, hi=95, max_res="national", x=595, y=180, wt="self_medication"), "mh_workforce": dict(lab="Mental-health workforce", short="MH staff", unit="/100k", dom="H", dir=-1, lo=0.2, hi=25, max_res="national", x=700, y=180, wt="depressive_sx"), "referral": dict(lab="Referral capacity", short="Referral", unit="idx", dom="H", dir=-1, lo=30, hi=90, max_res="national", x=805, y=180, wt="self_medication"), "oop": dict(lab="Out-of-pocket health cost", short="OOP cost", unit="%", dom="H", dir=+1, lo=8, hi=80, max_res="national", x=910, y=180, wt="self_medication"), "food_insec": dict(lab="Food insecurity (population)", short="Food insec.", unit="%", dom="S", dir=+1, lo=3, hi=55, max_res="national", x=595, y=290, wt="food_insecurity"), "poverty": dict(lab="Poverty rate", short="Poverty", unit="%", dom="S", dir=+1, lo=1, hi=60, max_res="national", x=700, y=290, wt="food_insecurity"), "youth_unemp": dict(lab="Youth unemployment", short="Youth unemp.", unit="%", dom="S", dir=+1, lo=5, hi=30, max_res="national", x=805, y=290, wt="food_insecurity"), "cd_burden": dict(lab="CD burden (respiratory/diarrhoeal)", short="CD burden", unit="idx", dom="D", dir=+1, lo=15, hi=90, max_res="global", regional=True, x=595, y=400, wt="recurrent_uri"), "dm_prev": dict(lab="Diabetes prevalence", short="Diabetes", unit="%", dom="D", dir=+1, lo=3, hi=16, max_res="national", x=700, y=400, wt="fam_hx"), "dep_prev": dict(lab="Depression prevalence", short="Depression", unit="%", dom="D", dir=+1, lo=3, hi=9, max_res="national", x=805, y=400, wt="depressive_sx"), "campus_svc": dict(lab="Campus/community services", short="Campus svc", unit="idx", dom="R", dir=-1, lo=15, hi=90, max_res="subnational", x=595, y=505, wt="academic_stress"), "crisis": dict(lab="Verified crisis pathway", short="Crisis line", unit="", dom="R", dir=-1, lo=0, hi=1, max_res="national", x=700, y=505, wt="si_flag"), } DOMAINS = { "E": "Environmental E_g", "H": "Healthcare system H_g", "S": "Socioeconomic S_g", "D": "Disease burden D_g", "R": "Resource / referral R_g", } EDGES_G = [("primary_care", "referral"), ("oop", "primary_care"), ("poverty", "food_insec"), ("wash", "cd_burden"), ("pm25", "cd_burden"), ("mh_workforce", "referral")] RES = ["city", "subnational", "national", "region", "global"] RES_LAB = {"city": "City", "subnational": "Subnational", "national": "National", "region": "Cross-border region", "global": "Global baseline"} def _rank(r: str) -> int: return RES.index(r) GLOBAL_BASE = { "pm25": dict(v=32, ev=.70, yr=2024, src="illustrative global baseline"), "heat_days": dict(v=12, ev=.55, yr=2024, src="illustrative global baseline"), "wash": dict(v=57, ev=.70, yr=2022, src="illustrative global baseline"), "primary_care": dict(v=55, ev=.60, yr=2023, src="illustrative global baseline"), "mh_workforce": dict(v=13, ev=.55, yr=2023, src="illustrative global baseline"), "referral": dict(v=55, ev=.55, yr=2023, src="illustrative global baseline"), "oop": dict(v=32, ev=.65, yr=2022, src="illustrative global baseline"), "food_insec": dict(v=29, ev=.70, yr=2023, src="illustrative global baseline"), "poverty": dict(v=24, ev=.70, yr=2022, src="illustrative global baseline"), "youth_unemp": dict(v=13, ev=.65, yr=2023, src="illustrative global baseline"), "cd_burden": dict(v=45, ev=.60, yr=2022, src="illustrative global baseline"), "dm_prev": dict(v=10, ev=.70, yr=2021, src="illustrative global baseline"), "dep_prev": dict(v=5.0, ev=.60, yr=2019, src="illustrative global baseline"), "campus_svc": dict(v=45, ev=.30, yr=2023, src="illustrative global baseline"), "crisis": dict(v=0, ev=.20, yr=2022, src="Unverified directory aggregation", note="no verified current pathway"), } LOCS: Dict[str, Dict[str, Any]] = { "dhaka": dict(lat=23.777, lon=90.399, iso3="BGD", name="Dhaka, Bangladesh", short="Dhaka", lang="Bengali / English", neighbors=[], obs={}), "stockholm": dict(lat=59.329, lon=18.069, iso3="SWE", name="Stockholm, Sweden", short="Stockholm", lang="Swedish / English", neighbors=[], obs={}), "nairobi": dict(lat=-1.286, lon=36.817, iso3="KEN", name="Nairobi, Kenya", short="Nairobi", lang="Swahili / English", neighbors=[], obs={}), "chicago": dict(lat=41.878, lon=-87.63, iso3="USA", name="Chicago, United States", short="Chicago", lang="English / Spanish", neighbors=[], obs={}), "coxs": dict(lat=21.427, lon=92.005, iso3="BGD", name="Cox's Bazar, Bangladesh (border region)", short="Cox's Bazar", lang="Bengali / Chittagonian / English", neighbors=[dict(n="Teknaf sub-district (BD)", d=42, x=False), dict(n="Maungdaw, Rakhine (MM)", d=78, x=True)], obs={}), "global": dict(name="Global baseline (no locality)", short="Global", lang="varies", neighbors=[], obs={}), } def _slug(name: str) -> str: return re.sub(r"[^a-z0-9]+", "_", name.lower()).strip("_") def _iso3_for(iso2: str) -> Optional[str]: for r in worldbank.countries(): if r["iso2"].upper() == iso2.upper(): return r["iso3"] return None def hydrate(loc_id: str) -> None: """Retrieve this place's context: point measurements, then national series.""" loc = LOCS.get(loc_id) if not loc or loc.get("_hydrated") or loc_id == "global": return loc["_hydrated"] = True # city-level: real measurements at the coordinates the user named if loc.get("lat") is not None: for factor, rows in geo.point_context(loc["lat"], loc["lon"]).items(): keep = [o for o in loc["obs"].get(factor, []) if o["res"] != "city"] loc["obs"][factor] = rows + keep # national-level: World Bank series iso3 = loc.get("iso3") if not iso3: iso2 = loc.get("iso2") iso3 = _iso3_for(iso2) if iso2 else None if not iso3: m = worldbank.match_country(loc.get("country") or loc["name"]) iso3 = m["iso3"] if m else None loc["iso3"] = iso3 if not iso3: return for factor, rows in worldbank.fetch_context(iso3).items(): keep = [o for o in loc["obs"].get(factor, []) if o["res"] != "national"] loc["obs"][factor] = keep + rows def ensure_place(query: str) -> Optional[str]: """Resolve any free-text place to a registry id, fetching its context if new.""" if not query: return None key = query.strip().lower() if key in LOCS: hydrate(key) return key for alias, lid in LOC_ALIAS.items(): if len(alias) > 3 and re.search(rf"\b{re.escape(alias)}\b", key): hydrate(lid) return lid # Any place on earth: geocode it, then retrieve context for that point. g = geo.geocode(query) if g: lid = _slug(g["label"]) if lid not in LOCS: LOCS[lid] = dict(name=g["label"], short=g["name"], country=g["country"], iso2=g["iso2"], lat=g["lat"], lon=g["lon"], lang="", neighbors=[], obs={}) hydrate(lid) return lid match = worldbank.match_country(query) if not match: return None name = match["name"] lid = _slug(name) if lid not in LOCS: LOCS[lid] = dict(name=name, short=name, country=name, iso3=match["iso3"], lang="", neighbors=[], obs={}) hydrate(lid) return lid def place_choices() -> List[tuple]: """Registry entries for the UI, newest resolutions last.""" return [(v["name"], k) for k, v in LOCS.items()] LOC_ALIAS = { "dhaka": "dhaka", "bangladesh": "dhaka", "bd": "dhaka", "stockholm": "stockholm", "sweden": "stockholm", "swedish": "stockholm", "nairobi": "nairobi", "kenya": "nairobi", "chicago": "chicago", "united states": "chicago", "usa": "chicago", "u.s.": "chicago", "america": "chicago", "american": "chicago", "cox's bazar": "coxs", "coxs bazar": "coxs", "cox bazar": "coxs", "border": "coxs", "global": "global", "baseline": "global", } # --------------------------------------------------------------------------- # # STATE # --------------------------------------------------------------------------- # @dataclass class State: """Everything a turn needs. The case is dynamic — whatever person is described.""" # Starts EMPTY. Loading an example person by default meant a first-time user # describing themselves was merged into someone else's profile. case: Dict[str, Dict[str, Any]] = field(default_factory=dict) summary: str = "" a: str = "dhaka" b: str = "stockholm" tau: float = 0.55 geo_off: bool = False cf: Optional[Dict[str, float]] = None cf_label: str = "" model: str = "" # -- case access ------------------------------------------------------- @property def sev(self) -> Dict[str, int]: return {k: int(v.get("sev", 0)) for k, v in self.case.items()} def set_sev(self, key: str, value: int) -> None: if value <= 0: self.case.pop(key, None) return node = self.case.setdefault(key, dict(sub="", st="reported", conf=.8)) node["sev"] = int(value) def set_case(self, nodes: Dict[str, Dict[str, Any]], summary: str = "") -> None: """Replace X_u wholesale, then derive any inferable nodes.""" clean = {k: v for k, v in nodes.items() if k in NODE_CATALOG and int(v.get("sev", 0)) > 0} for src, dst, why in INFERENCE_RULES: if src in clean and dst not in clean: clean[dst] = dict(sev=max(1, int(clean[src].get("sev", 1)) - 0), sub=why, st="inferred", conf=.5) self.case = clean if summary: self.summary = summary def profile(self) -> Dict[str, Dict[str, Any]]: return layout(self.case) def height(self) -> int: return case_height(self.case) def tier(self) -> str: if self.sev.get("si_flag", 0) > 0: return "red" if self.sev.get("depressive_sx", 0) >= 2 or self.sev.get("self_medication", 0) >= 2: return "amber" return "green" # --------------------------------------------------------------------------- # # §10-§12 EVIDENCE GATE + RESOLUTION LADDER # --------------------------------------------------------------------------- # def stale_penalty(yr: int) -> float: age = YEAR - yr return min(.30, .05 * (age - STALE_AFTER)) if age > STALE_AFTER else 0.0 def resolve(loc_id: str, tau: float, overrides: Optional[Dict[str, float]] = None) -> Dict[str, Dict[str, Any]]: """Walk the resolution ladder per factor, applying the evidence gate.""" loc = LOCS[loc_id] out: Dict[str, Dict[str, Any]] = {} for key, f in FACTORS.items(): capped = _rank(f["max_res"]) chain = list(loc["obs"].get(key, [])) + [dict(res="global", **GLOBAL_BASE[key])] chain = [o for o in chain if _rank(o["res"]) <= capped] chain.sort(key=lambda o: _rank(o["res"])) used, skipped = None, [] for o in chain: pen = stale_penalty(o["yr"]) eff = o["ev"] - pen if eff >= tau: used = dict(o, eff=eff, stale=pen > 0) break skipped.append(dict(o, eff=eff, stale=pen > 0)) finest = chain[0] if chain else None if used: out[key] = dict(used, ok=True, finest=finest, skipped=skipped, backed_off=bool(finest and used["res"] != finest["res"])) else: reason = (f"no observation at or above {f['max_res']} level cleared tau" if chain else "no admissible observation") out[key] = dict(ok=False, skipped=skipped, finest=finest, reason=reason) if overrides and key in overrides: if out[key]["ok"]: out[key] = dict(out[key], v=overrides[key], cf=True, src="structural counterfactual (user-set)") else: out[key] = dict(ok=True, v=overrides[key], ev=tau, eff=tau, yr=YEAR, res="national", cf=True, src="structural counterfactual (user-set)", backed_off=False, skipped=[], stale=False, finest=finest) return out def fmt(v: Any, key: str) -> str: unit = FACTORS[key]["unit"] if key == "crisis": return "verified" if v else "unverified" n = f"{v:.1f}" if (abs(v) < 10 and not float(v).is_integer()) else f"{int(v)}" return f"{n} {unit}".strip() # --------------------------------------------------------------------------- # # §14 SUPPORT PLANNER — pi # --------------------------------------------------------------------------- # ACTIONS: List[Dict[str, Any]] = [ dict(id="crisis", says="Talk to someone today rather than sitting with it alone. A named line or a trusted person, now, not later.", lab="Verified crisis pathway and immediate safety planning", tier="red", base=lambda s: 100.0 if s.get("si_flag", 0) > 0 else 0.0, drivers=["si_flag"], mods=[]), dict(id="sleep", says="Anchor the wake time first, even on bad nights, and protect the hour before bed. Sleep debt is usually the cheapest thing to move and it lifts mood and appetite with it.", label_fn=lambda s: "Sleep stabilisation with " + ("study-load planning" if s.get("academic_stress") else "work-load planning" if s.get("work_stress") else "a review of what is keeping you up"), lab="Sleep stabilisation paired with academic-load planning", tier="green", base=lambda s: 1.2 * max(s.get("academic_stress", 0), s.get("work_stress", 0), s.get("burnout", 0)) + 1.4 * s.get("sleep_short", 0) + .4 * s.get("headache", 0), drivers=["academic_stress", "work_stress", "burnout", "sleep_short", "headache"], mods=[dict(k="heat_days", cmp=">=", th=20, d=.5, why="sustained heat exposure degrades sleep consolidation"), dict(k="campus_svc", cmp=">=", th=60, d=.4, why="campus study-support services exist to absorb load")]), dict(id="depression", says="Low mood that has lasted weeks is worth naming to a professional, not managing alone. Ask specifically for an assessment, not just reassurance.", lab="Structured depression follow-up with specialist referral", tier="amber", base=lambda s: 2.0 * s.get("depressive_sx", 0) + 1.4 * s.get("anxiety_sx", 0) + .6 * s.get("sleep_short", 0) + .5 * s.get("prior_trauma", 0), drivers=["depressive_sx", "anxiety_sx", "sleep_short", "prior_trauma"], mods=[dict(k="dep_prev", cmp=">=", th=6, d=.3, why="higher background depressive burden in this context"), dict(k="oop", cmp=">=", th=50, d=-.5, why="high out-of-pocket share reduces realistic uptake")], feas=dict(k="mh_workforce", cmp=">=", th=3, alt="Stepped non-specialist-first support with explicit escalation triggers", msg="specialist mental-health capacity is thin; the specialist-referral route is not assumed")), dict(id="food", says="Aim for reliable, cheap, safe calories on a fixed schedule rather than perfect nutrition. Skipped meals drive both the stomach symptoms and the mood dip.", lab="Food-safety and low-cost nutrition planning", tier="green", base=lambda s: 1.3 * s.get("food_insecurity", 0) + .8 * s.get("gi_symptoms", 0), drivers=["food_insecurity", "gi_symptoms"], mods=[dict(k="food_insec", cmp=">=", th=25, d=.9, why="population-level food insecurity is high here"), dict(k="wash", cmp="<=", th=55, d=.8, why="weak sanitation coverage raises food-borne risk")]), dict(id="metabolic", says="Ask for a blood-sugar and blood-pressure check. With family history and disrupted sleep this is a measurement question, not a lifestyle lecture.", lab="Early metabolic screening (HbA1c, blood pressure)", tier="amber", base=lambda s: .9 * s.get("fam_hx", 0) + .7 * s.get("food_insecurity", 0) + .5 * s.get("poor_diet", 0) + .5 * s.get("sedentary", 0) + .4 * s.get("metabolic_vuln", 0), drivers=["fam_hx", "food_insecurity", "poor_diet", "sedentary", "metabolic_vuln"], mods=[dict(k="dm_prev", cmp=">=", th=10, d=.8, why="elevated background diabetes prevalence"), dict(k="primary_care", cmp=">=", th=60, d=.5, why="first-contact screening is realistically reachable"), dict(k="oop", cmp=">=", th=50, d=-.7, why="cost exposure makes routine screening less feasible")], feas=dict(k="primary_care", cmp=">=", th=45, alt="Opportunistic screening via campus clinic or subsidised programme", msg="routine primary-care screening cannot be assumed at this access level")), dict(id="resp", says="Ventilation, hand hygiene and being current on seasonal vaccination cut repeat infections more than anything else available to you.", lab="Respiratory infection reduction (ventilation, hygiene, vaccination)", tier="green", base=lambda s: 1.1 * s.get("recurrent_uri", 0) + .9 * s.get("crowded_housing", 0) + .7 * s.get("asthma", 0), drivers=["recurrent_uri", "crowded_housing", "asthma"], mods=[dict(k="cd_burden", cmp=">=", th=60, d=1.0, why="high communicable-disease background burden"), dict(k="pm25", cmp=">=", th=35, d=.6, why="particulate exposure compounds airway irritation")]), dict(id="air", says="On high-pollution days, shift exertion indoors and keep windows shut at peak traffic hours.", lab="Air-quality exposure management", tier="green", requires=dict(k="pm25", cmp=">=", th=25), base=lambda s: .5 * s.get("recurrent_uri", 0) + .3 * s.get("crowded_housing", 0) + .8 * s.get("asthma", 0), drivers=["recurrent_uri", "asthma"], mods=[dict(k="pm25", cmp=">=", th=50, d=2.4, why="PM2.5 is far above guideline levels"), dict(k="pm25", cmp=">=", th=25, d=.8, why="PM2.5 exceeds guideline levels")]), dict(id="finance", says="Money pressure is upstream of the food and the sleep here. A hardship fund, payment plan or benefits check often moves more than any health advice.", lab="Financial-aid and student-support navigation", tier="green", base=lambda s: .9 * s.get("food_insecurity", 0) + 1.0 * s.get("financial_strain", 0) + .4 * s.get("displacement", 0), drivers=["food_insecurity", "financial_strain", "displacement"], mods=[dict(k="poverty", cmp=">=", th=20, d=.6, why="structural poverty context"), dict(k="youth_unemp", cmp=">=", th=15, d=.3, why="constrained student labour market"), dict(k="campus_svc", cmp=">=", th=60, d=.6, why="an institutional aid pathway is documented")]), dict(id="continuity", says="Pick one place to go back to, so someone sees the whole picture instead of one symptom at a time.", lab="Care-continuity plan to replace self-medication", tier="amber", base=lambda s: 1.2 * s.get("self_medication", 0), drivers=["self_medication"], mods=[dict(k="primary_care", cmp="<=", th=50, d=.5, why="weak first contact makes explicit navigation more important"), dict(k="referral", cmp="<=", th=50, d=.4, why="thin referral capacity requires a planned route"), dict(k="oop", cmp=">=", th=50, d=.4, why="cost exposure drives informal self-treatment")]), dict(id="heat", says="Plan effort and sleep around the heat: hydration, shade in peak hours, and a cooler sleeping setup.", lab="Heat-exposure planning", tier="green", requires=dict(k="heat_days", cmp=">=", th=15), base=lambda s: .3 + .2 * s.get("sleep_short", 0), drivers=["sleep_short"], mods=[dict(k="heat_days", cmp=">=", th=30, d=1.0, why="prolonged extreme-heat season")]), dict(id="social", says="One recurring, low-effort contact beats an ambitious plan. Isolation feeds the mood symptoms directly.", lab="Social connection and peer-support planning", tier="green", base=lambda s: 1.2 * s.get("isolation", 0) + .8 * s.get("displacement", 0) + .5 * s.get("depressive_sx", 0), drivers=["isolation", "displacement", "depressive_sx"], mods=[dict(k="campus_svc", cmp=">=", th=60, d=.7, why="community or campus groups are documented here"), dict(k="youth_unemp", cmp=">=", th=15, d=.3, why="a thin labour market narrows everyday contact")]), dict(id="substance", says="Look at what the drinking or use is doing for you before trying to cut it. Sleep and mood usually improve first.", lab="Substance-use conversation and harm reduction", tier="amber", base=lambda s: 1.6 * s.get("substance_use", 0), drivers=["substance_use"], mods=[dict(k="primary_care", cmp=">=", th=60, d=.4, why="a first-contact route for this exists")], feas=dict(k="referral", cmp=">=", th=45, alt="Self-directed harm-reduction guidance with an explicit escalation trigger", msg="referral capacity is thin; a specialist route is not assumed")), dict(id="fatigue", says="Fatigue with disrupted sleep and repeat infection deserves one joined-up review rather than three separate ones.", lab="Fatigue review — sleep, mood and infection burden together", tier="amber", base=lambda s: 1.3 * s.get("chronic_fatigue", 0) + .4 * s.get("back_pain", 0), drivers=["chronic_fatigue", "back_pain"], mods=[dict(k="cd_burden", cmp=">=", th=60, d=.5, why="recurrent infection is a plausible contributor here"), dict(k="primary_care", cmp=">=", th=60, d=.4, why="workup is realistically reachable")]), dict(id="gi", says="Track what precedes the episodes and keep oral rehydration to hand. Persistent or bloody symptoms need review, not self-management.", lab="GI symptom review and rehydration guidance", tier="green", base=lambda s: 1.0 * s.get("gi_symptoms", 0) + 1.2 * s.get("diarrhoeal", 0), drivers=["gi_symptoms", "diarrhoeal"], mods=[dict(k="wash", cmp="<=", th=45, d=.9, why="sanitation coverage is low"), dict(k="cd_burden", cmp=">=", th=60, d=.5, why="diarrhoeal disease burden is elevated")]), ] def _test(v: float, cmp: str, th: float) -> bool: return v >= th if cmp == ">=" else v <= th def crisis_resource(ctx: Dict[str, Any], geo_off: bool) -> Dict[str, str]: if geo_off: return dict(txt="Geography ablated - route via the nearest emergency service and a trusted person; " "no local service is named.", lvl="none") c = ctx.get("crisis") if c and c["ok"] and c["v"] == 1: return dict(txt=f"Verified pathway available: {c.get('note', 'national support line')} " f"({RES_LAB[c['res']]}, {c['yr']}).", lvl=c["res"]) return dict(txt="No sufficiently current verified local pathway. Falling back to emergency services, a trusted " "person, and in-person urgent care - no local service is named.", lvl="global") def plan(state: State, ctx: Dict[str, Any], geo_off: bool) -> List[Dict[str, Any]]: s = state.sev items: List[Dict[str, Any]] = [] for a in ACTIONS: score = a["base"](s) if score <= 0: continue if a.get("requires"): if geo_off: continue c = ctx.get(a["requires"]["k"]) if not c or not c["ok"] or not _test(c["v"], a["requires"]["cmp"], a["requires"]["th"]): continue geo_why, abstained, evs = [], [], [] if not geo_off: for m in a["mods"]: c = ctx.get(m["k"]) if not c or not c["ok"]: abstained.append(FACTORS[m["k"]]["lab"]) continue if _test(c["v"], m["cmp"], m["th"]): score += m["d"] evs.append(c["eff"]) geo_why.append(f"{m['why']} - {FACTORS[m['k']]['short']} {fmt(c['v'], m['k'])} " f"({RES_LAB[c['res']]} {c['yr']}" f"{', counterfactual' if c.get('cf') else ''})") label = a["lab"] if a.get("label_fn"): try: label = a["label_fn"](s) except Exception: pass feas_note = None if a.get("feas") and not geo_off: c = ctx.get(a["feas"]["k"]) if c and c["ok"] and not _test(c["v"], a["feas"]["cmp"], a["feas"]["th"]): label = a["feas"]["alt"] feas_note = a["feas"]["msg"] score -= .3 elif not c or not c["ok"]: feas_note = (f"{FACTORS[a['feas']['k']]['lab']} did not clear the evidence gate - " f"capacity is not assumed either way.") why = [f"{NODE_CATALOG[k]['lab'].lower()} (severity {s.get(k, 0)})" for k in a["drivers"] if s.get(k, 0) > 0] conf = min(1.0, max(0.0, (sum(evs) / len(evs) if evs else .70) * (.8 if geo_off else 1.0))) items.append(dict(id=a["id"], lab=label, base=a["lab"], tier=a["tier"], score=score, why=why, says=a.get("says", ""), geo_why=geo_why, abstained=abstained, feas_note=feas_note, conf=conf, resource=crisis_resource(ctx, geo_off) if a["id"] == "crisis" else None)) items.sort(key=lambda x: -x["score"]) return items # --------------------------------------------------------------------------- # # §9 GEOGRAPHIC INTERVENTION FLIP # --------------------------------------------------------------------------- # def gif(pa: List[Dict], pb: List[Dict], la: str, lb: str) -> List[Dict[str, str]]: out: List[Dict[str, str]] = [] ma = {x["id"]: dict(x, r=i) for i, x in enumerate(pa)} mb = {x["id"]: dict(x, r=i) for i, x in enumerate(pb)} for i, a in ma.items(): b = mb.get(i) if not b: out.append(dict(t="pathway", d=f"**{a['base']}** is in the {la} pathway but absent in {lb}.")) continue if abs(a["r"] - b["r"]) >= 2 and (a["r"] < TOP or b["r"] < TOP): out.append(dict(t="priority", d=f"**{a['base']}** moves from rank {a['r']+1} in {la} to rank {b['r']+1} in {lb}.")) if a["lab"] != b["lab"]: out.append(dict(t="feasibility", d=f"**{a['base']}** is delivered as \"{a['lab']}\" in {la} and \"{b['lab']}\" in {lb}.")) if abs(a["conf"] - b["conf"]) >= .15: out.append(dict(t="confidence", d=f"Confidence in **{a['base']}** shifts {a['conf']*100:.0f}% -> {b['conf']*100:.0f}% " f"because local evidence quality differs.")) if i == "crisis" and a["resource"] and b["resource"] and a["resource"]["txt"] != b["resource"]["txt"]: out.append(dict(t="escalation", d=f"The verified escalation resource differs: {la} -> {a['resource']['lvl']} level; " f"{lb} -> {b['resource']['lvl']} level.")) for i, b in mb.items(): if i not in ma: out.append(dict(t="pathway", d=f"**{b['base']}** appears in {lb} but not in {la}.")) return out # --------------------------------------------------------------------------- # # §18-§21 UNCERTAINTY, PRESSURE, SIMILARITY # --------------------------------------------------------------------------- # def uncertainty(state: State, ctx: Dict[str, Any], geo_off: bool) -> Dict[str, float]: prof = state.profile() ps = [p for p in prof.values() if p["sev"] > 0] u_i = (1 - sum(p["conf"] for p in ps) / len(ps)) if ps else 1.0 used = [c for c in ctx.values() if c["ok"]] u_g = 1.0 if geo_off else (1 - sum(c["eff"] for c in used) / len(used) if used else 1.0) u_k = .30 u_t = 0.0 if geo_off else (sum(min(1, (YEAR - c["yr"]) / 10) for c in used) / len(used) if used else 1.0) return dict(U_I=u_i, U_G=u_g, U_K=u_k, U_T=u_t) def norm(key: str, v: float) -> float: f = FACTORS[key] n = (v - f["lo"]) / (f["hi"] - f["lo"]) n = max(0.0, min(1.0, n)) return n if f["dir"] == 1 else 1 - n def pressure(state: State, ctx: Dict[str, Any]) -> float: num = den = 0.0 for k in FACTORS: c = ctx.get(k) if not c or not c["ok"]: continue w = state.sev.get(FACTORS[k]["wt"], 0) / 3 + .12 num += w * norm(k, c["v"]) den += w return 100 * num / den if den else 0.0 def similarity(state: State) -> List[Dict[str, Any]]: a = {k: norm(k, c["v"]) for k, c in resolve(state.a, state.tau, state.cf).items() if c["ok"]} out = [] for lid in LOCS: if lid == state.a: continue b = {k: norm(k, c["v"]) for k, c in resolve(lid, state.tau).items() if c["ok"]} num = den = 0.0 for k, av in a.items(): if k in b: w = state.sev.get(FACTORS[k]["wt"], 0) / 3 + .12 num += w * abs(av - b[k]) den += w out.append(dict(id=lid, d=num / den if den else 1.0)) out.sort(key=lambda x: x["d"]) return out # --------------------------------------------------------------------------- # # §17 STRUCTURAL COUNTERFACTUALS # --------------------------------------------------------------------------- # CFS = [ dict(id="mh", k="mh_workforce", v=12, lab="Raise mental-health workforce to 12 per 100k"), dict(id="pc", k="primary_care", v=82, lab="Raise primary-care access to 82 (index)"), dict(id="fs", k="food_insec", v=8, lab="Reduce population food insecurity to 8%"), dict(id="wa", k="wash", v=95, lab="Raise safely managed sanitation to 95%"), dict(id="pm", k="pm25", v=9, lab="Reduce PM2.5 to 9 ug/m3"), dict(id="op", k="oop", v=72, lab="Raise out-of-pocket health cost to 72%"), dict(id="cs", k="campus_svc", v=75, lab="Establish campus support services (index 75)"), ] SCENARIOS = [ dict(id="sp", n="Same person, different place", d="Dhaka -> Stockholm", a="dhaka", b="stockholm", si=0, tau=.55, q="Move this person from Bangladesh to Sweden."), dict(id="bd", n="Border region backoff", d="Cox's Bazar -> Dhaka", a="coxs", b="dhaka", si=0, tau=.55, q="How does living in Cox's Bazar affect this case?"), dict(id="cap", n="Thin specialist capacity", d="Nairobi -> Chicago", a="nairobi", b="chicago", si=0, tau=.50, q="Move this person from Nairobi to Chicago."), dict(id="esc", n="Crisis escalation", d="Dhaka -> Nairobi, PHQ-9 item 9", a="dhaka", b="nairobi", si=1, tau=.55, q="Why did your answer change?"), ] # --------------------------------------------------------------------------- # # §15 PIPELINE TRACE # --------------------------------------------------------------------------- # def trace(state: State, ctx: Optional[Dict[str, Any]], loc: Optional[str], gate: bool = True, plan_n: int = 0, expl: str = "structured output verbalised") -> List[Dict[str, Any]]: used = sum(1 for c in ctx.values() if c["ok"]) if ctx else 0 back = sum(1 for c in ctx.values() if c["ok"] and c.get("backed_off")) if ctx else 0 active = sum(1 for v in state.sev.values() if v > 0) n = len(FACTORS) return [ dict(s="Case parser", r=f"{active} active nodes, {len(EDGES_I)} edges", skip=False), dict(s="Location resolver", r=loc or "not required", skip=loc is None), dict(s="Context retrieval", r=f"{n} candidate factors" if gate else "skipped - ablation", skip=not gate), dict(s="Evidence gate", r=(f"{used}/{n} passed tau {state.tau:.2f}" + (f", {back} backed off" if back else "")) if gate else "C_g* = empty", skip=not gate), dict(s="Context fusion", r=f"G_S = {active} + {used} nodes" if gate else "G_S = G_I only", skip=False), dict(s="Support planner", r=f"{plan_n} actions activated, top {TOP} shown" if plan_n else "not invoked", skip=not plan_n), dict(s="Safety layer", r=state.tier().upper(), skip=False), dict(s="Explanation", r=expl, skip=False), ]