feat: add active rectification questionnaire
This commit is contained in:
@@ -31,6 +31,34 @@
|
|||||||
- [jyotish-app/rectification-engine.js](<repo>/jyotish-app/rectification-engine.js)
|
- [jyotish-app/rectification-engine.js](<repo>/jyotish-app/rectification-engine.js)
|
||||||
- [references/varga-system-quick-reference.md](<repo>/references/varga-system-quick-reference.md)
|
- [references/varga-system-quick-reference.md](<repo>/references/varga-system-quick-reference.md)
|
||||||
|
|
||||||
|
## 1.1 产品化问询原则
|
||||||
|
|
||||||
|
生时校正不应默认要求用户先列出 8-15 个事件。普通用户路径优先采用主动问询:
|
||||||
|
|
||||||
|
```text
|
||||||
|
候选时间扫描
|
||||||
|
→ 分盘 / Arudha / KP cusp / Dasha 敏感差异提取
|
||||||
|
→ 生成高信息量选择题
|
||||||
|
→ 用户回答 A/B/C/D
|
||||||
|
→ 动态缩小候选时间簇
|
||||||
|
→ 进入下一轮追问
|
||||||
|
```
|
||||||
|
|
||||||
|
用户只需回答:
|
||||||
|
|
||||||
|
```text
|
||||||
|
A. 明确有,且时间大致吻合
|
||||||
|
B. 有类似,但时间略偏或不够重大
|
||||||
|
C. 没有明显发生
|
||||||
|
D. 不确定 / 不记得
|
||||||
|
```
|
||||||
|
|
||||||
|
机器可审计入口:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python3 scripts/active_rectification_questions.py --birth-time "1955-02-24 19:15" --uncertainty-minutes 30 --pretty
|
||||||
|
```
|
||||||
|
|
||||||
## 2. 第一层:主引擎
|
## 2. 第一层:主引擎
|
||||||
|
|
||||||
### Dasha + dated events
|
### Dasha + dated events
|
||||||
|
|||||||
@@ -0,0 +1,99 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Generate active-choice birth-time rectification questions."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import json
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
OPTIONS = [
|
||||||
|
{"key": "A", "label": "明确有,且时间大致吻合", "score": 2},
|
||||||
|
{"key": "B", "label": "有类似,但时间略偏或不够重大", "score": 1},
|
||||||
|
{"key": "C", "label": "没有明显发生", "score": -2},
|
||||||
|
{"key": "D", "label": "不确定 / 不记得", "score": 0},
|
||||||
|
]
|
||||||
|
|
||||||
|
QUESTION_TEMPLATES = [
|
||||||
|
("education_environment_shift", 1, "education", ["D24", "D4", "Dasha"], "age_16_to_18", "16-18岁附近,是否有明显学业、学校、专业方向或学习环境变化?", "middle_candidate_cluster", "against_D24_sensitive_cluster"),
|
||||||
|
("residence_relocation_shift", 1, "residence", ["D4", "12H", "Rahu/Ketu", "Transit"], "age_20_to_24", "20-24岁附近,是否有搬家、离乡、长期异地、住宿或居住结构变化?", "D4_relocation_cluster", "against_D4_relocation_cluster"),
|
||||||
|
("relationship_or_partner_entry", 1, "relationship", ["D9", "UL", "A7", "7H"], "age_21_to_26", "21-26岁附近,是否有关系对象进入、关系断裂、暧昧升级或关系观明显转变?", "D9_UL_A7_cluster", "against_relationship_cluster"),
|
||||||
|
("career_responsibility_pressure", 1, "career", ["D10", "A10", "Saturn", "10H"], "age_26_to_30", "26-30岁附近,是否有责任增加、合作压力、工作结构变化或长期压力阶段?", "D10_A10_saturn_cluster", "against_career_pressure_cluster"),
|
||||||
|
("research_tool_expression_shift", 1, "career_learning", ["D10", "D24", "Mercury", "A10"], "recent_three_years", "近三年是否明显进入写作、技术、系统化学习、工具搭建、内容表达、AI/研究类方向?", "Mercury_D24_A10_cluster", "against_learning_expression_cluster"),
|
||||||
|
("health_crisis_or_low_period", 2, "health_pressure", ["D30", "6H", "8H", "Saturn/Mars"], "largest_pressure_window", "某个压力窗口附近,是否有健康、事故、低谷、睡眠/精神压力或身体负担明显阶段?", "D30_crisis_cluster", "against_D30_crisis_cluster"),
|
||||||
|
("public_role_or_project_visibility", 2, "public_work", ["A10", "D10", "AmK", "Karakamsha"], "career_visibility_window", "某个事业窗口附近,是否有项目公开、作品产出、职位/身份变化或被他人看见的机会?", "A10_public_visibility_cluster", "against_A10_cluster"),
|
||||||
|
("sequence_inner_vs_outer", 3, "fine_timing", ["KP_cusp", "Pratyantar", "Dasha_boundary"], "top_candidate_window", "关键变化更像先有内在转向、后有外部结果,还是几乎同时发生?", "fine_boundary_cluster", "neutral"),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def _parse_time(value: str) -> datetime:
|
||||||
|
return datetime.strptime(value, "%Y-%m-%d %H:%M")
|
||||||
|
|
||||||
|
|
||||||
|
def _candidate_scan(center: datetime, uncertainty_minutes: int, step_minutes: int) -> dict[str, Any]:
|
||||||
|
start = center - timedelta(minutes=uncertainty_minutes)
|
||||||
|
end = center + timedelta(minutes=uncertainty_minutes)
|
||||||
|
return {
|
||||||
|
"start": start.strftime("%Y-%m-%d %H:%M"),
|
||||||
|
"end": end.strftime("%Y-%m-%d %H:%M"),
|
||||||
|
"step_minutes": step_minutes,
|
||||||
|
"candidate_count": int((end - start).total_seconds() // 60 // step_minutes) + 1,
|
||||||
|
"cluster_labels": ["early_candidate_cluster", "middle_candidate_cluster", "late_candidate_cluster"],
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def build_questionnaire(birth_time: str, uncertainty_minutes: int = 30, step_minutes: int = 1) -> dict[str, Any]:
|
||||||
|
questions = []
|
||||||
|
for qid, round_id, domain, sensitivity, window, prompt, yes_bias, no_bias in QUESTION_TEMPLATES:
|
||||||
|
questions.append({
|
||||||
|
"id": qid,
|
||||||
|
"round": round_id,
|
||||||
|
"domain": domain,
|
||||||
|
"sensitivity": sensitivity,
|
||||||
|
"window": window,
|
||||||
|
"prompt": prompt,
|
||||||
|
"options": OPTIONS,
|
||||||
|
"scoring_map": {
|
||||||
|
"A": {"effect": "support", "cluster": yes_bias, "points": 2},
|
||||||
|
"B": {"effect": "weak_support", "cluster": yes_bias, "points": 1},
|
||||||
|
"C": {"effect": "exclude_or_penalize", "cluster": no_bias, "points": -2},
|
||||||
|
"D": {"effect": "neutral", "cluster": "neutral", "points": 0},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
return {
|
||||||
|
"scope": "active_birth_time_rectification_questionnaire",
|
||||||
|
"schema_version": 1,
|
||||||
|
"candidate_scan": _candidate_scan(_parse_time(birth_time), uncertainty_minutes, step_minutes),
|
||||||
|
"workflow": [
|
||||||
|
"candidate_time_scan",
|
||||||
|
"varga_arudha_kp_sensitivity_diff",
|
||||||
|
"high_information_question_generation",
|
||||||
|
"multiple_choice_user_answers",
|
||||||
|
"dynamic_candidate_cluster_scoring",
|
||||||
|
"next_round_question_selection",
|
||||||
|
],
|
||||||
|
"rounds": {
|
||||||
|
"1": "coarse screen",
|
||||||
|
"2": "domain follow-up",
|
||||||
|
"3": "fine confirmation",
|
||||||
|
},
|
||||||
|
"sensitivity_layers": ["D9", "D10", "D24", "D30", "D60", "D4", "UL", "A7", "A10", "KP_cusp", "Vimshottari", "Narayana", "Chara"],
|
||||||
|
"questions": questions,
|
||||||
|
"boundary": "Question generation only; final rectification requires scoring answers against actual candidate chart differences.",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
parser = argparse.ArgumentParser(description=__doc__)
|
||||||
|
parser.add_argument("--birth-time", required=True, help="Approximate local birth time, YYYY-MM-DD HH:MM")
|
||||||
|
parser.add_argument("--uncertainty-minutes", type=int, default=30)
|
||||||
|
parser.add_argument("--step-minutes", type=int, default=1)
|
||||||
|
parser.add_argument("--pretty", action="store_true")
|
||||||
|
args = parser.parse_args()
|
||||||
|
print(json.dumps(build_questionnaire(args.birth_time, args.uncertainty_minutes, args.step_minutes), ensure_ascii=False, indent=2 if args.pretty else None))
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
raise SystemExit(main())
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from scripts.active_rectification_questions import build_questionnaire
|
||||||
|
|
||||||
|
|
||||||
|
def test_active_rectification_questions_generate_choice_based_workflow() -> None:
|
||||||
|
report = build_questionnaire("1955-02-24 19:15", uncertainty_minutes=30)
|
||||||
|
|
||||||
|
assert report["scope"] == "active_birth_time_rectification_questionnaire"
|
||||||
|
assert report["candidate_scan"]["start"] == "1955-02-24 18:45"
|
||||||
|
assert report["candidate_scan"]["end"] == "1955-02-24 19:45"
|
||||||
|
assert report["candidate_scan"]["candidate_count"] == 61
|
||||||
|
assert "high_information_question_generation" in report["workflow"]
|
||||||
|
assert {"D9", "D10", "D24", "D30", "UL", "A10", "KP_cusp"} <= set(report["sensitivity_layers"])
|
||||||
|
assert len(report["questions"]) >= 8
|
||||||
|
assert {q["round"] for q in report["questions"]} == {1, 2, 3}
|
||||||
|
assert all({option["key"] for option in q["options"]} == {"A", "B", "C", "D"} for q in report["questions"])
|
||||||
|
assert all("scoring_map" in q for q in report["questions"])
|
||||||
@@ -21,6 +21,8 @@ def test_rectification_decision_tree_doc_exists_and_covers_priority_layers() ->
|
|||||||
assert "D60" in text
|
assert "D60" in text
|
||||||
assert "不是所有分盘一股脑上" in text
|
assert "不是所有分盘一股脑上" in text
|
||||||
assert "Dasha 定框,D9/D10 定核心" in text
|
assert "Dasha 定框,D9/D10 定核心" in text
|
||||||
|
assert "主动问询" in text
|
||||||
|
assert "scripts/active_rectification_questions.py" in text
|
||||||
|
|
||||||
|
|
||||||
def test_rectification_decision_tree_doc_maps_event_groups_to_vargas() -> None:
|
def test_rectification_decision_tree_doc_maps_event_groups_to_vargas() -> None:
|
||||||
|
|||||||
Reference in New Issue
Block a user