revert(#170): drop the info-deleting compression, keep the measurement (round 2 start)
按 FIX-token-card-1:
- references/product_runtime_contract.md 恢复原样
- 删除自拟任务书(以 Claude 的 TASK-token-card-20261109 为准)
- 三处被弱化的断言恢复原样
- consultation-evidence-card.ts 回到 staging:删信息换来的压缩全部作废,重新按
「不删信息」做
- 新增测量脚本 consult-card-size-170.mts(真实工具链,state.evidenceCardChars)
基线 c032823f 实测(3 盘 × 10 领域):卡片 min 41,963 / 中位 46,017 / max 53,930 /
平均 46,587;modelVisibleChars 平均 91,559。块大小表见 PROGRESS。
Co-Authored-By: Grok (agent loop)
This commit is contained in:
co-authored by
Grok (agent loop)
parent
ebe0797b36
commit
bf5186fe62
@@ -51,3 +51,53 @@
|
||||
|
||||
- 无模型凭据:盲测与「准确度不下降」留待 staging 真机。
|
||||
- 本机 Node 22 / Windows;未跑前端全量 `npm test` 与 Python 快速门(按执行方分工由 Claude 另跑)。
|
||||
|
||||
|
||||
## 第 1 次打回后的重做(2026-11-09,按 FIX-token-card-1 + 正式任务书)
|
||||
|
||||
### 已撤回
|
||||
|
||||
- `references/product_runtime_contract.md` 已恢复原样(不再改生产合同文件)。
|
||||
- 删除自拟任务书 `docs/tasks/TASK-token-card-compress-20261109.md`;以 Claude 的 `TASK-token-card-20261109.md` + `FIX-token-card-1.md` 为准。
|
||||
- 上一轮改动过的三处断言(`consult-card-calendar-narayana-20261108`、`consult-downstream-labels-r3-20261107`、`consult-evidence-card-full-data-20261107`)已恢复原样。
|
||||
- `frontend/src/lib/consultation-evidence-card.ts` 回到 staging 版本:上一轮靠删信息换来的压缩全部作废,重新按「不删信息」来做。
|
||||
|
||||
### 改前基线(用真实工具链量,`state.evidenceCardChars`)
|
||||
|
||||
用 `frontend/scripts/research/consult-card-size-170.mts`,3 张公开盘 × 10 个引擎领域 = 30 例;
|
||||
基线提交 `c032823f9`(staging,已含 #169):
|
||||
|
||||
| 指标 | 模型可见卡片字符数 |
|
||||
| --- | ---: |
|
||||
| 最小 | 41,963 |
|
||||
| 中位 | 46,017 |
|
||||
| 最大 | 53,930 |
|
||||
| 平均 | 46,587 |
|
||||
|
||||
同一脚本量出的 `modelVisibleChars`(含契约与 methodology)平均 91,559。
|
||||
|
||||
### 基线里各块有多大(30 例平均)
|
||||
|
||||
| 块 | 平均字符数 |
|
||||
| --- | ---: |
|
||||
| `base`(合计) | 35,892 |
|
||||
| ├ `base.timing` | 17,360 |
|
||||
| │ ├ `dasha_systems`(26 个系统) | 7,525 |
|
||||
| │ ├ `sade_sati_timeline` | 2,622 |
|
||||
| │ ├ `transits_detail` | 2,478 |
|
||||
| │ ├ `annual_brief` | 2,105 |
|
||||
| │ └ `narayana` | 1,624 |
|
||||
| ├ `base.natal` | 15,539 |
|
||||
| │ ├ `yogas_detail` | 8,298 |
|
||||
| │ ├ `affliction_evidence` | 2,721 |
|
||||
| │ └ `planets` | 1,503 |
|
||||
| └ `base.parameters` | 1,719 |
|
||||
| `domain_sections` | 8,882 |
|
||||
| `meta` | 1,553 |
|
||||
|
||||
### 一个重要约束(下一步的前提)
|
||||
|
||||
查卡段名(`EVIDENCE_LOOKUP_SECTIONS`)里只有 `vimshottari_mahadashas`、`chara_dasha` 这类,
|
||||
**没有**其余 24 个大运系统,也**没有**年运(Tajika)块。按 FIX 的要求「某层没有对应段名时,这层不许下卡」,
|
||||
这两块必须留在卡上。所以 30% 只能靠结构压缩(同事实引用、恒定说明只写一次、去空值、表格改列名 + 行数组),
|
||||
不能靠下卡。下一步从 `base.timing.dasha_systems` 与 `base.natal.yogas_detail` 两块开始。
|
||||
|
||||
@@ -1,92 +0,0 @@
|
||||
# TASK · 省 token:数据卡按本题路由裁层(#170 / BUG-1813)
|
||||
|
||||
- 基线:`origin/staging` `c032823f9`(2026-11-09,round7 整合重封之后)
|
||||
- 执行分支:`codex/token-card-compress-20261109`
|
||||
- 关联:issue #170(`agent/排队` → 本单领取后 `agent/执行中`)、#171(查卡多段,另一分支)、BUG-1813
|
||||
|
||||
## 1. 事实与测量
|
||||
|
||||
写答案的模型每轮读「答题契约 + 数据卡」。2026-11-07 占星师方案把「完整数据」放上卡(`TASK-consult-evidence-card-v2-20260927` 之后的第四轮):全部 26 个大运系统的当前层、全部 Yoga 行连同每行的行星元数据、年运精简块、受冲账本,全部随每张卡送达。
|
||||
|
||||
在 `c032823f9` 上用真实金样(`frontend/tests/fixtures/consult-evidence-card-golden.json`,3 张公开盘 × 12 个领域)测「答题契约 + 数据卡」的模型可见字符数:
|
||||
|
||||
| 指标 | 基线 |
|
||||
| --- | --- |
|
||||
| 平均 | 46,664 |
|
||||
| 最小 | 41,705 |
|
||||
| 最大 | 54,532(`barack_obama` / `general`) |
|
||||
|
||||
单张卡里最大的块(`barack_obama` / `general`):
|
||||
|
||||
| 块 | 字符 | 说明 |
|
||||
| --- | --- | --- |
|
||||
| `base.natal.yogas_detail` | 7,918 | 全部 Yoga 行 + 每行的 `owns` / `functional` / `dignity` 元数据 |
|
||||
| `base.timing.dasha_systems` | 7,794 | 26 个大运系统,其中 20+ 个是 `applicable:false` 的说明行 |
|
||||
| `base.timing.sade_sati_timeline` | 2,695 | 近 7 年 Sade Sati 行 + 方法说明 |
|
||||
| `base.natal.affliction_evidence` | 2,765 | 受冲账本 |
|
||||
| `base.timing.transits_detail` | 2,476 | 行运 |
|
||||
| `base.timing.annual_brief` | 2,270 | 年运块,每个领域都带 |
|
||||
| `domain.argala` | 3,199 | 领域 Argala |
|
||||
| `domain.kp` | 2,007 | KP Placidus |
|
||||
|
||||
`EVIDENCE_CARD_SPECS[domain].route` 已经在每张卡上写明本题要读的技法(`D1 / D9 / D10 / … / Vimshottari / Narayana / transits / KP`)。基础段却把路由没点名的层也塞进去:非路由大运系统、年运块、yoga 元数据。
|
||||
|
||||
## 2. 根因
|
||||
|
||||
`baseExtras()` 是「整个盘一份基础段」的实现,不知道当前领域的路由;只要引擎算出了某一层,就无条件上卡。2026-11-07 的「完整数据」决定把可上卡的层扩到全部大运与年运,于是每张卡都带着本题不读的层。
|
||||
|
||||
## 3. 决策记录(产品 #170,2026-11-09)
|
||||
|
||||
1. **按本题路由裁层**:基础段只保留 `EVIDENCE_CARD_SPECS[domain].route` 点名的层。
|
||||
- 大运系统:只留路由点名的 Vimshottari / Narayana;其余 24 个系统改为查卡工具可取。
|
||||
- Yoga:只在 `spec.yogas === true` 的领域上卡(career / wealth / parents / children / general);其余领域查卡。
|
||||
- 年运块:只在 `annual` 路由上卡;其余领域查卡。
|
||||
2. **同一事实只算一次**:Yoga 行的 `planets` 从 `{ 星: { owns, functional, dignity } }` 压成星名数组;行星事实仍在 `base.natal.planets`、`functional_benefic_malefic` 与领域宫位行里。
|
||||
3. 这是一次**推翻 2026-11-07「回答模型拿到完整数据」的有限让步**:只让出「本题路由不读的层」,路由点名的层一张不少;完整数据仍在服务端回执与查卡工具里。
|
||||
4. 不改引擎、不改提示词规则、不改数据库;金样不重采。
|
||||
|
||||
## 4. 硬红线
|
||||
|
||||
1. 路由点名的层不得缺失:每条路由的 Vimshottari / Narayana / 本领域正式分盘 / 本领域宫 / KP Placidus / 行运。
|
||||
2. 不得删事实:被裁的层必须仍能从 `read-consultation-evidence` 取到,或在卡上另一处存在。
|
||||
3. 不改既有断言的语义;必须改的断言写「原值 / 新值 / 原因」三栏。
|
||||
4. 金样 `consult-evidence-card-golden.json` 不动。
|
||||
5. 不改 `scripts/**`、不改数据库、不改 `.gitea/**`。
|
||||
|
||||
## 5. 任务分解
|
||||
|
||||
| # | 任务 | 验收 |
|
||||
| --- | --- | --- |
|
||||
| T1 | `baseExtras()` 接收领域;大运系统按 `spec.route` 过滤;Yoga 按 `spec.yogas` 上卡;年运块按 `spec.annualHouses` 上卡 | 36 张卡测量通过;每条路由的双大运轨仍在 |
|
||||
| T2 | Yoga 行 `planets` 压成星名数组 | 行的星名与引擎 `yoga_conditions.rows[].planets` 的键一致 |
|
||||
| T3 | 体积回归测试 `consult-card-size-170.test.ts` | 平均 ≤ 33,000、最大 ≤ 44,000;路由层逐项存在 |
|
||||
| T4 | 改既有断言(两处大运、一处 yoga、一处年运) | 三栏说明齐全;`consult*.test.ts` 全量通过 |
|
||||
| T5 | 文案:运行合同里 `Jaimini karaka` 改为 `Jaimini karakas`(清单词扫描要求是卡上键名) | `consult-affliction-reading-20261001` 通过 |
|
||||
|
||||
## 6. 让步顺序
|
||||
|
||||
1. 先保「路由点名的层一张不少」与「事实可查」。
|
||||
2. 再保 30% 体积目标。
|
||||
3. 最后才考虑改断言措辞;不得为过测试删字段。
|
||||
|
||||
## 7. 开工前置
|
||||
|
||||
```powershell
|
||||
git fetch origin --prune
|
||||
git worktree add -b codex/token-card-compress-20261109 .worktrees/token-card-compress-20261109 origin/staging
|
||||
```
|
||||
|
||||
前端自测(Windows,工作树内):
|
||||
|
||||
```powershell
|
||||
frontend\node_modules\.bin\tsc --noEmit -p frontend
|
||||
frontend\node_modules\eslint\bin\eslint.js <改动文件>
|
||||
node frontend\node_modules\tsx\dist\cli.mjs --test frontend/tests/consult-card-size-170.test.ts
|
||||
node frontend\node_modules\tsx\dist\cli.mjs --test (全部 frontend/tests/consult*.test.ts)
|
||||
```
|
||||
|
||||
盲测(同一批题、改前 / 改后各跑一次、人工评分)需要模型凭据;本机没有,按 `BLOCKED.md` 记为环境缺口,部署后由产品负责人在 staging 走真机。
|
||||
|
||||
## 8. BUG 编号
|
||||
|
||||
开工时 `docs/BUG_HISTORY.md` 最大号是 BUG-1809(本分支);#170 标题预留 BUG-1813,本单写 BUG-1813。
|
||||
@@ -0,0 +1,70 @@
|
||||
/**
|
||||
* #170 测量:模型可见数据卡字符数,逐盘逐领域(走真实工具链,读 runtime state)。
|
||||
* 运行:frontend > node node_modules/tsx/dist/cli.mjs scripts/research/consult-card-size-170.mts
|
||||
*/
|
||||
import { readFileSync } from "node:fs";
|
||||
|
||||
import {
|
||||
createConsultationRuntimeState,
|
||||
createConsultationTools,
|
||||
} from "../../src/mastra/consultation-tools.ts";
|
||||
import { consultationEngineDomainIds } from "../../src/lib/consultation-domain-registry.ts";
|
||||
|
||||
type Json = Record<string, unknown>;
|
||||
|
||||
const golden = JSON.parse(readFileSync(
|
||||
new URL("../../tests/fixtures/consult-evidence-card-golden.json", import.meta.url),
|
||||
"utf8",
|
||||
)) as { charts: Array<{ id: string; workflow: Json }> };
|
||||
|
||||
const rows: Array<{ chart: string; domain: string; card: number; visible: number }> = [];
|
||||
|
||||
for (const chart of golden.charts) {
|
||||
for (const domain of consultationEngineDomainIds) {
|
||||
const state = createConsultationRuntimeState();
|
||||
const tools = createConsultationTools({
|
||||
userId: "u",
|
||||
sessionId: "s",
|
||||
requestId: `size-${chart.id}-${domain}`,
|
||||
consultationMode: "verified_chart",
|
||||
theme: domain,
|
||||
serverChart: {
|
||||
name: "public",
|
||||
toolInput: {
|
||||
year: 1955, month: 2, day: 24, hour: 19, minute: 15, city: "San Francisco",
|
||||
lat: 37.77, lon: -122.42, tz: -8, ayanamsa: "raman",
|
||||
},
|
||||
truth: { birthTimeSource: "reported" },
|
||||
} as never,
|
||||
state,
|
||||
runWorkflow: async () => structuredClone(chart.workflow) as never,
|
||||
});
|
||||
await tools["run-jyotish-consultation"].execute!(
|
||||
{ question: "这一块怎么样", domains: [domain] } as never,
|
||||
{ writer: { custom: async () => {} } } as never,
|
||||
);
|
||||
rows.push({
|
||||
chart: chart.id,
|
||||
domain,
|
||||
card: state.evidenceCardChars ?? 0,
|
||||
visible: state.modelVisibleChars ?? 0,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const summarize = (values: number[]) => {
|
||||
const sorted = [...values].sort((a, b) => a - b);
|
||||
return {
|
||||
cases: sorted.length,
|
||||
min: sorted[0],
|
||||
median: sorted[Math.floor(sorted.length / 2)],
|
||||
max: sorted[sorted.length - 1],
|
||||
mean: Math.round(sorted.reduce((sum, value) => sum + value, 0) / sorted.length),
|
||||
};
|
||||
};
|
||||
|
||||
console.log(JSON.stringify({
|
||||
card: summarize(rows.map((row) => row.card)),
|
||||
visible: summarize(rows.map((row) => row.visible)),
|
||||
rows,
|
||||
}, null, 2));
|
||||
@@ -1904,18 +1904,7 @@ const DASHA_ROW_KEYS = [
|
||||
"continuity_issue",
|
||||
] as const;
|
||||
|
||||
function baseExtras(
|
||||
context: unknown,
|
||||
base: EvidenceCard["base"],
|
||||
gaps: string[],
|
||||
domain: ConsultationDomain,
|
||||
) {
|
||||
// #170: the base section carries only the layers this domain's route names.
|
||||
// A route does not name a dasha system, a yoga family or the annual Tajika
|
||||
// block, so the card leaves it to the one-shot lookup instead of shipping it
|
||||
// on every question.
|
||||
const spec = EVIDENCE_CARD_SPECS[domain];
|
||||
const routeSystems = new Set(spec.route.map((item) => item.toLowerCase()));
|
||||
function baseExtras(context: unknown, base: EvidenceCard["base"], gaps: string[]) {
|
||||
const layers = nativeLayers(context);
|
||||
const localLayers = record(record(context).local_layers);
|
||||
const parameters = readyLayer(layers, "card_parameters");
|
||||
@@ -1932,9 +1921,6 @@ function baseExtras(
|
||||
for (const raw of dashas.rows) {
|
||||
const row = record(raw);
|
||||
if (typeof row.system !== "string") continue;
|
||||
// #170: other systems stay in the lookup; the card keeps the ones this
|
||||
// route reads (every route names Vimshottari and Narayana).
|
||||
if (!routeSystems.has(row.system.toLowerCase())) continue;
|
||||
if (row.status === NOT_AVAILABLE_YET && row.applicable === undefined) {
|
||||
gaps.push(`base.dasha_systems.${row.system}:${NOT_AVAILABLE_YET}`);
|
||||
continue;
|
||||
@@ -1998,49 +1984,37 @@ function baseExtras(
|
||||
} else {
|
||||
gaps.push("base.sade_sati_timeline");
|
||||
}
|
||||
// #170: the annual Tajika block rides only on the annual route; other
|
||||
// questions read it through the lookup when they ask about this year.
|
||||
if (spec.annualHouses) {
|
||||
const annualPack = record(layers.annual_tajika);
|
||||
if (annualPack.status === "parameter_sensitive" || annualPack.status === "partial_verified") {
|
||||
const reference = String(record(layers.transit_detail).reference_date ?? "").slice(0, 10);
|
||||
const mudda = Array.isArray(annualPack.mudda_dasha) ? annualPack.mudda_dasha.map(record) : [];
|
||||
const at = reference ? mudda.findIndex((row) => String(row.start) <= reference && reference < String(row.end)) : -1;
|
||||
const muddaNow = at >= 0 ? mudda.slice(at, at + 2) : mudda.slice(0, 2);
|
||||
base.timing.annual_brief = {
|
||||
...pickKeys(annualPack, ["status", "varsha_year", "annual_lagna", "varshesha", "varshesha_basis", "muntha", "tajika_aspects", "tajika_aspects_status", "boundary",
|
||||
// BUG-1722: the Mudda rows are a view clipped to this return -> next return (her Q2).
|
||||
"mudda_view",
|
||||
// BUG-1731 (her Q2): 「今年」 is the calendar year, which spans two Tajika years split at the
|
||||
// return; both years' coverage and the split date travel (the engine computed them, the card dropped them).
|
||||
"calendar_year"]),
|
||||
mudda_running_and_next: muddaNow.map((row) => copy(row)).filter((row): row is Json => row !== undefined),
|
||||
};
|
||||
} else {
|
||||
gaps.push(`base.annual_brief${typeof annualPack.reason === "string" ? `:${annualPack.reason}` : ""}`);
|
||||
}
|
||||
const annualPack = record(layers.annual_tajika);
|
||||
if (annualPack.status === "parameter_sensitive" || annualPack.status === "partial_verified") {
|
||||
const reference = String(record(layers.transit_detail).reference_date ?? "").slice(0, 10);
|
||||
const mudda = Array.isArray(annualPack.mudda_dasha) ? annualPack.mudda_dasha.map(record) : [];
|
||||
const at = reference ? mudda.findIndex((row) => String(row.start) <= reference && reference < String(row.end)) : -1;
|
||||
const muddaNow = at >= 0 ? mudda.slice(at, at + 2) : mudda.slice(0, 2);
|
||||
base.timing.annual_brief = {
|
||||
...pickKeys(annualPack, ["status", "varsha_year", "annual_lagna", "varshesha", "varshesha_basis", "muntha", "tajika_aspects", "tajika_aspects_status", "boundary",
|
||||
// BUG-1722: the Mudda rows are a view clipped to this return -> next return (her Q2).
|
||||
"mudda_view",
|
||||
// BUG-1731 (her Q2): 「今年」 is the calendar year, which spans two Tajika years split at the
|
||||
// return; both years' coverage and the split date travel (the engine computed them, the card dropped them).
|
||||
"calendar_year"]),
|
||||
mudda_running_and_next: muddaNow.map((row) => copy(row)).filter((row): row is Json => row !== undefined),
|
||||
};
|
||||
} else {
|
||||
gaps.push(`base.annual_brief${typeof annualPack.reason === "string" ? `:${annualPack.reason}` : ""}`);
|
||||
}
|
||||
const bookRules = readyLayer(layers, "book_rules");
|
||||
if (bookRules) base.book_rules = pickKeys(bookRules, ["status", "routes", "runtime_packets", "route_reference_rules"]) as JsonRecord;
|
||||
const yogas = spec.yogas ? readyLayer(layers, "yoga_conditions") : null;
|
||||
const yogas = readyLayer(layers, "yoga_conditions");
|
||||
if (yogas && Array.isArray(yogas.rows)) {
|
||||
// 产品 2026-11-07 同步占星师 Yoga 规则批次: claim_kind (structural_observation
|
||||
// is not a named yoga) and the executed formation checks, copied verbatim.
|
||||
// #170: the per-row planet block (owns / functional / dignity) repeated the
|
||||
// base planets table; the row keeps the names, the facts stay in base.natal.
|
||||
const rows = yogas.rows.map((row) => {
|
||||
const picked = pickKeys(row, [
|
||||
"name", "category", "status", "claim_kind", "claim_status", "condition", "conditions_met", "reductions",
|
||||
"formation", "formation_checks", "planets", "running_dasha_levels",
|
||||
// 占星师 2026-11-08 第六轮 R3: Neecha Bhanga witnesses with reference / relative_house /
|
||||
// self_reference; `self_reference_only` rows hold by the literal source branch only.
|
||||
"formation_evidence",
|
||||
]);
|
||||
const names = Object.keys(record(picked.planets));
|
||||
if (names.length) picked.planets = names;
|
||||
else delete picked.planets;
|
||||
return picked;
|
||||
});
|
||||
const rows = yogas.rows.map((row) => pickKeys(row, [
|
||||
"name", "category", "status", "claim_kind", "claim_status", "condition", "conditions_met", "reductions",
|
||||
"formation", "formation_checks", "planets", "running_dasha_levels",
|
||||
// 占星师 2026-11-08 第六轮 R3: Neecha Bhanga witnesses with reference / relative_house /
|
||||
// self_reference; `self_reference_only` rows hold by the literal source branch only.
|
||||
"formation_evidence",
|
||||
]));
|
||||
if (rows.some((row) => row.condition === NOT_AVAILABLE_YET)) gaps.push(`base.yogas_detail.condition:${NOT_AVAILABLE_YET}`);
|
||||
// 占星师第三轮审查 2026-11-07: rules the engine could not evaluate are listed
|
||||
// apart (never as "not formed"); absent when every rule was evaluated.
|
||||
@@ -2052,7 +2026,7 @@ function baseExtras(
|
||||
rows,
|
||||
...(notEvaluated.length ? { not_evaluated: notEvaluated, ...pickKeys(yogas, ["not_evaluated_rule", "rule_engine_status"]) } : {}),
|
||||
};
|
||||
} else if (spec.yogas) {
|
||||
} else {
|
||||
gaps.push("base.yogas_detail");
|
||||
}
|
||||
const strength = readyLayer(layers, "strength_detail");
|
||||
@@ -2793,7 +2767,7 @@ export function buildEvidenceCard(
|
||||
natal: baseNatal(claimEvidence(first.packet, "natal_foundation"), gaps),
|
||||
timing: baseTiming(claimEvidence(first.packet, "timing"), gaps, horizon),
|
||||
};
|
||||
baseExtras(first.context, base, gaps, first.domain);
|
||||
baseExtras(first.context, base, gaps);
|
||||
const domainSections: EvidenceCard["domain_sections"] = {};
|
||||
const domains: ConsultationDomain[] = [];
|
||||
const planFor = (domain: ConsultationDomain): EvidenceCardVargaPlan | undefined => {
|
||||
@@ -2819,7 +2793,7 @@ export function buildEvidenceCard(
|
||||
natal: baseNatal(claimEvidence(source.packet, "natal_foundation"), ownGaps),
|
||||
timing: baseTiming(claimEvidence(source.packet, "timing"), ownGaps, horizon),
|
||||
};
|
||||
baseExtras(source.context, candidate, ownGaps, source.domain);
|
||||
baseExtras(source.context, candidate, ownGaps);
|
||||
if (JSON.stringify(candidate) !== JSON.stringify(base)) {
|
||||
own = candidate;
|
||||
gaps.push(`${source.domain}.base_differs`, ...ownGaps.map((gap) => `${source.domain}.${gap}`));
|
||||
|
||||
@@ -11,7 +11,7 @@ import type { ConsultationDomain } from "../src/lib/consultation-domain-registry
|
||||
|
||||
type Json = Record<string, unknown>;
|
||||
const golden = JSON.parse(readFileSync(new URL("./fixtures/consult-evidence-card-golden.json", import.meta.url), "utf8")) as {
|
||||
charts: Array<{ id: string; workflow: Json; annual_workflow: Json; timing_workflow: Json }>;
|
||||
charts: Array<{ id: string; workflow: Json; timing_workflow: Json }>;
|
||||
};
|
||||
|
||||
function card(workflow: Json, domain: ConsultationDomain): EvidenceCard {
|
||||
@@ -24,11 +24,8 @@ const nativeAnnual = (workflow: Json) => ((workflow.chart as Json).consultation_
|
||||
const narayanaModule = (workflow: Json) => (((workflow.chart as Json).modules as Json).narayana_dasha as Json);
|
||||
|
||||
test("BUG-1731: the card carries the calendar year the engine computed: both Tajika years and the return that splits them", () => {
|
||||
// 原值:timing / wealth 卡也带 base.timing.annual_brief.calendar_year
|
||||
// 新值:年运块只在 annual 路由上卡(EVIDENCE_CARD_SPECS.annual.annualHouses),其余题目从查卡工具取
|
||||
// 原因:#170 省 token:数据卡按本题路由裁层
|
||||
for (const chart of golden.charts) {
|
||||
for (const [workflow, domain] of [[chart.annual_workflow, "annual"]] as const) {
|
||||
for (const [workflow, domain] of [[chart.timing_workflow, "timing"], [chart.workflow, "wealth"]] as const) {
|
||||
const engine = nativeAnnual(workflow).calendar_year as Json;
|
||||
const onCard = (card(workflow, domain).base.timing.annual_brief as Json).calendar_year as Json;
|
||||
assert.deepEqual(onCard, engine, `${chart.id} ${domain}`);
|
||||
|
||||
@@ -63,27 +63,24 @@ test("Shadbala and Argala sections carry their reading labels", () => {
|
||||
}
|
||||
});
|
||||
|
||||
test("dasha systems carry the boundary rule and the route's own systems", () => {
|
||||
test("dasha systems carry the boundary rule and full instants", () => {
|
||||
const built = card(golden.charts[0].workflow, "career");
|
||||
const timing = record(record(built.base).timing);
|
||||
const dashas = record(timing.dasha_systems);
|
||||
assert.equal(dashas.boundary_rule, "start_inclusive_end_exclusive");
|
||||
// 原值:卡上 26 个大运系统,逐行检查 md.start_at 的时刻格式
|
||||
// 新值:卡上只留本题路由点名的 Vimshottari / Narayana;其余系统交给查卡工具
|
||||
// 原因:#170 按本题路由裁层(省 token),大运系统不再每张卡全量送达
|
||||
const systems = record(dashas.systems);
|
||||
assert.deepEqual(Object.keys(systems).sort(), ["narayana", "vimshottari"]);
|
||||
assert.match(String(record(record(systems.narayana).md).start), /^\d{4}-\d{2}-\d{2}$/);
|
||||
// Producers with a time of day carry start_at; date-only producers keep the date alone.
|
||||
const stamps = Object.values(record(dashas.systems))
|
||||
.map((row) => record(record(row).md).start_at)
|
||||
.filter((value): value is string => typeof value === "string");
|
||||
assert.ok(stamps.length > 0);
|
||||
for (const stamp of stamps) assert.match(stamp, /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}([+-]\d{2}:\d{2})$/);
|
||||
});
|
||||
|
||||
test("yoga rules the engine could not evaluate are listed apart from formed yogas", () => {
|
||||
// 原值:family 卡也带 yogas_detail
|
||||
// 新值:yoga 只在 EVIDENCE_CARD_SPECS[domain].yogas 为 true 的领域上卡,改用 career
|
||||
// 原因:#170 按本题路由裁层
|
||||
const built = card(golden.charts[0].workflow, "career");
|
||||
const built = card(golden.charts[0].workflow, "family");
|
||||
const detail = record(record(record(built.base).natal).yogas_detail);
|
||||
const notEvaluated = Array.isArray(detail.not_evaluated) ? detail.not_evaluated as Json[] : [];
|
||||
assert.ok(notEvaluated.length > 0, "the career packet has three rules without an evaluator");
|
||||
assert.ok(notEvaluated.length > 0, "the family packet has three rules without an evaluator");
|
||||
const formed = new Set((Array.isArray(detail.rows) ? detail.rows as Json[] : []).map((row) => row.name));
|
||||
for (const row of notEvaluated) {
|
||||
assert.ok(["not_implemented", "evaluation_failed"].includes(String(row.evaluation_status)));
|
||||
|
||||
@@ -68,23 +68,13 @@ test("base parameters are the engine's own (ayanamsa, nodes, house systems, kara
|
||||
}
|
||||
});
|
||||
|
||||
test("only the route's dasha systems ride the base card, the rest stay a lookup", () => {
|
||||
// 原值:每个 applicable 的系统都在卡上(2026-11-07「回答模型拿到完整数据」),
|
||||
// not_available 的系统写 base.dasha_systems.<name> gap
|
||||
// 新值:只保留本题路由(EVIDENCE_CARD_SPECS[domain].route)点名的大运系统;
|
||||
// 其余系统交给查卡工具,不再上卡、不再记 gap
|
||||
// 原因:#170 省 token:数据卡按本题路由裁层,同一事实不在每个问题上重复送达
|
||||
test("every dasha system's running layer is on the base card, not-yet-computed systems are gaps", () => {
|
||||
for (const chart of golden.charts) {
|
||||
const card = cardFor(chart.workflow, "career");
|
||||
const systems = dig(card.base.timing, "dasha_systems", "systems") as Json;
|
||||
const rows = dig(native(chart.workflow), "dasha_systems", "rows") as Json[];
|
||||
const routeSystems = new Set(["vimshottari", "narayana"]);
|
||||
for (const row of rows) {
|
||||
const name = row.system as string;
|
||||
if (!routeSystems.has(name)) {
|
||||
assert.equal(systems[name], undefined, `${chart.id} ${name} not on the card`);
|
||||
continue;
|
||||
}
|
||||
if (row.applicable === undefined) {
|
||||
assert.equal(systems[name], undefined, `${chart.id} ${name}`);
|
||||
assert.ok(card.meta.gaps.includes(`base.dasha_systems.${name}:not_available_yet`), `${chart.id} ${name} gap`);
|
||||
@@ -92,7 +82,7 @@ test("only the route's dasha systems ride the base card, the rest stay a lookup"
|
||||
}
|
||||
for (const level of ["md", "ad", "pd"]) assert.deepEqual(dig(systems, name, level), row[level], `${chart.id} ${name} ${level}`);
|
||||
}
|
||||
for (const name of ["vimshottari", "narayana"]) {
|
||||
for (const name of ["vimshottari", "narayana", "chara", "sthira", "drig", "shoola", "niryaana_shoola", "navamsha", "lagna_kendradi", "yogini", "kalachakra", "ashtottari", "tribhagi"]) {
|
||||
assert.ok(systems[name], `${chart.id} ${name}`);
|
||||
}
|
||||
// Vimshottari agrees with the card's own running periods; Narayana carries dates.
|
||||
@@ -100,17 +90,13 @@ test("only the route's dasha systems ride the base card, the rest stay a lookup"
|
||||
assert.equal(dig(systems, "narayana", "md", "sign"), dig(card.base.timing.narayana, "md", "sign"));
|
||||
assert.match(String(dig(systems, "narayana", "md", "start")), /^\d{4}-\d{2}-\d{2}$/);
|
||||
assert.equal(typeof dig(systems, "narayana", "date_basis"), "string");
|
||||
assert.equal(typeof dig(systems, "ashtottari", "applicable"), "boolean");
|
||||
}
|
||||
});
|
||||
|
||||
test("transit detail, yogas with conditions, strength and Jaimini sit on the base card", () => {
|
||||
for (const chart of golden.charts) {
|
||||
const card = cardFor(chart.workflow, "timing");
|
||||
// 原值:timing 卡也带 yogas_detail
|
||||
// 新值:yoga 只在 EVIDENCE_CARD_SPECS[domain].yogas 为 true 的领域(career / general / …)上卡,
|
||||
// timing / annual 等路由不含 yoga 的题目从查卡工具取
|
||||
// 原因:#170 按本题路由裁层
|
||||
const yogaCard = cardFor(chart.workflow, "general");
|
||||
const layers = native(chart.workflow);
|
||||
const transits = card.base.timing.transits_detail as Json;
|
||||
assert.deepEqual(transits.bodies, dig(layers, "transit_detail", "bodies"), chart.id);
|
||||
@@ -122,7 +108,7 @@ test("transit detail, yogas with conditions, strength and Jaimini sit on the bas
|
||||
assert.notEqual(dig(transits, "bodies", "Moon", "chandra_bala"), undefined);
|
||||
assert.equal(typeof dig(transits, "sade_sati", "active"), "boolean");
|
||||
assert.deepEqual(transits.key_dates, dig(layers, "slow_transits", "key_dates"));
|
||||
const yogas = yogaCard.base.natal.yogas_detail as Json;
|
||||
const yogas = card.base.natal.yogas_detail as Json;
|
||||
assert.equal((yogas.rows as Json[]).length, (dig(layers, "yoga_conditions", "rows") as Json[]).length);
|
||||
// 原值:status 只有 candidate / computed
|
||||
// 新值:结构观察行写 structural_observation,名字带 (Observation)
|
||||
@@ -131,13 +117,6 @@ test("transit detail, yogas with conditions, strength and Jaimini sit on the bas
|
||||
assert.ok(["candidate", "computed", "structural_observation"].includes(String(row.status)), `${chart.id} ${row.name}`);
|
||||
assert.equal(row.status === "structural_observation", String(row.name).endsWith("(Observation)"), `${chart.id} ${row.name}`);
|
||||
if (row.status === "structural_observation") assert.equal(row.claim_kind, "structural_observation");
|
||||
// 原值:row.planets 是 { 星: { owns, functional, dignity } }
|
||||
// 新值:row.planets 是星名数组;owns / functional / dignity 仍在 base.natal 的行星表与功能吉凶表
|
||||
// 原因:#170 同一事实只算一次,去掉逐行重复的行星元数据
|
||||
if (row.planets !== undefined) {
|
||||
assert.ok(Array.isArray(row.planets), `${chart.id} ${row.name} planets are names`);
|
||||
for (const name of row.planets as string[]) assert.equal(typeof name, "string");
|
||||
}
|
||||
}
|
||||
const strength = card.base.natal.strength as Json;
|
||||
assert.deepEqual(strength.bhava_bala_rupas, dig(layers, "strength_detail", "bhava_bala", "houses"));
|
||||
|
||||
@@ -203,7 +203,7 @@ evidence_card.backstage records, for the backend only, that global web evidence
|
||||
<!-- 产品规则,不是占星师的方法:S-12 讲时间时两套运程都读到当前层。 -->
|
||||
12. 讲时间(最近、今年、接下来几年)时,两套运程都读到当前那一层:Vimshottari 的大运、小运、细运,Narayana 的主运和子运,各落在哪个宫、管什么。主运和子运方向不同时两面都说,不只取主运。
|
||||
<!-- 产品规则,不是占星师的方法:S-13 跨层同源只算一个来源(BUG-1773)。落实占星师 2026-11-08 验收说明(rulings-round6-reply-20261108「产品正在做的四件事」第 1 条:「不能把同源规则条数当独立证据票数」)。BUG-1758 只在受冲来源账内按事实去重;r6 盲测人工复核 3/10、staging 2/9 的回答把跨层复用(同一颗星既是 AmK 又是 AL 主星;同一宫主在 D1、Arudha、Jaimini 各算一次;同一月亮事实既进瑜伽又进力量)当成独立支持。卡上新增各领域 `independence`(fact_id → 复用它的层;support / pressure 按不同 fact_id 列)。 -->
|
||||
13. 卡上各领域的 `independence` 是同源索引:`facts` 的每个键是一个盘面事实(一颗星的 D1 落座落宫、一个同宫、一个相位、一颗星的功能吉凶),值是复用这个事实的层(宫主、宫内星、Arudha 主星、Jaimini karakas、瑜伽、事业分析、Shadbala、大运主星、受冲来源);`support` / `pressure` 列出支持 / 压力一面的不同事实。几层共用同一个事实时,写成一个来源,并点名复用它的层(例「土星落天秤 2 宫这一个事实,同时是 AmK 和 A2 主星」),不当成几条独立证据;独立的支持或压力只按不同的事实计。几层共用一个事实时,不写「多层一致支持」「多体系一致」「N 条独立线索」。
|
||||
13. 卡上各领域的 `independence` 是同源索引:`facts` 的每个键是一个盘面事实(一颗星的 D1 落座落宫、一个同宫、一个相位、一颗星的功能吉凶),值是复用这个事实的层(宫主、宫内星、Arudha 主星、Jaimini karaka、瑜伽、事业分析、Shadbala、大运主星、受冲来源);`support` / `pressure` 列出支持 / 压力一面的不同事实。几层共用同一个事实时,写成一个来源,并点名复用它的层(例「土星落天秤 2 宫这一个事实,同时是 AmK 和 A2 主星」),不当成几条独立证据;独立的支持或压力只按不同的事实计。几层共用一个事实时,不写「多层一致支持」「多体系一致」「N 条独立线索」。
|
||||
<!-- /product-block:shared-reading -->
|
||||
|
||||
## F. 可能违反占星师 2026-11-07 写作底线的现有产品规则(第 1–6 条已按她 2026-11-08 第五轮裁定 6 修改)
|
||||
|
||||
Reference in New Issue
Block a user