"""Server-side mirror of the frontend agent-name desensitization map. The web UI displays agent names through a static term→abbreviation map (``frontend-web/src/lib/desensitize.ts``) — e.g. the stored name "态势分析" is shown as "TS分析". The gallery search box therefore matches against the *desensitized* display form, not the raw stored name. When search moves server-side (paginated listing), the backend must match the same way, so this module mirrors that static map verbatim. The frontend map is never extended at runtime (``addDesensitizeEntries`` is defined but never called), so a static copy stays in sync. Used by the SQL listing query to match "raw name OR desensitized name". """ from __future__ import annotations # Mirror of DESENSITIZE_MAP in frontend-web/src/lib/desensitize.ts. # Keep entries identical to the frontend so server-side and client-side search # behave the same. Order does not affect membership tests. DESENSITIZE_MAP: dict[str, str] = { "态势": "TS", "你好": "nh", "滨海作战体系": "第一张网", "滨海作战部队": "第一张网", "Taiwan Air Force Flight Units": "第二张网", "台海空军飞行部队": "第二张网", "空军": "KJ", "海军": "HJ", "陆军": "LJ", "陆战队": "LZD", "海巡": "HX", "憲兵": "XB", "資通電軍": "ZTDJ", "情报": "QB", "指挥": "ZH", "信息准备": "XX准备", "行为体": "XWT", "驱动因素": "QDYS", "叙事": "XS", "前三定": "QSD", "受众目标": "SZMB", "作战": "ZZ", "目的": "MD", "关键传播节点": "GJCBJD", "战法": "ZF", "行动": "XD", "打击": "DJ", "目标": "MB", "战力": "ZL", "筹划": "COH", "策划": "CEH", "模拟": "MN", "推演": "TY", "认知": "RZ", "触及": "CJ", "军": "J", "美军": "MJ", "台军": "TJ", } def desensitize_name(name: str) -> str: """Return ``name`` with every mapped term replaced by its abbreviation. Mirrors the frontend ``desensitize()`` — a sequence of literal replacements. """ out = name for term, abbr in DESENSITIZE_MAP.items(): if term in out: out = out.replace(term, abbr) return out def name_matches(name: str, needle: str) -> bool: """True when ``needle`` (case-insensitive) is in the raw OR desensitized name.""" n = needle.strip().lower() if not n: return True return n in name.lower() or n in desensitize_name(name).lower() def source_terms_for_needle(needle: str) -> list[str]: """Return the raw source terms whose abbreviation contains ``needle``. Lets the SQL search add ``name LIKE %term%`` clauses so a user typing an abbreviation (e.g. "TS") still matches the stored raw term (e.g. "态势"). """ n = needle.strip().lower() if not n: return [] return [term for term, abbr in DESENSITIZE_MAP.items() if n in abbr.lower()]