docs(tasks): marriage match follows upstream bde5d615 (35-point core, order-independent)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014Ad9rqAB8pXXvyzh7f4X8v
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
bafa296829
commit
c4c983cf2a
+66
-10
@@ -137,7 +137,10 @@ def _reference_sign(chart: Mapping[str, Any], reference: str) -> Optional[int]:
|
||||
if value is not None:
|
||||
return int(value // 30)
|
||||
if reference == "lagna":
|
||||
return _sign_index(chart.get("ascendant")) or _sign_index(chart.get("lagna"))
|
||||
# Aries is index 0, which is a valid reference sign rather than a
|
||||
# missing value. Do not use boolean fallback here.
|
||||
ascendant_sign = _sign_index(chart.get("ascendant"))
|
||||
return ascendant_sign if ascendant_sign is not None else _sign_index(chart.get("lagna"))
|
||||
return _sign_index(_planet_data(chart, "Moon" if reference == "moon" else "Venus"))
|
||||
|
||||
|
||||
@@ -296,7 +299,8 @@ def compare_kuja(person_a: Mapping[str, Any], person_b: Mapping[str, Any], profi
|
||||
|
||||
|
||||
def _moon_lon(chart: Mapping[str, Any]) -> Optional[float]:
|
||||
return _planet_lon(chart, "Moon") or _lon(chart.get("moon_lon"))
|
||||
longitude = _planet_lon(chart, "Moon")
|
||||
return longitude if longitude is not None else _lon(chart.get("moon_lon"))
|
||||
|
||||
|
||||
def _are_natural_friends(lord_a: str, lord_b: str) -> bool:
|
||||
@@ -305,44 +309,96 @@ def _are_natural_friends(lord_a: str, lord_b: str) -> bool:
|
||||
return lord_b in FRIENDSHIP.get(lord_a, {}).get("friend", []) and lord_a in FRIENDSHIP.get(lord_b, {}).get("friend", [])
|
||||
|
||||
|
||||
def _ashtakoota_with_exceptions(person_a: Mapping[str, Any], person_b: Mapping[str, Any]) -> Dict[str, Any]:
|
||||
def _ashtakoota_with_exceptions(
|
||||
person_a: Mapping[str, Any],
|
||||
person_b: Mapping[str, Any],
|
||||
) -> Dict[str, Any]:
|
||||
moon_a, moon_b = _moon_lon(person_a), _moon_lon(person_b)
|
||||
if moon_a is None or moon_b is None:
|
||||
return {"status": "not_available", "reason": "Both Moon longitudes are required for Ashtakoota."}
|
||||
raw = calculate_ashtakoot(moon_a, moon_b)
|
||||
forward = calculate_ashtakoot(moon_a, moon_b)
|
||||
reverse = calculate_ashtakoot(moon_b, moon_a)
|
||||
# The historical Varna point encodes caste hierarchy. It cannot truthfully
|
||||
# be relabelled as a modern value judgement, so the international product
|
||||
# excludes it rather than deriving a replacement from lunar signs.
|
||||
score_names = ("Vashya", "Tara", "Yoni", "GrahaMaitri", "Gana", "Bhakoot", "Nadi")
|
||||
symmetric_scores = {
|
||||
name: round(
|
||||
(float(forward["scores"][name]) + float(reverse["scores"][name])) / 2.0,
|
||||
2,
|
||||
)
|
||||
for name in score_names
|
||||
}
|
||||
international_core = {
|
||||
"method": "International Ashtakoota core v1 (both input orders averaged; classical Varna excluded)",
|
||||
"scores": symmetric_scores,
|
||||
"total_score": round(sum(symmetric_scores.values()), 2),
|
||||
"max_score": 35.0,
|
||||
"match_percentage": round(sum(symmetric_scores.values()) / 35.0 * 100, 1),
|
||||
"person_a_details": {
|
||||
"moon_sign": SIGNS[int(moon_a // 30)],
|
||||
"nakshatra": get_nakshatra(moon_a)["name"],
|
||||
"nakshatra_pada": get_nakshatra(moon_a)["pada"],
|
||||
},
|
||||
"person_b_details": {
|
||||
"moon_sign": SIGNS[int(moon_b // 30)],
|
||||
"nakshatra": get_nakshatra(moon_b)["name"],
|
||||
"nakshatra_pada": get_nakshatra(moon_b)["pada"],
|
||||
},
|
||||
"directionality": {
|
||||
"status": "symmetric",
|
||||
"rule": "all included Kutas are averaged across person_a -> person_b and person_b -> person_a",
|
||||
},
|
||||
"boundary": "This is a mutual-reference score, not a caste hierarchy or an automatic relationship verdict.",
|
||||
}
|
||||
sign_a, sign_b = SIGNS[int(moon_a // 30)], SIGNS[int(moon_b // 30)]
|
||||
nak_a, nak_b = get_nakshatra(moon_a), get_nakshatra(moon_b)
|
||||
bhakoot_exceptions = []
|
||||
if raw["scores"]["Bhakoot"] == 0:
|
||||
if symmetric_scores["Bhakoot"] == 0:
|
||||
lord_a, lord_b = SIGN_LORDS[sign_a], SIGN_LORDS[sign_b]
|
||||
if lord_a == lord_b:
|
||||
bhakoot_exceptions.append("same_sign_lord")
|
||||
elif _are_natural_friends(lord_a, lord_b):
|
||||
bhakoot_exceptions.append("mutual_natural_friend_sign_lords")
|
||||
nadi_exceptions = []
|
||||
if raw["scores"]["Nadi"] == 0:
|
||||
if symmetric_scores["Nadi"] == 0:
|
||||
if nak_a["index"] == nak_b["index"] and nak_a["pada"] != nak_b["pada"]:
|
||||
nadi_exceptions.append("same_nakshatra_different_pada")
|
||||
if sign_a == sign_b and nak_a["index"] != nak_b["index"]:
|
||||
nadi_exceptions.append("same_rashi_different_nakshatra")
|
||||
return {
|
||||
"status": "executed",
|
||||
"raw_36_point": raw,
|
||||
"international_core": international_core,
|
||||
"shared_life_dialogue": {
|
||||
"status": "not_scored",
|
||||
"storage": "no_answers_collected",
|
||||
"prompts": [
|
||||
"What does a committed relationship mean to each of you?",
|
||||
"How do you want to make decisions about money, home, and care responsibilities?",
|
||||
"What boundaries, privacy expectations, and conflict-repair habits matter most?",
|
||||
"How do you each imagine work, family, location, and personal autonomy over the next few years?",
|
||||
],
|
||||
"boundary": "These are voluntary discussion prompts, not a compatibility test or a score.",
|
||||
},
|
||||
"classical_varna": {
|
||||
"status": "excluded_from_default",
|
||||
"reason": "Historical caste hierarchy is not used in the international product or its default score.",
|
||||
},
|
||||
"person_a": {"moon_sign": sign_a, "nakshatra": nak_a},
|
||||
"person_b": {"moon_sign": sign_b, "nakshatra": nak_b},
|
||||
"exception_review": {
|
||||
"bhakoot": {
|
||||
"raw_score": raw["scores"]["Bhakoot"],
|
||||
"raw_score": symmetric_scores["Bhakoot"],
|
||||
"candidate_cancellations": bhakoot_exceptions,
|
||||
"status": "candidate_mitigation" if bhakoot_exceptions else "none",
|
||||
},
|
||||
"nadi": {
|
||||
"raw_score": raw["scores"]["Nadi"],
|
||||
"raw_score": symmetric_scores["Nadi"],
|
||||
"candidate_cancellations": nadi_exceptions,
|
||||
"status": "candidate_mitigation" if nadi_exceptions else "none",
|
||||
},
|
||||
"yoni": {
|
||||
"raw_score": raw["scores"]["Yoni"],
|
||||
"raw_score": symmetric_scores["Yoni"],
|
||||
"status": "legacy_three_band_table",
|
||||
"boundary": "The legacy 4/2/0 table remains visible. Intermediate 3/1 grades require a separately versioned source table before becoming a default score.",
|
||||
},
|
||||
+17
-8
@@ -9,18 +9,25 @@ which person is labelled male or female.
|
||||
|
||||
## Product Layers
|
||||
|
||||
1. **Ashtakoota (36 points)**: retain the raw Moon-based score and each Kuta.
|
||||
It is one compatibility lens, not a relationship decision.
|
||||
2. **Exception review**: show Bhakoot and Nadi cancellation candidates without
|
||||
1. **International Ashtakoota core (35 points)**: calculate both input orders
|
||||
and average every included Kuta before display, so swapping two people
|
||||
cannot change the result. The historical `Varna` point is excluded, not
|
||||
renamed: it encodes caste hierarchy and cannot truthfully be inferred as a
|
||||
modern value judgement from lunar signs. It remains one compatibility lens,
|
||||
not a relationship decision.
|
||||
2. **Shared-life dialogue (not scored)**: the product offers voluntary prompts
|
||||
about commitment, money, home, care, boundaries, and autonomy. It neither
|
||||
collects answers nor turns them into a compatibility score.
|
||||
3. **Exception review**: show Bhakoot and Nadi cancellation candidates without
|
||||
silently changing the raw score. Yoni remains visibly marked as a legacy
|
||||
three-band table until a versioned 14x14 intermediate-score source table is
|
||||
admitted.
|
||||
3. **Kuja / Mangal Dosha**: Mars only, inspected independently from Lagna,
|
||||
4. **Kuja / Mangal Dosha**: Mars only, inspected independently from Lagna,
|
||||
Moon, and Venus. The product exposes every reference-frame placement and
|
||||
mitigation rather than creating a percentage score.
|
||||
4. **Papasamya**: Mars, Saturn, Sun, Rahu, and Ketu are reported as a separate
|
||||
5. **Papasamya**: Mars, Saturn, Sun, Rahu, and Ketu are reported as a separate
|
||||
pressure layer. It is never labelled Kuja Dosha.
|
||||
5. **Relationship structure and timing**: D1, D9, UL, DK, and relationship
|
||||
6. **Relationship structure and timing**: D1, D9, UL, DK, and relationship
|
||||
Dasha are input-dependent. Missing data is reported as unavailable rather
|
||||
than replaced with an inferred result.
|
||||
|
||||
@@ -70,10 +77,12 @@ and input-dependent layers.
|
||||
|
||||
## Customer Presentation Rules
|
||||
|
||||
- Show the 36-point total as a compact summary, with the eight component rows.
|
||||
- Keep the 35-point Moon/nakshatra reference layer out of the headline; show it
|
||||
only as compact supporting evidence when the user requests the traditional
|
||||
component rows.
|
||||
- Render Kuja and Papasamya as separate cards with their selected profile.
|
||||
- State that an active signal is neither a diagnosis nor a prediction.
|
||||
- Do not make an automatic approval/rejection from 36 points, Kuja, or
|
||||
- Do not make an automatic approval/rejection from the matching layer, Kuja, or
|
||||
Papasamya alone.
|
||||
- If a requested D1/D9/UL/DK/timing layer was not supplied, show it as missing
|
||||
instead of suppressing the section.
|
||||
+63
@@ -66,6 +66,69 @@ def test_bhakoot_and_nadi_exceptions_are_exposed_without_rewriting_raw_score():
|
||||
assert "same_nakshatra_different_pada" in nadi["candidate_cancellations"]
|
||||
|
||||
|
||||
def test_international_core_is_order_independent_and_excludes_caste_point():
|
||||
a = chart(0, 30, 60, 180)
|
||||
b = chart(90, 120, 150, 270)
|
||||
forward = build_marriage_match_dossier(a, b)["ashtakoota_36"]["international_core"]
|
||||
reverse = build_marriage_match_dossier(b, a)["ashtakoota_36"]["international_core"]
|
||||
assert forward["total_score"] == reverse["total_score"]
|
||||
assert forward["scores"] == reverse["scores"]
|
||||
assert forward["max_score"] == 35.0
|
||||
assert "ValuesOrientation" not in forward["scores"]
|
||||
assert "Varna" not in forward["scores"]
|
||||
dialogue = build_marriage_match_dossier(a, b)["ashtakoota_36"]["shared_life_dialogue"]
|
||||
assert dialogue["status"] == "not_scored"
|
||||
|
||||
|
||||
def test_reported_directional_reproduction_is_order_independent_without_varna():
|
||||
# The historical directional calculation yields 10 vs 9 when 45 and 200
|
||||
# degrees are swapped because it includes Varna. The product core omits
|
||||
# Varna and averages every included Kuta, so input order is immaterial.
|
||||
a = chart(0, 45, 80, 120)
|
||||
b = chart(90, 200, 170, 210)
|
||||
forward = build_marriage_match_dossier(a, b)["ashtakoota_36"]["international_core"]
|
||||
reverse = build_marriage_match_dossier(b, a)["ashtakoota_36"]["international_core"]
|
||||
assert forward["total_score"] == reverse["total_score"] == 9.0
|
||||
assert forward["scores"] == reverse["scores"]
|
||||
assert forward["directionality"]["status"] == "symmetric"
|
||||
|
||||
|
||||
def test_zero_degree_moon_is_a_valid_ashtakoota_input():
|
||||
result = build_marriage_match_dossier(chart(0, 0, 20, 180), chart(90, 120, 150, 270))
|
||||
assert result["ashtakoota_36"]["status"] == "executed"
|
||||
assert result["ashtakoota_36"]["international_core"]["person_a_details"]["moon_sign"] == "Aries"
|
||||
|
||||
|
||||
def test_aries_named_ascendant_is_a_valid_lagna_reference():
|
||||
# Aries is sign index 0 and must not be discarded by a boolean fallback.
|
||||
person_a = {"ascendant": "Aries", "planets": {"Mars": {"longitude": 120.0}}}
|
||||
person_b = {"ascendant": "Taurus", "planets": {"Mars": {"longitude": 240.0}}}
|
||||
kuja = build_marriage_match_dossier(person_a, person_b)["kuja_mangal_dosha"]
|
||||
lagna_frame = kuja["person_a"]["frames"][0]
|
||||
assert lagna_frame["reference"] == "lagna"
|
||||
assert lagna_frame["reference_sign"] == "Aries"
|
||||
assert lagna_frame["mars_house"] == 5
|
||||
assert lagna_frame["status"] == "not_triggered"
|
||||
|
||||
|
||||
def test_moon_only_quick_reference_stays_neutral_and_marks_missing_pressure_layers():
|
||||
result = build_marriage_match_dossier(
|
||||
{"planets": {"Moon": {"longitude": 0.0}}},
|
||||
{"planets": {"Moon": {"longitude": 120.0}}},
|
||||
)
|
||||
assert result["ashtakoota_36"]["status"] == "executed"
|
||||
assert result["ashtakoota_36"]["international_core"]["total_score"] == 18.5
|
||||
assert result["kuja_mangal_dosha"]["person_a"]["status"] == "not_available"
|
||||
assert result["kuja_mangal_dosha"]["person_b"]["status"] == "not_available"
|
||||
|
||||
|
||||
def test_shared_life_dialogue_is_not_a_score_or_questionnaire_store():
|
||||
dialogue = build_marriage_match_dossier(chart(0, 30, 60, 180), chart(90, 120, 150, 270))["ashtakoota_36"]["shared_life_dialogue"]
|
||||
assert dialogue["status"] == "not_scored"
|
||||
assert dialogue["storage"] == "no_answers_collected"
|
||||
assert dialogue["prompts"]
|
||||
|
||||
|
||||
def test_non_romantic_scope_is_rejected():
|
||||
with pytest.raises(ValueError, match="romantic/marriage"):
|
||||
build_marriage_match_dossier(chart(0, 1, 20, 180), chart(90, 4, 120, 270), relationship_type="business")
|
||||
@@ -501,4 +501,4 @@
|
||||
| `TASK-data-layer-types-20261013.md` | `PROGRESS-data-layer-types-20261013.md` | 数据访问层改用自己的 DataClient 类型第一阶段(BUG-1351;任务书预留的 1343 已被更大编号越过) | 待验收 | `codex/data-layer-types-20261013` |
|
||||
| `TASK-contracts-helpers-dedupe-20261013.md` | — | 前后端契约与小工具去重第一阶段 | 待领取(排队:frontend-dead-code、consult-route-split) | `codex/contracts-helpers-dedupe-20261013` |
|
||||
| `TASK-people-archive-limit-20-20261017.md` | — | 星盘档案上限 5→20 人(触发器新迁移 + 常量 + 文案;婚恋合盘前置单) | 待领取 | `codex/people-archive-limit-20-20261017`;动表,需 test:db |
|
||||
| `TASK-marriage-match-p1-20261017.md` | — | 婚恋合盘第一期:独立页、任意两人、直接搬上游占星师 marriage_match.py(3c01a214)、按报告扣点(BUG-1370 起) | 排队(等上限单合入) | `codex/marriage-match-p1-20261017`;动表 |
|
||||
| `TASK-marriage-match-p1-20261017.md` | — | 婚恋合盘第一期:独立页、任意两人、直接搬上游占星师 marriage_match.py(bde5d615,35 分七项、不分男女)、按报告扣点(BUG-1370 起) | 排队(等上限单合入) | `codex/marriage-match-p1-20261017`;动表 |
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
- `origin/staging` = `f95eb670b`(开工前 `git fetch origin --prune`,以最新 `origin/staging` 为准)
|
||||
- 分支:`codex/marriage-match-p1-20261017`;工作树:`.worktrees/marriage-match-p1-20261017`
|
||||
- **前置**:`TASK-people-archive-limit-20-20261017.md` 必须先合入 staging。两单都改 `components/people/people-page.tsx`,本单在它之后从最新 `origin/staging` 开分支。
|
||||
- 原型(v5,火星冲克按上游模块口径):https://claude.ai/artifact/Tq6vhXFMnGitDZhJdamaHe 。原型里的人名、分数、宫位全是虚构示例。
|
||||
- 原型(v6,35 分七项、火星冲克按上游模块口径):https://claude.ai/artifact/Tq6vhXFMnGitDZhJdamaHe 。原型里的人名、分数、宫位全是虚构示例。
|
||||
|
||||
## 现状实证(按符号定位)
|
||||
|
||||
@@ -34,31 +34,31 @@
|
||||
| D1 | 合盘做成独立功能:侧栏一项「**婚恋合盘**」,独立页面,页面标题「婚恋合盘」。档案里**任意两个人**都能合(包括两个都不是本人) |
|
||||
| D2 | 第一版**只做恋爱 / 婚姻**,不给选关系类型;旧的 `relationshipType`(business / family / general)分支删除 |
|
||||
| D3 | **一次合盘按一次报告扣点**(`report.full` 的定价)。同一对人、两人出生资料都没改、规则版本没变时,再次打开直接读已保存的结果,不扣点 |
|
||||
| D4 | 八项**逐项展示**,不做大号总分;历史列表不挂分数 |
|
||||
| D5 | **不考虑同性**:男方女方按档案性别定;两人性别相同或有一人没填时,按选择顺序第一个人当男方,不提示、不互换平均(推翻同日早先「互换取平均」的说法) |
|
||||
| D4 | 配对表**逐项展示**(第四版起为七项、满分 35,见 D12),不做大号总分;历史列表不挂分数 |
|
||||
| D5 | ~~按档案性别定男方~~ **第四版撤销**:上游已改成两个方向各算一次取平均,结果与顺序、性别都无关。本单**不读性别、不分男女方**,按选人顺序传 `person_a` / `person_b` 即可;页面不出现「男方 / 女方」 |
|
||||
| D6 | 副标题定稿:「看这段感情往后的走向,和两人之间的助力与考验。」页面任何地方不写「合不合」「会不会在一起」 |
|
||||
| D7 | 新增星盘入口放在两个选人下拉的**最底一行**「+ 新增星盘」,本页弹出与星盘档案同一张新建表,保存后自动选进这一格;档案满时这一行变灰 |
|
||||
| D8 | 「开始合盘」按钮居中,点数说明在按钮正下方 |
|
||||
| D9 | **引擎直接搬上游占星师的实现**(产品 2026-10-17 第三版):`yinduzhanxing` main `3c01a2148e82741dca62547a0ec1bab0b252766e` 的 `scripts/marriage_match.py`(默认 profile `practitioner_consensus_v1`,可选 `raman_muhurtha_v1`)原样搬入,口径以它为准。**推翻**本任务书前两版自写的规则:同日第一版的 `raman_muhurtha_v1` 细则,以及第二版自写的化解条件分档表、「未见 / 轻 / 明显」归档规则、Yoni 五档表。仍然推翻现有 `calc_kuja_dosha` 的五星合分与 `match_kuja_dosha` 的性别不对称(上游模块本身也不用它们) |
|
||||
| D9 | **引擎直接搬上游占星师的实现**(产品 2026-10-17 第三版):`yinduzhanxing` 提交 `bde5d615`(分支 `codex/personal-report-export-r9`;第三版曾固定 `3c01a214`,第四版改为此提交)的 `scripts/marriage_match.py`(默认 profile `practitioner_consensus_v1`,可选 `raman_muhurtha_v1`)原样搬入,口径以它为准。**推翻**本任务书前两版自写的规则:同日第一版的 `raman_muhurtha_v1` 细则,以及第二版自写的化解条件分档表、「未见 / 轻 / 明显」归档规则、Yoni 五档表。仍然推翻现有 `calc_kuja_dosha` 的五星合分与 `match_kuja_dosha` 的性别不对称(上游模块本身也不用它们) |
|
||||
| D10 | **规则有分歧时并列展示**:默认 profile 的结果在前;`raman_muhurtha_v1` 与默认结果不同的地方,用「按 Raman 一派:……」并列写出,不悄悄替换。化解条件按调研记录 `docs/research/marriage_match_practitioner_consensus_2026_10_17.md` 加上分档标签(只影响展示,不改上游结果) |
|
||||
| D11 | 第二期(本单不做、不留占位):两人大运同步「什么时候顺」、就这对关系开对话、导出报告、非婚恋关系 |
|
||||
| D12 | **第四版(产品 2026-10-18)跟随上游修复**:① 配对层改为上游的「国际化核心」——Varna(种姓)从默认结果里去掉,剩七项、满分 **35**;每项两个方向平均,会出现 0.5 分;② 本地不再有任何与上游不同的改动(0° 月亮、白羊上升两个 bug 上游已修);③ 「考验较多」的分数线按比例换算为 **低于 17.5 分**;④ 上游新增的四个「可以一起聊聊」的问题(不打分、不收集回答)放在结果页脚注前 |
|
||||
| D13 | 上游 main 10-17~18 新增的「完整合盘解读」流程(双语报告、D9 互动证据、章节覆盖门槛等 26 个提交)**不在本单范围**,留作第二期参考 |
|
||||
|
||||
### 引擎口径(上游 `marriage_match.py`,摘要)
|
||||
|
||||
| 层 | 上游行为(照搬) | 我们在外面加的 |
|
||||
| --- | --- | --- |
|
||||
| 36 分 | `calculate_ashtakoot(moon_a, moon_b)`,`person_a` 当男方。原始分不改;Bhakoot(同主星、互为朋友)与 Nadi(同星宿不同 pada、同星座不同星宿)只给 `candidate_cancellations` | **按档案性别把男方放 `person_a`**(D5:同性或缺性别按选择顺序)。页面表尾两个合计:原始合计、计入候选例外后的合计 |
|
||||
| 配对层(35 分) | `_ashtakoota_with_exceptions` 对 `calculate_ashtakoot` 正反各算一次,七项(互控 Vashya、星宿 Tara、性情 Yoni、月主友谊 GrahaMaitri、气质 Gana、月宫距离 Bhakoot、脉 Nadi)各自平均,结果在 `international_core`;Varna 标 `excluded_from_default`。Bhakoot(同主星、互为朋友)与 Nadi(同星宿不同 pada、同星座不同星宿)只给 `candidate_cancellations`,不改分 | 按选人顺序传入,不读性别。页面七行,分数按上游原样显示(含 .5),表尾两个合计:原始合计 / 35、计入候选例外后的合计 / 35;表下一行小字「传统第八项种姓(Varna)不计入」 |
|
||||
| 性情 Yoni | 保留旧 4 / 2 / 0 三档,标 `legacy_three_band_table`;中间档要等有正式出处的完整表 | 页面注明「沿用三档旧表,中间档待定」 |
|
||||
| 火星冲克 | 只看火星;从上升、月亮、金星三处;默认犯宫 1/2/4/7/8/12(Raman:2/4/7/8/12);宫 × 星座例外表直接取消该处;本垣 / 入旺、木星同宫、月亮同宫列为 mitigation,**不改变该处是否触发**;狮子 / 水瓶默认只标 disputed(Raman 下取消)。轻重 = 触发处数 0/1/2/3 → none / mild / material / strong;两人比较 = 处数差 ≤ 1 为 broadly_balanced,否则 asymmetric,不分男女 | 中文:无 / 轻 / 明显 / 重;相当 / 一方偏重。mitigation 中文名 + 分档标签:本垣、入旺、木星同宫(广泛公认),月亮同宫、宫 × 星座例外(常见),狮子 / 水瓶(有争议)。每处写「减轻,不改变这一处」 |
|
||||
| 婚姻宫压力 | 火星、土星、太阳、罗睺、计都,从三处数,用 profile 的宫位集;只列出 | 两人处数的一句比较(大致均衡 / 一方明显偏重,差 ≥ 3 处或两倍以上),不出加权分 |
|
||||
| 各自婚姻征象 | `relationship_structure` 只报告调用方传进来了哪些层(`d1_relationship`、`d9`、`upapada`、`darakaraka`、`relationship_dasha`),不计算 | 我们用 `relationship_analysis.analyze_relationship` 算 D1 第 7 宫主、金星、D9、UL、DK,作为快照字段传入;`relationship_dasha` 第一期不传(第二期) |
|
||||
| 输入 | 只收已算好的盘快照(上升、各行星经度),不收出生资料 | Next 侧先并行算两张盘(沿用 BUG-1333 的并行 + 跳过外部概览),再组快照调用 |
|
||||
|
||||
**结论措辞**(产品层,不进上游模块):落点三档「助力多于考验」「有几处需要留意,建议结合整盘细看」「考验较多,建议请占星师细看」。规则:火星冲克 `asymmetric`,或任一方「重」→ 至少第二档;同时原始 36 分低于 18 且火星冲克 `asymmetric` → 第三档;其余第一档。36 分只作一层参考。
|
||||
**结论措辞**(产品层,不进上游模块):落点三档「助力多于考验」「有几处需要留意,建议结合整盘细看」「考验较多,建议请占星师细看」。规则:火星冲克 `asymmetric`,或任一方「重」→ 至少第二档;同时配对层原始合计低于 **17.5**(满分 35)且火星冲克 `asymmetric` → 第三档;其余第一档。配对分只作一层参考。
|
||||
|
||||
**上游两个已知 bug(10-17 已报给占星师)**,搬入时在本地修掉,并在 PROGRESS 里记成「与上游差异」,等上游修好后再对齐:
|
||||
1. 结果依赖 A / B 顺序(种姓等有方向的项);我们靠「男方放 `person_a`」规避,**不改上游算法**。
|
||||
2. `_moon_lon`、`_reference_sign` 里的 `or` 把 0 当缺失:月亮在 0° 时 36 分 `not_available`;上升只给星座名「Aries」时上升一处 `not_available`。本地改成显式 `is None` 判断,各补一条测试。
|
||||
**上游两个 bug(10-17 报给占星师,10-18 已在 `bde5d615` 修复,Claude 复测通过)**:顺序依赖已由「两个方向取平均」消除(随机 3000 对对调结果全一致);`or` 把 0 当缺失已改为显式 `is None`,上游各有回归测试。本单**原样搬入,不做本地改动**;若上游 main 之后有新修复,验收时由 Claude 决定是否同步。
|
||||
|
||||
## 硬红线
|
||||
|
||||
@@ -76,13 +76,13 @@
|
||||
|
||||
| # | 内容 | 验收标准 |
|
||||
| --- | --- | --- |
|
||||
| T1 | **引擎**:把上游 `scripts/marriage_match.py`(`3c01a214`,本仓副本在 `docs/research/upstream_marriage_match_3c01a214/`)原样搬入,只做「已知 bug 2」的本地修复;搬入上游 `tests/test_marriage_match_product.py`。新增本地胶水模块(建议 `scripts/marriage_match_snapshot.py`):两份已算好的盘 + 性别 → 按 D5 定 `person_a` / `person_b` → 用 `relationship_analysis.analyze_relationship` 补各自婚姻征象快照 → 默认与 Raman 两个 profile 各调一次 `build_marriage_match_dossier` | 上游 6 条测试全过;新增:0° 月亮、白羊上升(星座名)各一条;男女顺序按性别(女方选在第一格时仍放 `person_b`);两个 profile 结果都在;`diff` 上游文件只剩 bug 2 的改动(写进 PROGRESS) |
|
||||
| T1 | **引擎**:把上游 `scripts/marriage_match.py`(`bde5d615`,本仓副本在 `docs/research/upstream_marriage_match_bde5d615/`)与 `tests/test_marriage_match_product.py` 原样搬入,**不做任何改动**(`scripts/ashtakoot.py` 与上游相同,不动)。新增本地胶水模块(建议 `scripts/marriage_match_snapshot.py`):两份已算好的盘 → 按选人顺序作 `person_a` / `person_b` → 用 `relationship_analysis.analyze_relationship` 补各自婚姻征象快照 → 默认与 Raman 两个 profile 各调一次 `build_marriage_match_dossier` | 上游 12 条测试全过(`test_api_handler_*` 一条若依赖上游接口形状,按我们 T2 的薄注册改写并写三栏说明);`diff` 上游两文件为空(写进 PROGRESS);新增:胶水模块两人对调后配对层、火星冲克、婚姻宫压力结果一致;两个 profile 结果都在 |
|
||||
| T2 | **Python 接口**:`POST /api/marriage-match`(与上游同名)薄注册,在独立模块里实现,`jyotish_api_server.py` 只加注册行;旧 `/api/synastry` 保留给 MCP / CLI 兼容但不再被网页调用;CLI 加 `marriage-match` 命令(与上游参数一致) | `tests/test_api_server_growth_contract.py` 绿;端点 golden 一份进 `frontend/tests/fixtures/` |
|
||||
| T3 | **退役与文档纠错**:删 `scripts/synastry.py`,CLI `synastry` 改走新模块或删除(同步 `SKILL.md` 命令表、`tests/run_all.py`、`tests/test_open_source_integrations.py`);`ashtakoot.py` 的 `calc_kuja_dosha` / `match_kuja_dosha` 标为旧版、不被新功能调用;删掉三处 Papasamya / Mangal 匹配虚报 | 全量 pytest 与基线逐条比对无新增失败;`git grep "synastry.calc_ashtakoot"` 0 命中 |
|
||||
| T4 | **数据**:`synastry_reports` 加列——两人的人物引用(本人用约定值,他人用 `chart_profiles.id`)、两人出生资料指纹、规则版本 `rules_version`、结构化结果;旧行不迁移、不显示 | `npm run test:db`:同一对人同指纹第二次读到旧行;任一人改出生资料后指纹不同;删人后其合盘行消失 |
|
||||
| T5 | **Next 接口**:重写 `app/api/synastry/route.ts`(或新路由,旧的删):收两个人物 id → `loadSubjectBirth` 两次 → 查已保存(命中即返回,不扣点)→ 预留 `report.full` → 调 T2 → 存 → 结算;性别按 D5;保留产品开关 `compatibility` 与限流桶;删掉 `relationshipType` 与 business / family / general 分支 | 路由测试覆盖:命中缓存不扣点;计算失败释放不扣;两个 id 相同 400;不属于本人的 id 拒绝;同性 / 缺性别按顺序定男方 |
|
||||
| T6 | **页面**:侧栏「婚恋合盘」;`/marriage-match`(路由名可议,写进 DESIGN)选人页(两个自定义下拉,底行「+ 新增星盘」复用星盘档案新建表;居中按钮;点数来自定价不写死;之前的合盘列表只列「人名 × 人名 · 日期」,点开不扣点);结果页按原型 v5:三句话总述 → 各自的婚姻征象 → 八项配对(含例外绿字 / 另一派灰字、两个合计)→ 火星冲克(每人逐处列出「从哪里数、落第几宫、是否触发」,减轻条件带分档标签并注明「不改变这一处」;Raman 一派不同时并列一句;两人对比)→ 婚姻宫压力 → 脚注(口径来源、分档含义、「计算层归纳不是定论」) | 渲染测试:两个状态(两人火星冲克相当 / 一方明显)的 golden 渲染;20 人时下拉可滚动;手机 390 px 无横向滚动;`next build` 后 `/` 仍 Static,首屏 gzip ±2% |
|
||||
| T7 | **三句话总述**:由服务端按结构化结果确定性拼句(不调模型),讲各自征象、相处的助力与考验、八项合计只作一层参考;落点只用「结论措辞」的三档,第二、三档讲清原因 | 单测覆盖两种状态;文案进 VOICE |
|
||||
| T5 | **Next 接口**:重写 `app/api/synastry/route.ts`(或新路由,旧的删):收两个人物 id → `loadSubjectBirth` 两次 → 查已保存(命中即返回,不扣点)→ 预留 `report.full` → 调 T2 → 存 → 结算;不读性别(D5);保留产品开关 `compatibility` 与限流桶;删掉 `relationshipType` 与 business / family / general 分支 | 路由测试覆盖:命中缓存不扣点;计算失败释放不扣;两个 id 相同 400;不属于本人的 id 拒绝;两人对调后结果一致 |
|
||||
| T6 | **页面**:侧栏「婚恋合盘」;`/marriage-match`(路由名可议,写进 DESIGN)选人页(两个自定义下拉,底行「+ 新增星盘」复用星盘档案新建表;居中按钮;点数来自定价不写死;之前的合盘列表只列「人名 × 人名 · 日期」,点开不扣点);结果页按原型 v5:三句话总述 → 各自的婚姻征象 → 七项配对(满分 35,含例外绿字 / 另一派灰字、两个合计、「种姓项不计入」小字)→ 火星冲克(每人逐处列出「从哪里数、落第几宫、是否触发」,减轻条件带分档标签并注明「不改变这一处」;Raman 一派不同时并列一句;两人对比)→ 婚姻宫压力 → 「可以一起聊聊」四问(D12④,上游 `shared_life_dialogue.prompts` 译成中文,不收集回答)→ 脚注(口径来源、分档含义、「计算层归纳不是定论」) | 渲染测试:两个状态(两人火星冲克相当 / 一方明显)的 golden 渲染;20 人时下拉可滚动;手机 390 px 无横向滚动;`next build` 后 `/` 仍 Static,首屏 gzip ±2% |
|
||||
| T7 | **三句话总述**:由服务端按结构化结果确定性拼句(不调模型),讲各自征象、相处的助力与考验、配对合计(满分 35)只作一层参考;落点只用「结论措辞」的三档,第二、三档讲清原因 | 单测覆盖两种状态;文案进 VOICE |
|
||||
| T8 | **清旧入口**:档案页「和我合盘」改跳新页并预选两人;删 `SynastryDeepLink`、`use-synastry.ts` 中无人用的部分、`buildSynastryQuestion` 草稿路径、孤儿 CSS、`page.tsx` 开机拉合盘历史 | 按 AGENTS §7.8 grep 源码文本合同测试同轮改,三栏说明 |
|
||||
| T9 | **记录**:调研记录已由 Claude 写好,执行方只需在 PROGRESS 引用;DESIGN.md、VOICE.md(新文案、删人确认句、侧栏名)、CHANGELOG、BUG_HISTORY(下节编号)、`docs/testing/marriage-match-p1-20261017.md` 真人清单(选人、新增星盘、扣点与再次打开不扣、删人连带删除、手机宽) | 文档齐 |
|
||||
|
||||
@@ -99,8 +99,8 @@ cd .worktrees/marriage-match-p1-20261017
|
||||
git log --oneline -5 -- frontend/src/lib/subject-birth.ts # 确认上限 20 人那单已在
|
||||
grep -n "BUG-1[0-9][0-9][0-9]" docs/BUG_HISTORY.md | tail -3
|
||||
python3 scripts/pre_work_check.py --remote-timeout 8 --command-timeout 45
|
||||
# 上游文件(上游仓库私有,已原样放进本仓只读参照目录,固定到 3c01a214)
|
||||
ls docs/research/upstream_marriage_match_3c01a214/
|
||||
# 上游文件(上游仓库私有,已原样放进本仓只读参照目录,固定到 bde5d615)
|
||||
ls docs/research/upstream_marriage_match_bde5d615/
|
||||
# marriage_match.py.txt → scripts/marriage_match.py;test_marriage_match_product.py.txt → tests/test_marriage_match_product.py
|
||||
```
|
||||
|
||||
@@ -112,7 +112,7 @@ ls docs/research/upstream_marriage_match_3c01a214/
|
||||
|
||||
| 建议编号 | 事实 |
|
||||
| --- | --- |
|
||||
| BUG-1370 | 合盘不看性别,本人恒为男方(实证 3) |
|
||||
| BUG-1370 | 合盘结果依赖顺序,本人恒为男方(实证 3;修法:改为对调平均、不分男女) |
|
||||
| BUG-1371 | 网页合盘从不计算火星冲克(实证 4) |
|
||||
| BUG-1372 | 旧 `synastry.py` 互控写死 2.0 / 星宿下标越界(实证 7) |
|
||||
| BUG-1373 | 结果卡随旧面板删除,合盘只剩聊天草稿(实证 2) |
|
||||
|
||||
Reference in New Issue
Block a user