diff --git a/data/rules/cli.py b/data/rules/cli.py index 6fc686d..3df57f3 100644 --- a/data/rules/cli.py +++ b/data/rules/cli.py @@ -2,6 +2,7 @@ from __future__ import annotations import argparse import json +import sys from pathlib import Path from data.majors_catalog.cli import ( @@ -87,6 +88,15 @@ def _build_parser() -> argparse.ArgumentParser: audit_run.add_argument("--catalog-root", default=str(DEFAULT_CATALOG_ROOT)) audit_run.add_argument("--json", action="store_true") + subparsers.add_parser("order", help="delegate to data.orders.cli (orders commands)") + + doctor_parser = subparsers.add_parser( + "doctor", help="self-check rules/majors/audit/catalog wiring" + ) + doctor_parser.add_argument("--truth-root", default=str(DEFAULT_RULES_TRUTH_ROOT)) + doctor_parser.add_argument("--catalog-root", default=str(DEFAULT_CATALOG_ROOT)) + doctor_parser.add_argument("--json", action="store_true") + return parser @@ -99,8 +109,19 @@ def _emit(payload: dict[str, object], json_output: bool) -> int: def main(argv: list[str] | None = None) -> int: + # `order` is a pure delegation marker. Handle it before argparse so + # the orders parser can see its native subcommand + flags directly + # (including `--help`). + tokens = list(sys.argv[1:]) if argv is None else list(argv) + if tokens and tokens[0] == "order": + from data.orders.cli import main as orders_main + + # data.orders.cli.main expects subcommands without the `order` + # prefix (its parser exposes `create` / `list` / `show` / ...). + return orders_main(tokens[1:]) + parser = _build_parser() - args = parser.parse_args(argv) + args = parser.parse_args(tokens) if args.command == "rules" and args.rules_command == "status": loader = RuleLoader.from_truth_root(Path(args.truth_root)) @@ -167,6 +188,48 @@ def main(argv: list[str] | None = None) -> int: ) return _emit(payload, args.json) + if args.command == "doctor": + truth_root = Path(args.truth_root) + catalog_root = Path(args.catalog_root) + rules_section: dict[str, object] + rules_ok: bool + try: + rule_status = RuleLoader.from_truth_root(truth_root).build_status() + rules_section = { + "province_count": rule_status.province_count, + "national_rule_count": rule_status.national_rule_count, + } + rules_ok = rule_status.province_count > 0 + except (FileNotFoundError, KeyError, ValueError) as exc: + rules_section = {"error": f"{type(exc).__name__}: {exc}"} + rules_ok = False + + def _safe_build(builder: object, root: Path) -> tuple[dict[str, object], bool]: + try: + payload = builder(root) # type: ignore[operator] + return dict(payload), bool(payload.get("ok", True)) # type: ignore[union-attr] + except (FileNotFoundError, KeyError, ValueError) as exc: + return ( + {"ok": False, "error": f"{type(exc).__name__}: {exc}"}, + False, + ) + + majors_status, majors_status_ok = _safe_build( + build_majors_status_payload, catalog_root + ) + majors_verify, majors_verify_ok = _safe_build( + build_majors_verify_payload, catalog_root + ) + majors_ok = majors_status_ok and majors_verify_ok + overall_ok = rules_ok and majors_ok + payload = { + "ok": overall_ok, + "rules": rules_section, + "majors": majors_status, + "majors_verify": majors_verify, + } + return _emit(payload, args.json) + parser.error("unsupported command") return 2 diff --git a/docs/ACTIVE_EXECUTION_BOARD_2026-06-16_OPTIMIZATION.md b/docs/ACTIVE_EXECUTION_BOARD_2026-06-16_OPTIMIZATION.md index 151667c..05ea616 100644 --- a/docs/ACTIVE_EXECUTION_BOARD_2026-06-16_OPTIMIZATION.md +++ b/docs/ACTIVE_EXECUTION_BOARD_2026-06-16_OPTIMIZATION.md @@ -1,5 +1,8 @@ # Batch 1 审计收敛执行板(2026-06-16) +> **状态**:**已收口(历史快照)**。当前执行真相请看 `docs/ACTIVE_EXECUTION_BOARD_2026-06-17.md` + `docs/CURRENT_STATE.md` §0。 +> 本文件仅作历史审计记录保留,不再作为当前执行口径源。 + 真相源输入: 1. `docs/plans/2026-06-16-optimization-program.md` diff --git a/docs/ACTIVE_EXECUTION_BOARD_2026-06-17.md b/docs/ACTIVE_EXECUTION_BOARD_2026-06-17.md new file mode 100644 index 0000000..7d20922 --- /dev/null +++ b/docs/ACTIVE_EXECUTION_BOARD_2026-06-17.md @@ -0,0 +1,97 @@ +# ACTIVE_EXECUTION_BOARD_2026-06-17 + +> 真相源: `docs/CURRENT_STATE.md` §0 +> 设计快照: `docs/DESIGN_RULES_TRUSTED_CLI_2026-06-16.md` +> 优化计划: `docs/plans/2026-06-16-optimization-program.md` +> 状态: 已收口 / 未启动 两段式 + +--- + +## 1. 已收口(执行 Phase 1 / 1.5 / 2) + +### 1.1 执行 Phase 1 + 1.5 — 规则真相源化 + +- 已落地: + - `rules/_truth/national.yaml`(国家级规则) + - `rules/_truth/province/*.yaml`(27 省,2026 版) + - `data/rules/loader.py` `RuleLoader.from_truth_root` + - `data/rules/audit_engine.py` `AuditEngine.audit_plan` + - `data/rules/cli.py` `rules {status,verify}` + - `scripts/migrate_province_rules_to_truth.py`(从 `PROVINCE_RULES` 一次性迁移) + - `tests/test_rules_truth_phase1.py` + `tests/test_audit_cli_major_validation_phase2.py` +- 提交链: + - `ae4835e` rules truth source loader + rules CLI + - `8a61b8f` phase1.5 verify 收口 + 旧 checker 迁移 +- 三仓: gitea / origin / tksea 同步至 `edc5b11` + +### 1.2 执行 Phase 2 Batch 1 — 国家级专业目录 MVP + +- 已落地: + - `data/majors_catalog/models.py` `NationalMajor` + - `data/majors_catalog/loader.py` `MajorsCatalogLoader` + - `data/majors_catalog/national/2024.json` + `national/latest.json`(13 个 curated subset) + - `data/majors_catalog/cli.py` `majors {status,lookup,verify,changes}` + - `tests/test_majors_catalog_phase2.py` + `tests/test_majors_cli_phase2.py` +- 提交: `36ad58a feat(majors): add phase2 national catalog mvp` + +### 1.3 执行 Phase 2 Batch 2 — 校级招生目录骨架 + +- 已落地: + - `data/majors_catalog/schools/2026/10001.json`(1 所校级骨架样本) + - `data/majors_catalog/cli.py` `majors school-status / school-verify --year` + - `tests/test_majors_school_catalog_phase2.py` + `tests/test_majors_school_cli_phase2.py` +- 提交: `6b1157f feat(majors): add school catalog skeleton and verification` + +### 1.4 执行 Phase 2 Batch 3 — audit_engine 接入 major_validation + +- 已落地: + - `data/rules/audit_engine.py` `_validate_majors` 接入 `audit_plan` + - `data/rules/cli.py` `audit run --province --plan --truth-root --catalog-root --json` + - 输出口径: `MAJORS.not_found` / `MAJORS.non_active` + - `tests/test_audit_engine_major_validation_phase2.py` + `tests/test_audit_cli_major_validation_phase2.py` +- 提交: `edc5b11 feat(audit): validate majors in structured audit flow` + +--- + +## 2. 未启动(执行 Phase 3 = 统一 CLI 命令面) + +> 这是当前唯一未启动且**不依赖外部前置**的 P1 主目标。 +> Lane D(设计)的 Phase 4 = Lane E(执行)的 Phase 3,请勿混淆。 + +### 2.1 范围 + +按 6/16 优化计划 Phase 4 / 设计层 Phase 4 收敛: + +- `gaokao-cli {order,share,delivery,retention,payment,backup,channel,doctor}` 收纳 +- `gaokao-cli doctor` 最小自检(已有 loader / engine / CLI 调用面,不依赖 `gaokato/`) +- 旧 `scripts/gaokao-*` wrapper 的 deprecation warning +- `docs/CLI_API_MAPPING.md` 同步"已落地命令清单"节 + +### 2.2 暂不进入本轮 + +- `gaokato/services/*` 与 `gaokato/transport/*`(设计层 Phase 5 = 执行层 Phase 4) +- `gaokato/capabilities/registry.py`(等 doctor 落地后再用其 JSON 输出作为初版源) +- 退出码统一改写(沿用各子 CLI 现状,不与 Lane D 退出码收口任务冲突) + +### 2.3 不可触发的真源改动 + +- 不改 `data/rules/loader.py` 字段 +- 不改 `data/majors_catalog/*` 数据契约 +- 不改 `rules/_truth/*` yaml +- 不重写 `data/orders/cli.py` 状态机 + +--- + +## 3. 收口规则(防止 4 条线再漂) + +1. 状态查询一律走本文件 + `docs/CURRENT_STATE.md` §0 +2. 设计层 Phase 编号不等于执行层 Phase 编号;引用必须带"执行/设计"前缀 +3. 新写"待做"必须指明属于哪个执行 Phase +4. 历史评审/历史快照/历史整改计划三类文件不再进入本板追踪 +5. docs-only commit 必须在 commit message 标注"docs-only",避免与代码 commit 混轨 + +--- + +## 4. 与本板同步的当前任务文档 + +- `/home/long/.hermes/docs/tasks/current/TASK-20260617-gaokao-optimization-batch2.md` diff --git a/docs/CURRENT_RULES_STATE_2026-06-16.md b/docs/CURRENT_RULES_STATE_2026-06-16.md new file mode 100644 index 0000000..952c54e --- /dev/null +++ b/docs/CURRENT_RULES_STATE_2026-06-16.md @@ -0,0 +1,94 @@ +# CURRENT_RULES_STATE_2026-06-16 + +最后更新: 2026-06-16 +真相源: `docs/RULES_SOURCE_OF_TRUTH.md` +执行上下文: `docs/ACTIVE_EXECUTION_BOARD_2026-06-17.md` §1.1 + +--- + +## 1. 国家级规则覆盖矩阵 + +| scope | rule_key | title | severity | status | source_evidence_id | +| -------- | ------------- | ------------ | -------- | ------ | --------------------------- | +| national | parallel_rule | 平行志愿规则 | info | active | national-2026-parallel-rule | + +> 当前国家级仅 1 条(参考用);其余通用规则随各省差异实现。 + +--- + +## 2. 省级规则覆盖矩阵(2026 版) + +| province | slug | rules_count | status | yaml | +| -------- | ------------ | ----------- | ------ | ----------------------------------------- | +| 北京 | beijing | TBD | active | `rules/_truth/province/beijing.yaml` | +| 上海 | shanghai | TBD | active | `rules/_truth/province/shanghai.yaml` | +| 天津 | tianjin | TBD | active | `rules/_truth/province/tianjin.yaml` | +| 重庆 | chongqing | TBD | active | `rules/_truth/province/chongqing.yaml` | +| 河北 | hebei | TBD | active | `rules/_truth/province/hebei.yaml` | +| 山西 | shanxi | TBD | active | `rules/_truth/province/shanxi.yaml` | +| 内蒙古 | (待补) | — | — | — | +| 辽宁 | liaoning | TBD | active | `rules/_truth/province/liaoning.yaml` | +| 吉林 | jilin | TBD | active | `rules/_truth/province/jilin.yaml` | +| 黑龙江 | heilongjiang | TBD | active | `rules/_truth/province/heilongjiang.yaml` | +| 江苏 | jiangsu | TBD | active | `rules/_truth/province/jiangsu.yaml` | +| 浙江 | zhejiang | TBD | active | `rules/_truth/province/zhejiang.yaml` | +| 安徽 | anhui | TBD | active | `rules/_truth/province/anhui.yaml` | +| 福建 | fujian | TBD | active | `rules/_truth/province/fujian.yaml` | +| 江西 | jiangxi | TBD | active | `rules/_truth/province/jiangxi.yaml` | +| 山东 | shandong | TBD | active | `rules/_truth/province/shandong.yaml` | +| 河南 | henan | TBD | active | `rules/_truth/province/henan.yaml` | +| 湖北 | hubei | TBD | active | `rules/_truth/province/hubei.yaml` | +| 湖南 | hunan | TBD | active | `rules/_truth/province/hunan.yaml` | +| 广东 | guangdong | TBD | active | `rules/_truth/province/guangdong.yaml` | +| 广西 | guangxi | TBD | active | `rules/_truth/province/guangxi.yaml` | +| 海南 | hainan | TBD | active | `rules/_truth/province/hainan.yaml` | +| 四川 | sichuan | TBD | active | `rules/_truth/province/sichuan.yaml` | +| 贵州 | guizhou | TBD | active | `rules/_truth/province/guizhou.yaml` | +| 云南 | yunnan | TBD | active | `rules/_truth/province/yunnan.yaml` | +| 西藏 | xizang | TBD | active | `rules/_truth/province/xizang.yaml` | +| 陕西 | (待补) | — | — | — | +| 甘肃 | gansu | TBD | active | `rules/_truth/province/gansu.yaml` | +| 青海 | qinghai | TBD | active | `rules/_truth/province/qinghai.yaml` | +| 宁夏 | (待补) | — | — | — | +| 新疆 | xinjiang | TBD | active | `rules/_truth/province/xinjiang.yaml` | + +> 当前已落地 27 省 yaml(不含内蒙古/陕西/宁夏,3 个待补)。每省 `rules_count` 由本轮 Batch 4 候选任务回填。 + +--- + +## 3. 证据链覆盖矩阵 + +| 类别 | 已落地 | 备注 | +| -------- | ------ | ------------------------------------------------------- | +| 国家级 | ⚠ 占位 | `national-2026-parallel-rule` 是占位 ID,未指向真实文件 | +| 27 省 | ❌ 0 | `rules/_evidence/` 目录尚未建 | +| 全国通用 | ❌ 0 | 需 `rules/_evidence/national/` 子目录 | + +--- + +## 4. 当前能力面 + +- ✅ `gaokao-cli rules status --json` 报告 27 省在场 +- ✅ `gaokao-cli rules verify --json` 校验 `national.yaml` + `province/` +- ✅ `gaokao-cli audit run --province

--plan --json` 跑结构化审计 +- ❌ `gaokao-cli rules explain ` 未实现 +- ❌ "最近验证时间 > 90 天"自动告警未实现 + +--- + +## 5. 与旧 `PROVINCE_RULES` 的迁移 + +- 迁移方式: 一次性脚本 `scripts/migrate_province_rules_to_truth.py` +- 旧字典保留作为过渡期镜像,新代码只读 `_truth` +- 旧 `gaokao-checker` 内部已委托给 `audit_engine` +- 迁移完整性由 `tests/test_rules_truth_phase1.py` 锁定 + +--- + +## 6. 下一阶段(Batch 4 候选) + +1. `rules/_evidence//-.md` 补建 +2. 逐省 `rules_count` 实际数回填 +3. `gaokao-cli rules explain ` 命令 +4. "最近验证时间 > 90 天"自动告警 +5. 内蒙古/陕西/宁夏 3 省 yaml 补齐 diff --git a/docs/CURRENT_STATE.md b/docs/CURRENT_STATE.md index 0ce6812..4506e3c 100644 --- a/docs/CURRENT_STATE.md +++ b/docs/CURRENT_STATE.md @@ -1,24 +1,41 @@ # CURRENT_STATE 最后更新: 2026-06-17 -状态词: 本地验证完成(Phase2 major_validation 已接入 + 目录/审核闭环已收口) +状态词: 本地验证完成(执行 Phase 1 规则真相源化 + Phase 1.5 verify 收口 + Phase 2 专业目录三 Batch 全部收口 + 下一阶段 = 执行 Phase 3 统一 CLI 命令面,未启动) + 真相源优先级: 1. 本文件 -2. `docs/plans/2026-06-17-phase2-majors-catalog-implementation-plan.md`(当前 Phase2 专业目录执行计划) -3. `docs/DESIGN_RULES_TRUSTED_CLI_2026-06-16.md`(专业目录/CLI 总设计,当前被 Phase2 计划补充) -4. `docs/PROJECT_PLANNING_REALIGNMENT_2026-06-16.md`(规划/实现漂移审计) -5. `docs/RULES_SOURCE_OF_TRUTH.md`(规则真相源索引) -6. `docs/MAJOR_DATA_SOURCE_OF_TRUTH.md`(专业目录真相源索引) -7. `docs/CLI_API_MAPPING.md`(CLI/API 映射索引) +2. `docs/ACTIVE_EXECUTION_BOARD_2026-06-17.md`(6/17 当前执行板,docs-only 收口任务之一) +3. `docs/plans/2026-06-17-phase2-majors-catalog-implementation-plan.md`(Phase 2 已收口快照) +4. `docs/DESIGN_RULES_TRUSTED_CLI_2026-06-16.md`(设计快照;Phase 编号需与执行口径消歧) +5. `docs/PROJECT_PLANNING_REALIGNMENT_2026-06-16.md`(历史规划/实现漂移审计) +6. `docs/RULES_SOURCE_OF_TRUTH.md`(规则真相源索引;待补建) +7. `docs/MAJOR_DATA_SOURCE_OF_TRUTH.md`(专业目录真相源索引) +8. `docs/CLI_API_MAPPING.md`(CLI/API 映射索引) +9. `docs/P0_P1_P2_REMEDIATION_PLAN_2026-06-14.md`(历史整改板;§4 卡片以 §2.1 状态归一为准) +10. `docs/FRONTEND_UI_AUDIT_2026-06-16.md` / `docs/FRONTEND_UI_EXECUTION_BOARD_2026-06-16.md` +11. `docs/ACTIVE_REMEDIATION_2026-06-13.md` +12. `product/PRD.md` / `product/ROADMAP.md` / `docs/IMPLEMENTATION_PLAN_v2.md` +13. `docs/PRODUCTION_DEPLOYMENT_CHECKLIST_2026-06-15.md` +14. `reports/PRODUCT_PLANNING_TECH_ALIGNMENT_REVIEW_2026-06-13.md`(历史评审快照) -8. docs/P0_P1_P2_REMEDIATION_PLAN_2026-06-14.md(当前整改板;2026-06-15 已做状态归一) -9. docs/FRONTEND_UI_AUDIT_2026-06-16.md -10. docs/FRONTEND_UI_EXECUTION_BOARD_2026-06-16.md -11. docs/ACTIVE_REMEDIATION_2026-06-13.md -12. product/PRD.md / product/ROADMAP.md / docs/IMPLEMENTATION_PLAN_v2.md -13. docs/PRODUCTION_DEPLOYMENT_CHECKLIST_2026-06-15.md -14. reports/PRODUCT_PLANNING_TECH_ALIGNMENT_REVIEW_2026-06-13.md(历史评审快照) +--- + +## 0. 单一执行口径(2026-06-17) + +为消除设计 Phase 编号与执行 Phase 编号漂移,本节是当前唯一口径: + +| 设计层 Phase(DESIGN_RULES_TRUSTED_CLI_2026-06-16.md) | 执行层 Phase(当前真相) | 状态 | +| ------------------------------------------------------ | ------------------------------------------ | ---------------------------------------------- | +| Phase 1 规则真相源化 | 执行 Phase 1 | ✅ 已收口(`ae4835e` + `8a61b8f`,三仓同步) | +| Phase 2 统一审计引擎最小可用 | 执行 Phase 1.5(verify + 旧 checker 迁移) | ✅ 已收口(同上) | +| Phase 3 专业目录接入 MVP | 执行 Phase 2 | ✅ 已收口(`36ad58a` + `6b1157f` + `edc5b11`) | +| Phase 4 统一 CLI 命令面 | 执行 Phase 3 | ❌ 未启动 → 下一阶段 | +| Phase 5 智能体调度 + admin 整合 | 执行 Phase 4 | ❌ 未启动 → 等 Phase 3 收口 | +| Phase 6 文档/真相源索引收口 | 长期维护 | ⚠ 进行中(docs-only 收口本轮同步) | + +> 设计层 Phase ≠ 执行层 Phase。状态查询一律以本表 + 6/17 执行板为准。 --- diff --git a/docs/MAJOR_DATA_SOURCE_OF_TRUTH.md b/docs/MAJOR_DATA_SOURCE_OF_TRUTH.md index cec3ce2..6b7091c 100644 --- a/docs/MAJOR_DATA_SOURCE_OF_TRUTH.md +++ b/docs/MAJOR_DATA_SOURCE_OF_TRUTH.md @@ -1,9 +1,10 @@ # MAJOR_DATA_SOURCE_OF_TRUTH -最后更新: 2026-06-16 +最后更新: 2026-06-17 真相源: 本文件是"专业目录"维度的入口索引。 审计上下文: `docs/PROJECT_PLANNING_REALIGNMENT_2026-06-16.md` §2.3-2.4 设计上下文: `docs/DESIGN_RULES_TRUSTED_CLI_2026-06-16.md` §4 +执行上下文: `docs/ACTIVE_EXECUTION_BOARD_2026-06-17.md` §1.2-§1.4 --- @@ -28,20 +29,24 @@ ### 2.1 当前已落地范围(2026-06-17) -- 已落地 `data/majors_catalog/` 包与 national loader/CLI -- 已落地 `national/2024.json` + `national/latest.json` -- 当前 `coverage_mode = mvp_subset` -- 当前 curated subset = 13 个国家级本科专业样本 -- 已可通过: - - `python scripts/gaokao-cli majors status --json` - - `python scripts/gaokao-cli majors lookup 经济学 --json` - - `python scripts/gaokao-cli majors verify --json` -- 已新增: - - `schools/2026/10001.json` 校级招生目录骨架样本 -- 尚未落地: - - 更多学校 `schools//` 批量目录 - - `changes/2024-2026.md` - - `major_validation` 接入 `audit_engine` +执行 Phase 2 三个 Batch 全部收口: + +- 已落地 `data/majors_catalog/` 包与 national loader/CLI(Batch 1) +- 已落地 `national/2024.json` + `national/latest.json`(13 个 curated subset, `coverage_mode=mvp_subset`)(Batch 1) +- 已新增 `schools/2026/10001.json` 校级招生目录骨架样本(**当前仅 1 所样本**,未扩面)(Batch 2) +- 已落地 `major_validation` 接入 `audit_engine`(Batch 3) +- 已新增 CLI 子命令: + - `gaokao-cli majors {status,lookup,verify,changes}`(Batch 1) + - `gaokao-cli majors {school-status,school-verify --year}`(Batch 2) + - `gaokao-cli audit run --province --plan --truth-root --catalog-root --json` 命中 `MAJORS.not_found` / `MAJORS.non_active`(Batch 3) +- 提交链:`36ad58a` → `6b1157f` → `edc5b11`,三仓同步 + +### 2.2 尚未落地 + +- 更多学校 `schools//` 批量目录(仅 1 所样本) +- `changes/2024-2026.md` +- 国家层第二批扩面(>13 个专业) +- 国家层 2026 版(当前是 2024 版快照) --- @@ -84,6 +89,8 @@ SchoolMajorOffering: last_verified_at: datetime ``` +> **当前落地情况**:校级骨架 `schools/2026/10001.json` 1 所样本;其余待 Batch 4 扩面。 + --- ## 4. 接入策略 @@ -93,52 +100,36 @@ SchoolMajorOffering: - **首选源**: 教育部 2024 年《普通高等学校本科专业目录》 - **兜底源**: 阳光高考/学位中心 - **抓取方式**: 不自动爬取,使用人工收录 + 摘录 -- **校验**: `python3 -m data.majors_catalog.cli verify national` +- **校验**: `python3 scripts/gaokao-cli majors verify --json` ### 4.2 校级 - **首选源**: 各高校 2025/2026 招生章程 - **覆盖范围**: 首批 5-10 所重点高校 - **接入节奏**: 每所高校每年一次,5 月前完成 -- **校验**: `python3 -m data.majors_catalog.cli verify school ` +- **校验**: `python3 scripts/gaokao-cli majors school-verify --year --json` --- ## 5. 审计引擎集成 -`audit_engine.run` 接收 plan,新增一步: +`AuditEngine.audit_plan` 新增 `major_validation` 一步: -```python -def _validate_majors(plan: VolunteerPlan) -> list[AuditIssue]: - issues = [] - for item in plan.items: - for major_name in item.major_names: - major = majors_catalog.lookup(major_name) - if major is None: - issues.append(AuditIssue( - rule_id="MAJORS.not_found", - severity=RuleSeverity.WARNING, - title=f"专业未在国家级目录中找到: {major_name}", - suggestion="请人工核对", - )) - elif major.status != "active": - issues.append(AuditIssue( - rule_id="MAJORS.deprecated", - severity=RuleSeverity.CRITICAL, - title=f"专业已撤销/合并/改名: {major.name}", - evidence_quote=f"{major.year_removed} 年已 {major.status}", - )) - return issues -``` +- 命中 `MAJORS.not_found`(专业未在国家级目录中找到) +- 命中 `MAJORS.non_active`(专业已撤销/合并/改名) + +详细见 `tests/test_audit_engine_major_validation_phase2.py`。 --- -## 6. Phase 3 必须收口 +## 6. 下一阶段(执行 Phase 2 收口后的 Batch 4 候选清单) -- `data/majors_catalog/national/2024.json` 落地,500+ 专业覆盖 -- 5+ 重点高校 2025/2026 招生目录落地 -- `gaokao-cli majors lookup/validate` 可用 -- 1+ 真实 e2e:大厂AI方案跑 audit run 命中 major 验证 +> 已在 Batch 1/2/3 中**完成**的事项不再列入"必须收口"清单。 + +- 国家层扩面(>13 个专业)— **Batch 4 候选** +- 校级扩面(>1 所学校)— **Batch 4 候选** +- `changes/2024-2026.md` — **Batch 4 候选** +- 国家层 2026 版快照(当前为 2024 版)— **Batch 4 候选** --- @@ -147,7 +138,4 @@ def _validate_majors(plan: VolunteerPlan) -> list[AuditIssue]: - 国家级目录与校级目录语义混淆 - 教育部新设专业不进入旧目录(2024 之后新设) - 跨年招生章程口径不一致 - ---- - -**下一阶段**: Phase 3 实施,见 `docs/DESIGN_RULES_TRUSTED_CLI_2026-06-16.md` §11 +- 当前 13 个 curated subset 不应被误读为完整教育部目录 diff --git a/docs/P0_P1_P2_REMEDIATION_PLAN_2026-06-14.md b/docs/P0_P1_P2_REMEDIATION_PLAN_2026-06-14.md index e489e24..9cbbd02 100644 --- a/docs/P0_P1_P2_REMEDIATION_PLAN_2026-06-14.md +++ b/docs/P0_P1_P2_REMEDIATION_PLAN_2026-06-14.md @@ -136,7 +136,8 @@ ## 4. 详细整改任务板(历史执行快照,已不再作为当前状态真相) -> 说明:下方逐项卡片保留的是 2026-06-14 制定整改时的任务模板,很多条目仍显示 `状态: pending`。 +> ⚠️ **重要:以下卡片保留的是 2026-06-14 制定整改时的任务模板,绝大多数条目仍显示 `状态: pending`,但实际状态已在 §2.1 中归一为"已完成 / 已转入 T12 / 并入 T12"三类。本节仅作历史记录,不应再被引用为"当前未完成项"。** +> 当前状态请以 `docs/ACTIVE_EXECUTION_BOARD_2026-06-17.md` 与 `docs/CURRENT_STATE.md` §5-§6 为准。 > 当前状态请以上一节“2026-06-15 状态归一”为准;不要再用这里的旧状态判断项目是否全部完成。 ### P1-1 支付回调双写裂缝 diff --git a/docs/RULES_SOURCE_OF_TRUTH.md b/docs/RULES_SOURCE_OF_TRUTH.md index 34d9dde..b1b56a7 100644 --- a/docs/RULES_SOURCE_OF_TRUTH.md +++ b/docs/RULES_SOURCE_OF_TRUTH.md @@ -1,122 +1,129 @@ # RULES_SOURCE_OF_TRUTH -最后更新: 2026-06-16 +最后更新: 2026-06-17 真相源: 本文件是"规则"维度的入口索引。 -审计上下文: `docs/PROJECT_PLANNING_REALIGNMENT_2026-06-16.md` 设计上下文: `docs/DESIGN_RULES_TRUSTED_CLI_2026-06-16.md` §2 +执行上下文: `docs/ACTIVE_EXECUTION_BOARD_2026-06-17.md` §1.1 --- ## 1. 范围 -本文件收敛以下三类信息: +本文件收敛以下四类信息: -- 各省高考 2026 规则 -- 全国通用规则 -- 错误模式库 +- 国家级规则 +- 省级规则 +- 规则证据链 +- 统一审计入口 --- -## 2. 当前真相源路径(Phase 1 之后) +## 2. 当前真相源路径 -| 类别 | 路径 | 格式 | 写入方式 | -| ------------ | ----------------------------------- | --------- | ---------------------------------- | -| 全国通用规则 | `rules/_truth/national.yaml` | YAML | 人工 PR + 自动校验 | -| 省级规则 | `rules/_truth/province/.yaml` | YAML | 人工 PR + 自动校验 | -| 规则证据原文 | `rules/_evidence//` | 抓取/摘录 | 人工收录 | -| 错误模式库 | `rules/errors/ERRORS.md` | Markdown | 人工维护(短期保留) | -| 跨省索引 | `rules/provinces.md` | Markdown | 由 `data/rules/loader.py` 自动生成 | +| 类别 | 路径 | 格式 | 写入方式 | +| ---------- | ------------------------------------------ | -------- | ------------------------------- | +| 国家级规则 | `rules/_truth/national.yaml` | YAML | 人工摘录 + 官方文件校对 | +| 省级规则 | `rules/_truth/province/.yaml` | YAML | 人工摘录 + 招生考试院校对 | +| 规则证据层 | `rules/_evidence//-.md` | Markdown | 待补建(设计层 Phase 1.5 要求) | +| 旧规则字典 | `scripts/gaokao-checker` `PROVINCE_RULES` | Python | 过渡期镜像,已迁移到 \_truth | +| 错误码字典 | `rules/errors/ERRORS.md` | Markdown | 人工维护 | + +### 2.1 当前已落地范围(2026-06-17) + +- 已落地 `rules/_truth/national.yaml`(国家级) +- 已落地 `rules/_truth/province/.yaml`(27 省,2026 版) +- 已落地 `data/rules/loader.py` `RuleLoader.from_truth_root` +- 已落地 `data/rules/audit_engine.py` `AuditEngine` +- 已落地 `data/rules/cli.py` `rules {status,verify}` +- 已落地 `scripts/migrate_province_rules_to_truth.py`(从 `PROVINCE_RULES` 一次性迁移) +- 已落地 `tests/test_rules_truth_phase1.py` + `tests/test_rules_cli_phase1.py`(部分) + +### 2.2 尚未落地 + +- `rules/_evidence//-.md`(证据层)— 设计层 Phase 1.5 要求;当前**未建** +- 逐省证据链审计报告 — 本轮 docs-only 收口后,下一阶段再启动 +- 规则"最近验证时间 > 90 天"自动告警(设计层 §2.5) --- -## 3. 数据模型最小契约 +## 3. 数据模型 + +### 3.1 Rule(`data/rules/models.py`) ```python Rule: - rule_id: str # e.g. HUNAN.max_volunteers + rule_id: str # e.g. "HUNAN.max_volunteers" scope: "national" | "province" - province: str | None - year: int + province: str | None # 当 scope=province 时 + year: int # 2026 title: str description: str severity: "fatal" | "critical" | "warning" | "info" - value: dict # 机读字段,如 {"max_volunteers": 45} - source_evidence_id: str # 指向 rules/_evidence/.md + value: dict # 规则可机读形式 + source_evidence_id: str # 证据链引用 effective_date: date last_verified_at: datetime - version: str + version: str # "2026.1" status: "active" | "draft" | "deprecated" ``` ---- +### 3.2 AuditIssue(`data/rules/models.py`) -## 4. 当前覆盖矩阵(写入本文件时为准) - -| 省份 | 模式 | 文档宣称 | checker 实际 | 备注 | -| ------ | ---------- | --------------- | -------------- | ------------------------------ | -| 湖南 | 院校专业组 | ✅ | ✅ | 详细规则见 province/hunan.yaml | -| 广东 | 院校专业组 | ✅ | ✅ | | -| 湖北 | 院校专业组 | ✅ | ✅ | | -| 安徽 | 院校专业组 | ✅ | ✅ | | -| 江西 | 院校专业组 | ✅ | ✅ | | -| 甘肃 | 院校专业组 | ✅ | ✅ | | -| 黑龙江 | 院校专业组 | ✅ | ✅ | | -| 江苏 | 院校专业组 | ✅ | ✅ | | -| 福建 | 院校专业组 | ✅ | ✅ | | -| 广西 | 院校专业组 | ✅ | ✅ | | -| 北京 | 院校专业组 | ✅ | ✅ | | -| 上海 | 院校专业组 | ✅ | ✅ | | -| 天津 | 院校专业组 | ✅ | ✅ | | -| 海南 | 院校专业组 | ✅ | ✅ | | -| 浙江 | 专业+学校 | ✅ | ✅ | | -| 山东 | 专业+学校 | ✅ | ✅ | | -| 河北 | 专业+学校 | ✅ | ✅ | | -| 重庆 | 专业+学校 | ✅ | ✅ | | -| 辽宁 | 专业+学校 | ✅ | ✅ | | -| 贵州 | 专业+学校 | ✅ | ✅ | | -| 青海 | 专业+学校 | ✅ | ✅ | | -| 吉林 | 专业+学校 | ✅ | ✅ | | -| 新疆 | 传统 | ✅ | ✅ | | -| 西藏 | 传统 | ✅ | ✅ | | -| 河南 | 传统 | ✅ | ✅ | | -| 四川 | 传统 | ✅ | ✅ | | -| 云南 | 传统 | ✅ | ✅ | | -| 山西 | — | ❌(SKILL.md 漏) | ❌(checker 无) | SKILL.md frontmatter 漂移待修 | -| 陕西 | — | ❌(SKILL.md 含) | ❌(checker 无) | SKILL.md / checker 漂移待修 | - ---- - -## 5. 文档漂移清单(已识别) - -- D1: `rules/provinces.md` 与 `scripts/gaokao-checker` 口径一致为 27 省 -- D2: `skills/gaokao-spec-checker/SKILL.md` frontmatter 与实际 checker 省份集仍有漂移 -- D3: 传统模式数 provinces.md 写 5,SKILL.md 写 6 -- D4: 文档与代码都没有 source_evidence_id 链 - ---- - -## 6. Phase 1 必须收口 - -- 27 省全部有 `province/.yaml` -- 至少 1 条全国通用规则有 `national.yaml` -- 1-2 个规则有真实 `source_evidence_id` 链路(湖南优先) -- `gaokao-cli rules status` 可用 - ---- - -## 7. 验证脚本 - -```bash -# 规则台账 -gaokao-cli rules status - -# 单条规则解释 -gaokao-cli rules explain --province 湖南 --rule-id HUNAN.max_volunteers - -# 一致性自检 -python3 -m data.rules.cli verify +```python +AuditIssue: + rule_id: str + severity: RuleSeverity + title: str + message: str + evidence_quote: str | None + suggestion: str | None ``` --- -**下一阶段**: Phase 1 实施,见 `docs/DESIGN_RULES_TRUSTED_CLI_2026-06-16.md` §11 +## 4. 接入策略 + +### 4.1 国家级 + +- **首选源**: 教育部 / 教育考试院公开规则文件 +- **覆盖范围**: 平行志愿、一次投档、退档机制、体检要求等 +- **接入节奏**: 年度全量 + 差异更新 + +### 4.2 省级 + +- **首选源**: 各省招生考试院/教育考试院 2026 招生工作通知 +- **覆盖范围**: 27 省(与 `PROVINCE_RULES` 对齐) +- **接入节奏**: 月度差异更新 + +### 4.3 证据层 + +- **首选源**: 上述官方文件原文 +- **存储格式**: Markdown 摘录 + 官方文件 URL +- **校验**: 每条规则必须能引用至少一个证据条目 + +--- + +## 5. 统一审计入口 + +- `gaokao-cli rules status --json` — 27 省 + national 状态摘要 +- `gaokao-cli rules verify --json` — `national.yaml` + `province/` 完整性 +- `gaokao-cli audit run --province

--plan --truth-root --catalog-root --json` — 完整审计 + +--- + +## 6. 下一阶段(执行 Phase 1 收口后的 Batch 候选清单) + +> 已在 Phase 1 / 1.5 中**完成**的事项不再列入"必须收口"清单。 + +- `rules/_evidence//-.md` 证据层补建 — **Batch 4 候选** +- 逐省证据链审计报告 — **Batch 4 候选** +- `gaokao-cli rules explain ` 命令 — **Batch 4 候选** +- "最近验证时间 > 90 天"自动告警 — **Batch 4 候选** + +--- + +## 7. 风险 + +- 证据层未建导致规则可信度依赖 yaml 自描述 +- 旧 `PROVINCE_RULES` 字典与新 `_truth` 字典需要保持同步过渡 +- 月度刷新流程目前依赖人工 cron,未与 `gaokao-cli rules status` 联动 diff --git a/docs/plans/2026-06-17-phase2-majors-catalog-implementation-plan.md b/docs/plans/2026-06-17-phase2-majors-catalog-implementation-plan.md index 4d9dcfe..0e91287 100644 --- a/docs/plans/2026-06-17-phase2-majors-catalog-implementation-plan.md +++ b/docs/plans/2026-06-17-phase2-majors-catalog-implementation-plan.md @@ -1,5 +1,9 @@ # Phase2 专业目录真相源 Implementation Plan +> **状态**:**已收口(历史快照)**。三个 Batch 全部完成,三仓同步至 `edc5b11`。 +> 实际执行进度见 `docs/ACTIVE_EXECUTION_BOARD_2026-06-17.md` §1.2-§1.4 与 `docs/CURRENT_STATE.md` §0。 +> 本文件保留作为 2026-06-17 制定计划时的原始目标范围,不再作为当前待办清单。 + > **For Hermes:** Use subagent-driven-development skill to implement this plan task-by-task when tasks become independent enough; for the first batch, controller can implement directly because the repo currently has zero `data/majors*` code and the architecture needs one coherent landing. **Goal:** 为 gaokao-volunteer-system 落地“2026 官方专业目录”最小可用真相源,使系统首次具备国家级专业目录的结构化读取、查询、校验与 CLI 能力,并为后续 school offering / audit integration 铺路。 diff --git a/tests/test_cli_doctor_phase3.py b/tests/test_cli_doctor_phase3.py new file mode 100644 index 0000000..f6b380d --- /dev/null +++ b/tests/test_cli_doctor_phase3.py @@ -0,0 +1,87 @@ +from __future__ import annotations + +import json +import subprocess +import sys +from pathlib import Path + +import pytest + +PROJECT_ROOT = Path(__file__).resolve().parents[1] +CLI = PROJECT_ROOT / "scripts" / "gaokao-cli" + + +def _run_cli(*args: str) -> subprocess.CompletedProcess[str]: + return subprocess.run( + [sys.executable, str(CLI), *args], + check=False, + capture_output=True, + text=True, + cwd=PROJECT_ROOT, + ) + + +def test_cli_help_lists_order_and_doctor() -> None: + result = _run_cli("--help") + assert result.returncode == 0 + assert "order" in result.stdout + assert "doctor" in result.stdout + assert "rules" in result.stdout + assert "majors" in result.stdout + assert "audit" in result.stdout + + +def test_cli_order_delegates_list_subcommand() -> None: + # The orders CLI exposes a `list` subcommand. Asking for its --help + # proves the `gaokao-cli order ...` argv successfully reaches the + # orders parser instead of being swallowed by the top-level parser. + result = _run_cli("order", "list", "--help") + assert result.returncode == 0, result.stderr + # The orders parser's list subcommand should expose --status/--source/--limit; + # presence proves the delegation wired up correctly. + assert "--status" in result.stdout + assert "--limit" in result.stdout + + +def test_cli_doctor_returns_ok_json(tmp_path: Path) -> None: + result = _run_cli("doctor", "--json") + assert result.returncode == 0 + payload = json.loads(result.stdout) + assert payload["ok"] is True + assert payload["rules"]["province_count"] >= 27 + assert payload["rules"]["national_rule_count"] >= 0 + assert payload["majors"]["ok"] is True + assert payload["majors_verify"]["ok"] is True + + +def test_cli_doctor_fails_on_empty_truth_root(tmp_path: Path) -> None: + empty_truth = tmp_path / "truth" + empty_truth.mkdir() + empty_catalog = tmp_path / "catalog" + empty_catalog.mkdir() + (empty_catalog / "national").mkdir() + (empty_catalog / "schools").mkdir() + (empty_catalog / "national" / "2024.json").write_text( + json.dumps({"year": 2024, "majors": []}), + encoding="utf-8", + ) + (empty_catalog / "national" / "latest.json").write_text( + json.dumps({"year": 2024, "majors": []}), + encoding="utf-8", + ) + result = _run_cli( + "doctor", + "--truth-root", + str(empty_truth), + "--catalog-root", + str(empty_catalog), + "--json", + ) + assert result.returncode == 1 + payload = json.loads(result.stdout) + assert payload["ok"] is False + assert payload["rules"].get("province_count", 0) == 0 + # When the truth_root is incomplete the loader raises before any + # province_count key is populated, so a structured error key replaces + # the missing province_count. + assert "error" in payload["rules"] or payload["rules"].get("province_count", 0) == 0