"""Relationship-language gate. The guide's example, padded to the length floor, passes.""" from __future__ import annotations import copy import sys from pathlib import Path ROOT = Path(__file__).resolve().parents[1] SCRIPTS = ROOT / "scripts" if str(SCRIPTS) not in sys.path: sys.path.insert(0, str(SCRIPTS)) from marriage_match_service.synastry_full_reader_gate import validate_synastry_reader GUIDE = ( "你们这段关系的底色是有吸引,但节奏不同。刚靠近时,彼此容易觉得对方身上有自己缺的东西,所以会有自然的牵引。" "真正进入日常后,差异会浮出来:一个更习惯用行动表达关心,一个更需要语言和情绪回应;压力大的时候,一个想立刻解决,一个想先躲开。" "这不是谁对谁错,而是你们的安全感开关不在同一个位置。你们的优势在于,只要愿意把话说明白,理解力是在的;" "难点在于,情绪上来时容易把我需要空间听成你不在乎我。长期看,这段关系不是靠感觉自动顺滑的类型," "而是靠两个人把日常回应、冲突修复和现实安排一点点对齐。如果愿意练,它会长出很稳的默契;如果回避差异,摩擦会反复出现在同一个地方。" ) FILLER = "你们可以慢慢把彼此的节奏说清楚,把靠近、停顿和修复都留在两个人都能接受的距离里。" def _prose(seed: str) -> str: text = seed while len(text.replace(" ", "")) < 280: text += FILLER return text def _reason(plain: str, basis: str, evidence: str) -> dict: return { "plain": plain, "basis": basis, "evidence": evidence, "label": "single_system_inference", } def _passing(language: str = "zh") -> tuple[dict, dict]: evidence = "金星落在第七宫,同时木星大运覆盖接下来这一段" inputs = { section: {"quote": evidence, "note": "双方都需要把靠近说出来"} for section in ( "climate", "emotion", "communication", "safety", "conflict", "daily", "direction", "practices", ) } inputs["timeline"] = {"quote": "程序分好的三段"} reasons = [ _reason("所以你们靠近时会先感到彼此补上了一块缺口。", evidence, evidence), _reason("所以压力一大,一个想说开,一个想先留出空间。", evidence, evidence), ] titles = ("关系基调", "情感节奏", "沟通与理解", "安全感与亲密", "冲突与修复", "生活节律与现实协作", "长期方向") sections = [] for index, section_id in enumerate(("climate", "emotion", "communication", "safety", "conflict", "daily", "direction")): text = _prose(GUIDE if index == 0 else f"你们在{titles[index]}上需要彼此对齐。{GUIDE}") if language == "en": text = " ".join(["You two meet a different pace and can still name what each person needs."] * 90) sections.append({ "id": section_id, "title": titles[index], "text": text, "reasons": reasons if language == "zh" else [ _reason("So closeness asks both of you to say what care looks like.", "Venus in the 7th house during Jupiter dasha", evidence), _reason("So pressure sends one person toward talk and the other toward room.", "Venus in the 7th house during Jupiter dasha", evidence), ], }) if language == "en": evidence = "Venus in the 7th house during Jupiter dasha" for section_id, payload in inputs.items(): if section_id == "timeline": continue payload["quote"] = evidence for section in sections: if section["id"] == "timeline": continue for reason in section["reasons"]: reason["evidence"] = evidence reason["basis"] = evidence sections.append({ "id": "timeline", "segments": [ {"id": "s1", "phase": "靠近", "text": _prose("这段时间你们适合慢慢认识,把节奏说清楚。") if language == "zh" else "This stretch is for learning each other's pace. " * 20, "basis": evidence}, {"id": "s2", "phase": "对齐", "text": _prose("这段时间适合把日常安排摊开谈。") if language == "zh" else "This stretch is for naming daily arrangements. " * 20, "basis": evidence}, {"id": "s3", "phase": "稳住", "text": _prose("这段时间关系看你们是否把差异留在桌上。") if language == "zh" else "This stretch shows whether differences stay discussable. " * 20, "basis": evidence}, ], }) practice = "挑一件日常回应,这周各自说一次自己需要的靠近方式。" if language == "zh" else "Pick one daily response and each say what closeness you need." sections.append({ "id": "practices", "items": [{"text": practice, "evidence": evidence if language == "en" else "金星落在第七宫,同时木星大运覆盖接下来这一段"}], }) return {"sections": sections}, inputs def test_guide_example_passes_when_length_and_reasons_are_filled(): delivery, inputs = _passing("zh") result = validate_synastry_reader(delivery, inputs, "zh") assert result["ok"], result["failures"] def test_english_guide_shape_passes(): delivery, inputs = _passing("en") result = validate_synastry_reader(delivery, inputs, "en") assert result["ok"], result["failures"] def _one(delivery: dict, inputs: dict, language: str = "zh") -> set[str]: result = validate_synastry_reader(delivery, inputs, language) assert result["ok"] is False return {row["code"] for row in result["failures"]} def test_basis_house_that_is_not_in_the_evidence_fails(): delivery, inputs = _passing("zh") delivery["sections"][0]["reasons"][0]["basis"] = "金星落在第七宫,改成第八宫" assert "basis_mismatch" in _one(delivery, inputs) def test_each_banned_insertion_fails_once(): samples = { "金星": "banned_term", "七宫": "banned_term", "22 分": "score", "注定": "banned_judgement", "2027 年结婚": "dated_event", "宿曜": "supplementary_method", "execution_receipt": "private_term", } for snippet, code in samples.items(): delivery, inputs = _passing("zh") delivery["sections"][1]["text"] += snippet assert code in _one(delivery, inputs), snippet def test_analytic_opening_missing_section_and_missing_claim_fail(): delivery, inputs = _passing("zh") delivery["sections"][0]["text"] = "这段关系的核心不是顺滑。" + delivery["sections"][0]["text"] assert "analytic_opening" in _one(delivery, inputs) delivery, inputs = _passing("zh") delivery["sections"] = [row for row in delivery["sections"] if row["id"] != "safety"] assert "section_order" in _one(delivery, inputs) delivery, inputs = _passing("zh") delivery["sections"][2]["reasons"] = delivery["sections"][2]["reasons"][:1] assert "reason_count" in _one(delivery, inputs) def test_evidence_must_be_a_complete_field(): delivery, inputs = _passing("zh") delivery["sections"][0]["reasons"][0]["evidence"] = "金" assert "evidence_missing" in _one(delivery, inputs) def test_status_only_field_is_not_evidence(): delivery, inputs = _passing("zh") inputs["climate"]["status"] = "executed" delivery["sections"][0]["reasons"][0]["evidence"] = "executed" assert "evidence_missing" in _one(delivery, inputs) def test_d_chart_beside_chinese_fails(): delivery, inputs = _passing("zh") delivery["sections"][1]["text"] += "看D9" assert "banned_term" in _one(delivery, inputs) def test_timeline_phase_is_scanned(): delivery, inputs = _passing("zh") delivery["sections"][7]["segments"][0]["phase"] = "大运" assert "banned_term" in _one(delivery, inputs) def test_english_kuja_fails(): delivery, inputs = _passing("en") delivery["sections"][1]["text"] += " kuja " assert "banned_term" in _one(delivery, inputs, "en") def test_english_banned_word_fails(): delivery, inputs = _passing("en") delivery["sections"][1]["text"] += " Venus " assert "banned_term" in _one(delivery, inputs, "en") delivery, inputs = _passing("en") delivery["sections"][0]["text"] = "The core of this relationship is not ease. " + delivery["sections"][0]["text"] assert "analytic_opening" in _one(delivery, inputs, "en")