"""Plain-language vocabulary for the Executive Dynamics workspace/landing/company tabs — the "analyst workspace" side of the app. Everything imports from here instead of hardcoding strings, so "no ML jargon in the product surface" is enforced structurally (one file to review) rather than by convention. The Research side (Benchmarks/Methodology tabs) keeps its existing technical language and is NOT governed by this module. """ from theme import SIG_NEGATIVE, SIG_NEUTRAL, SIG_POSITIVE DIMENSION_LABELS = { "confidence": "Confidence", "stress": "Stress", "specificity": "Specificity", "alignment": "Alignment", "alignment_drift": "Alignment", } # Backend risk_tier values are lowercase ("low"|"medium"|"high"); this module is # solely responsible for mapping those onto the display strings/colors below. RISK_TIER_DISPLAY = {"low": "Low", "medium": "Elevated", "high": "High"} RISK_TIER_COLORS = {"low": SIG_POSITIVE, "medium": SIG_NEUTRAL, "high": SIG_NEGATIVE} def risk_tier_label(risk_tier: str | None) -> str: return RISK_TIER_DISPLAY.get(risk_tier, "Unknown") def risk_tier_color(risk_tier: str | None) -> str: return RISK_TIER_COLORS.get(risk_tier, SIG_NEUTRAL) def largest_friction_label(dimension: str | None) -> str: return DIMENSION_LABELS.get(dimension, "—") def company_name(ticker: str, ticker_names: dict[str, str]) -> str: """Full company name for a ticker, falling back to the raw ticker if unmapped.""" return ticker_names.get(ticker, ticker) def company_dropdown_choices(tickers: list[str], ticker_names: dict[str, str]) -> list[tuple[str, str]]: """(label, value) pairs for gr.Dropdown — label is 'Company Name (TICKER)', value stays the raw ticker so filtering/lookup logic downstream is unchanged. Sorted by company name.""" pairs = [(f"{company_name(t, ticker_names)} ({t})", t) for t in tickers] return sorted(pairs, key=lambda p: p[0]) def risk_driver_bullets(turn: dict) -> list[str]: """Plain-language driver bullets for the Evidence panel. Prefers the exported `risk_drivers` field (already human-readable strings from src/evaluation/behavioral_signals.py). Falls back to templating from the turn's own scores if that field is missing/empty, so the Evidence panel still shows something sensible before/without a fresh data export. """ drivers = turn.get("risk_drivers") if drivers: return list(drivers) fallback = [] if turn.get("stress", 0) > 0.6: fallback.append("elevated vocal/language stress") if (turn.get("alignment_drift") or 0) > 0.6: fallback.append("tone diverged from prepared remarks") if turn.get("specificity", 1.0) < 0.3: fallback.append("low specificity") return fallback