""" Parity check: backend/constants.py vs frontend/src/constants.ts. Run from project root: python3 scripts/check_constants_sync.py Exits 0 if in sync, 1 if drift detected. """ import re import sys from pathlib import Path ROOT = Path(__file__).parent.parent BACKEND = ROOT / "backend" / "app" / "constants.py" FRONTEND = ROOT / "frontend" / "src" / "constants.ts" EMOTION_KEYS = {"calm", "happy", "narrative", "angry", "excited", "sad", "fearful", "command"} errors = [] # ── backend/constants.py ────────────────────────────────────────────────────── py_text = BACKEND.read_text() # reliability: "angry": {"label": "Angry", ..., "reliability": "medium"}, py_reliability: dict[str, str] = {} for m in re.finditer(r'"(calm|happy|narrative|angry|excited|sad|fearful|command)":\s*\{[^}]*"reliability":\s*"(high|medium)"', py_text): py_reliability[m.group(1)] = m.group(2) # EMOTION_TEMPLATES speaker blocks: find lines like `"Yash": {` then scan for emotion keys # until next top-level speaker or closing of the outer dict def extract_template_keys_py(text: str, speaker: str) -> set[str]: # Find the speaker block inside EMOTION_TEMPLATES pattern = rf'"{speaker}":\s*\{{' m = re.search(pattern, text) if not m: return set() start = m.end() # Scan forward collecting emotion keys until balanced brace closes depth = 1 pos = start keys: set[str] = set() while pos < len(text) and depth > 0: if text[pos] == '{': depth += 1 elif text[pos] == '}': depth -= 1 if depth == 0: break # Check for emotion key line: "calm": or "happy": m2 = re.match(r'\s*"(calm|happy|narrative|angry|excited|sad|fearful|command)":\s*', text[pos:pos+30]) if m2: keys.add(m2.group(1)) pos += 1 return keys py_yash_emo = extract_template_keys_py(py_text, "Yash") py_neha_emo = extract_template_keys_py(py_text, "Neha") # ── frontend/src/constants.ts ───────────────────────────────────────────────── ts_text = FRONTEND.read_text() # reliability: angry: { label: "Angry", emoji: "😠", reliability: "medium" }, ts_reliability: dict[str, str] = {} for m in re.finditer(r'(calm|happy|narrative|angry|excited|sad|fearful|command):\s*\{[^}]*reliability:\s*"(high|medium)"', ts_text): ts_reliability[m.group(1)] = m.group(2) # EMOTION_TEMPLATES speaker blocks (TypeScript): `Yash: {` def extract_template_keys_ts(text: str, speaker: str) -> set[str]: pattern = rf'\b{speaker}:\s*\{{' m = re.search(pattern, text) if not m: return set() start = m.end() depth = 1 pos = start keys: set[str] = set() while pos < len(text) and depth > 0: if text[pos] == '{': depth += 1 elif text[pos] == '}': depth -= 1 if depth == 0: break m2 = re.match(r'\s*(calm|happy|narrative|angry|excited|sad|fearful|command):\s*', text[pos:pos+30]) if m2: keys.add(m2.group(1)) pos += 1 return keys ts_yash_emo = extract_template_keys_ts(ts_text, "Yash") ts_neha_emo = extract_template_keys_ts(ts_text, "Neha") # ── Compare ─────────────────────────────────────────────────────────────────── for key in EMOTION_KEYS: py_rel = py_reliability.get(key) ts_rel = ts_reliability.get(key) if py_rel and ts_rel and py_rel != ts_rel: errors.append(f"RELIABILITY MISMATCH '{key}': constants.py={py_rel!r}, constants.ts={ts_rel!r}") if py_rel and not ts_rel: errors.append(f"MISSING reliability for '{key}' in constants.ts") if ts_rel and not py_rel: errors.append(f"MISSING reliability for '{key}' in constants.py") for label, py_keys, ts_keys in [ ("Yash", py_yash_emo, ts_yash_emo), ("Neha", py_neha_emo, ts_neha_emo), ]: missing_in_ts = EMOTION_KEYS - ts_keys missing_in_py = EMOTION_KEYS - py_keys if missing_in_py: errors.append(f"MISSING EMOTIONS in {label} (constants.py): {sorted(missing_in_py)}") if missing_in_ts: errors.append(f"MISSING EMOTIONS in {label} (constants.ts): {sorted(missing_in_ts)}") if py_keys != ts_keys: errors.append(f"{label} template keys differ: py={sorted(py_keys)}, ts={sorted(ts_keys)}") if errors: print("CONSTANTS SYNC FAILED:") for e in errors: print(f" {e}") sys.exit(1) else: print("OK — constants.py and constants.ts are in sync.") sys.exit(0)