feat(consult): cards carry functional profile v2; close round-4 backtest residue (BUG-1158, BUG-1176, BUG-1182)
- Evidence-card and biography-backtest goldens regenerated with the capture
scripts (PYTHONHASHSEED=0, cache TTL 0; byte-identical twice); only the
functional-role fields change.
- CAREER_FIELD_ASK_RULE: A10 / AL / Sun / 10th lord in 4/6/8/12 say
resistance or cost only, never "not on stage / go deep", and houses are not
translated into fields.
- AFFLICTION_RANGE_RULE: no portrait of an afflicted person from the lord's
house or a benefic occupant ("managing / arranging / cares about your
future"), actions do not presuppose reaching them, one ask per answer.
- Examples that paired "4th lord in 10th" with "her attention is on your
future / close" now state the line is unafflicted (Monroe's chart had that
exact placement and copied it). Retrograde line also bans "drags on".
- Backtest script: several follow-up domains in one run.
Changed assertions carry 原值 / 新值 / 原因 in the test comments.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N4f2nya58RoRu4yEmJgRGE
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
3a40d42208
commit
7806c72f2c
@@ -146,7 +146,10 @@ test("parents roles carry full rows for the house lords and significators, funct
|
||||
}
|
||||
const father = dig(card(jobs.workflow, "parents").domain_sections.parents, "roles", "father") as Json;
|
||||
assert.ok((dig(father, "lord_rows", "Venus", "conjunct_with") as string[]).includes("Rahu"));
|
||||
assert.equal(dig(father, "significator_rows", "Sun", "functional"), "malefic");
|
||||
// 原值: "malefic"(v1 公式:12 宫主一律判凶)
|
||||
// 新值: "neutral"(profile bphs_ch34_general_with_sign_exceptions_v2:单管 12 宫的星是条件性的,不自动判凶)
|
||||
// 原因: 产品 2026-10-02 采纳上游功能吉凶 v2(57782aea,BUG-1158);golden 由 capture 脚本重新生成
|
||||
assert.equal(dig(father, "significator_rows", "Sun", "functional"), "neutral");
|
||||
});
|
||||
|
||||
test("health takes the 1st and 12th houses, education the 4th", () => {
|
||||
|
||||
@@ -24,7 +24,10 @@ const OLD_SECTION_RULE = /这个人是什么样、你们怎么相处、事情会
|
||||
// 原值: SECTION_RULE_TEXT = T1 定稿句(「包括这个人在不在你身边、近不近」「受冲…第一句就写这一面,用『可能』,段末…请用户确认」)
|
||||
// 新值: 受冲两个及以上的写法改由 AFFLICTION_RANGE_RULE 定义(压力迹象 + 范围 + 请用户说一句实际情况,不断定传记事实、不写反向安慰)
|
||||
// 原因: 产品 2026-10-02 改口径(名人回测对照组误报 1→15;盘分不出父母在不在、几段感情、有没有病)
|
||||
const AFFLICTION_RANGE_TEXT = "同一个人或同一件事有两个及以上受冲信号:先说这条线有压力或距离的迹象(括号里写是哪几条),再说现实里可能是什么,给一个范围(例:「现实里可能是分开、离得远,也可能只是关系紧、话少」);不替用户断定他自己更清楚的事实(父母在不在身边、几段婚姻或感情、有没有病),也不反过来写「有分量」「站得住」「不是缺位」「底子不差」「关系稳」「能走下去」;这一段末尾用一句话请用户说说实际情况,全篇这类请求最多一处。";
|
||||
// 原值(2): 上一行定稿句,止于「……全篇这类请求最多一处。」
|
||||
// 新值(2): 加「不拿宫主落宫、宫里吉星描写这个人对你做什么……行动建议不写给他打电话、去问他」与「问过之后别的段落不再另问」
|
||||
// 原因(2): 第四轮回测梦露 ×2、琵雅芙 ×1 受冲两个以上的母亲仍写「在管、在安排、近」,3 份全篇两处请求(BUG-1182)
|
||||
const AFFLICTION_RANGE_TEXT = "同一个人或同一件事有两个及以上受冲信号:先说这条线有压力或距离的迹象(括号里写是哪几条),再说现实里可能是什么,给一个范围(例:「现实里可能是分开、离得远,也可能只是关系紧、话少」);不替用户断定他自己更清楚的事实(父母在不在身边、几段婚姻或感情、有没有病),也不反过来写「有分量」「站得住」「不是缺位」「底子不差」「关系稳」「能走下去」;不拿宫主落哪一宫、宫里有吉星去描写这个人对你做什么(「在管」「在安排」「心思在你的前途上」「照顾是实的」都预设人在身边,宫主落宫和吉星只说这条线另有支撑的迹象),这一方的行动建议也不写给他打电话、去问他;这一段末尾用一句话请用户说说实际情况,全篇这类请求最多一处,问过之后别的段落不再另问。";
|
||||
const SECTION_RULE_TEXT = `每段先说盘上最强的一两个信号说明了什么;盘上没有依据的方面不写,不用日常场景凑段落。${AFFLICTION_RANGE_TEXT}请用户说实际情况的那句例:「实际是哪一种,告诉我一句,我按真实情况再看。」不预设有没有伴侣、是否已婚、是否在上班、做哪类工作、孩子性别、父母是否在身边;性别未知时不用「他」「她」称伴侣和孩子。`;
|
||||
|
||||
function source(relative: string) {
|
||||
@@ -73,7 +76,10 @@ test("T2: no 逆行 in prompts or examples except the prohibition line and techn
|
||||
));
|
||||
assert.deepEqual(hits, allowed);
|
||||
assert.equal(allowed.length, 3);
|
||||
assert.match(productConversationVoice, /不是。(4 宫主落 10 宫)她把心力放在你的前途上/);
|
||||
// 原值: /不是。(4 宫主落 10 宫)她把心力放在你的前途上/
|
||||
// 新值: /不是。(母亲这条线没有受冲;4 宫主落 10 宫)她把心力放在你的前途上/
|
||||
// 原因: BUG-1182(示例读法被照抄,复发自 BUG-1165)
|
||||
assert.match(productConversationVoice, /不是。(母亲这条线没有受冲;4 宫主落 10 宫)她把心力放在你的前途上/);
|
||||
assert.doesNotMatch(productConversationVoice, /已经在一起的关系|这段婚姻/);
|
||||
});
|
||||
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
// PROGRESS-consult-upstream-functional-v2-20261002: the evidence cards carry the
|
||||
// upstream functional profile v2 (BUG-1158), and the round-4 backtest residue is
|
||||
// closed in the single-definition rules (BUG-1176 career placements, BUG-1182
|
||||
// parents portrait / second ask, BUG-1165 retrograde). Expected functional roles
|
||||
// come from the real engine golden (scripts/research/capture_consult_evidence_card_golden.py).
|
||||
import assert from "node:assert/strict";
|
||||
import { readFileSync } from "node:fs";
|
||||
import test from "node:test";
|
||||
|
||||
import { buildEvidenceCard } from "../src/lib/consultation-evidence-card.ts";
|
||||
import type { ConsultationDomain } from "../src/lib/consultation-domain-registry.ts";
|
||||
import { createConsultationPlan } from "../src/lib/consultation-plan.ts";
|
||||
import { CONDENSED_SHARED_READING_LINES } from "../src/lib/consultation-condensed-checklist.ts";
|
||||
import { AFFLICTION_RANGE_RULE, CAREER_FIELD_ASK_RULE } from "../src/lib/consultation-thinking-plan.ts";
|
||||
import {
|
||||
consultationWorkflowResponseSchema,
|
||||
toAgentConsultationContext,
|
||||
toModelOutput,
|
||||
} from "../src/mastra/consultation-workflow.ts";
|
||||
import { productConversationVoice } from "../src/mastra/product-voice.ts";
|
||||
|
||||
type Json = Record<string, unknown>;
|
||||
const golden = JSON.parse(readFileSync(
|
||||
new URL("./fixtures/consult-evidence-card-golden.json", import.meta.url),
|
||||
"utf8",
|
||||
)) as { charts: { id: string; workflow: Json }[] };
|
||||
const backtest = JSON.parse(readFileSync(
|
||||
new URL("./fixtures/consult-biography-backtest-golden.json", import.meta.url),
|
||||
"utf8",
|
||||
)) as { figures: { id: string; shared_chart: { modules: { functional_benefic_malefic: Json } } }[] };
|
||||
|
||||
function card(workflow: Json, domain: ConsultationDomain) {
|
||||
const context = toAgentConsultationContext(consultationWorkflowResponseSchema.parse(structuredClone(workflow)));
|
||||
const packet = toModelOutput(context, createConsultationPlan({ userIntent: "q", theme: domain }), domain);
|
||||
return buildEvidenceCard([{ domain, packet, context }]);
|
||||
}
|
||||
|
||||
test("goldens carry functional profile v2 from the real engine (BUG-1158)", () => {
|
||||
const layers = [
|
||||
...golden.charts.map((chart) => (chart.workflow.chart as Json).modules as Json).map((modules) => modules.functional_benefic_malefic as Json),
|
||||
...backtest.figures.map((figure) => figure.shared_chart.modules.functional_benefic_malefic),
|
||||
];
|
||||
assert.equal(layers.length, 12);
|
||||
for (const layer of layers) assert.equal(layer.profile, "bphs_ch34_general_with_sign_exceptions_v2");
|
||||
// Scorpio (Taylor): explicit exceptions Mars mixed, Sun yoga-supporting; Venus (7/12) no longer malefic by the 12th alone.
|
||||
const taylor = card(golden.charts.find((chart) => chart.id === "elizabeth_taylor")!.workflow, "career");
|
||||
const functional = taylor.base.natal.functional_benefic_malefic as Json;
|
||||
assert.deepEqual(functional.functional_benefics, ["Jupiter", "Moon", "Sun"]);
|
||||
assert.deepEqual(functional.functional_malefics, ["Mercury", "Saturn"]);
|
||||
assert.deepEqual(functional.functional_neutrals, ["Mars", "Venus"]);
|
||||
assert.deepEqual(functional.yogakarakas, []);
|
||||
// Virgo (Jobs): Jupiter challenging by exception, Sun (sole 12th lord) neutral.
|
||||
const jobs = card(golden.charts.find((chart) => chart.id === "steve_jobs")!.workflow, "parents");
|
||||
const jobsFunctional = jobs.base.natal.functional_benefic_malefic as Json;
|
||||
assert.deepEqual(jobsFunctional.functional_malefics, ["Jupiter", "Mars", "Moon"]);
|
||||
assert.deepEqual(jobsFunctional.functional_neutrals, ["Sun"]);
|
||||
});
|
||||
|
||||
test("career: weak placements of A10 / AL / Sun / 10th lord are resistance, not a behind-the-scenes verdict (BUG-1176)", () => {
|
||||
for (const phrase of [
|
||||
"A10、AL、太阳、10 宫主落 4、6、8、12 宫只说这条线的阻力或代价",
|
||||
"不翻成「不在台前」「不在台面上」「不靠曝光」「幕后」「不是站台式」「往深处做」",
|
||||
"也不把落宫翻成行业",
|
||||
]) assert.ok(CAREER_FIELD_ASK_RULE.includes(phrase), phrase);
|
||||
// Still the only ask of the answer.
|
||||
assert.ok(CAREER_FIELD_ASK_RULE.includes("这一句就是全篇唯一一处请用户说实际情况"));
|
||||
});
|
||||
|
||||
test("afflicted person: no portrait of what they do, no presupposing action, one ask (BUG-1182)", () => {
|
||||
for (const phrase of [
|
||||
"不拿宫主落哪一宫、宫里有吉星去描写这个人对你做什么",
|
||||
"「在管」「在安排」「心思在你的前途上」「照顾是实的」都预设人在身边",
|
||||
"这一方的行动建议也不写给他打电话、去问他",
|
||||
"问过之后别的段落不再另问",
|
||||
]) assert.ok(AFFLICTION_RANGE_RULE.includes(phrase), phrase);
|
||||
// The examples only show "near / cares about your future" for an unafflicted mother line.
|
||||
assert.match(productConversationVoice, /盘上有证据、且这条线没有受冲时可以写「4 宫主落 10 宫,母亲的注意力在你的前途上」(受冲两个及以上按受冲规则写,不写这一句)/);
|
||||
assert.match(productConversationVoice, /她离你近,对你的事上心,你们之间来往多。(母亲这条线没有受冲;4 宫主落 10 宫;月亮在 10 宫)/);
|
||||
assert.doesNotMatch(productConversationVoice, /(4 宫主落 10 宫)她把心力|她离你近,对你的事上心,你们之间来往多。(4 宫主落 10 宫/);
|
||||
});
|
||||
|
||||
test("retrograde is never the reason for 'drags on' either (BUG-1165 recurrence)", () => {
|
||||
const line = CONDENSED_SHARED_READING_LINES.find((text) => text.includes("逆行不等于"));
|
||||
assert.ok(line);
|
||||
assert.match(line, /逆行不等于「反复、打回来」,也不等于「拖得久、拖出来」,推论里不拿逆行当理由(依据里可以列)/);
|
||||
});
|
||||
@@ -231,7 +231,10 @@ test("the pinned Good examples split by person and stay plain; the coined-patter
|
||||
// 原值: assert.match(parents, /(4 宫母亲宫的宫主水星落在 10 宫事业宫;代表母亲的月亮也在 10 宫)/)
|
||||
// 新值: 括号只放占位(4 宫主落 10 宫;月亮在 10 宫);爸爸段演示「受冲先说、用可能、段末请确认」;不再有「分量重」「问爸爸的意见」
|
||||
// 原因: TASK-consult-no-presupposition-and-backtest-20261001 D1/D3(BUG-1164/1165):旧例预设父亲在场,回测里缺席的父亲被照抄成「话少、分量重」
|
||||
assert.match(parents, /(4 宫主落 10 宫;月亮在 10 宫)/);
|
||||
// 原值(3): /(4 宫主落 10 宫;月亮在 10 宫)/
|
||||
// 新值(3): /(母亲这条线没有受冲;4 宫主落 10 宫;月亮在 10 宫)/
|
||||
// 原因(3): 第五轮回测前修(BUG-1182):梦露盘 4 宫主正落 10 宫且母亲受冲两个以上,模型照抄「上心、前途」;示例只在没有受冲时演示「近、上心」
|
||||
assert.match(parents, /(母亲这条线没有受冲;4 宫主落 10 宫;月亮在 10 宫)/);
|
||||
// 原值(2): /他可能不常在你身边[\s\S]*如果实际情况不是这样,告诉我,我按真实情况重看。/
|
||||
// 新值(2): 爸爸段写「有距离的迹象」+ 范围 + 「实际是哪一种,告诉我一句」,不断定在不在身边
|
||||
// 原因(2): 产品 2026-10-02 改口径(名人回测对照组误报 1→15;盘分不出父母在不在身边)
|
||||
@@ -285,7 +288,10 @@ test("the voice answers the sentence asked: no motive guessing, yes/no first, co
|
||||
// 原值: assert.match(voice, /不是。4 宫主水星逆行落 10 宫/)
|
||||
// 新值: 「不是。(4 宫主落 10 宫)」;示例与提示词里不再有「逆行」,已婚预设的婚姻 Good 例删除
|
||||
// 原因: TASK-consult-no-presupposition-and-backtest-20261001 D2/D3(BUG-1165):「逆行」被照抄成「反复、打回来」
|
||||
assert.match(voice, /不是。(4 宫主落 10 宫)她把心力放在你的前途上/);
|
||||
// 原值(2): /不是。(4 宫主落 10 宫)她把心力放在你的前途上/
|
||||
// 新值(2): /不是。(母亲这条线没有受冲;4 宫主落 10 宫)她把心力放在你的前途上/
|
||||
// 原因(2): BUG-1182:受冲两个以上的母亲被照抄成「心力在你前途上」;示例括号写明这一读法只在没有受冲时成立
|
||||
assert.match(voice, /不是。(母亲这条线没有受冲;4 宫主落 10 宫)她把心力放在你的前途上/);
|
||||
assert.doesNotMatch(voice, /逆行|已经在一起的关系/);
|
||||
assert.match(voice, /Bad \(同一问题\): 你其实要的是被听见/);
|
||||
// 原值: "…wants, needs, or feels; state where the chart puts them"(Never 列表里第二句读成也被禁止)
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user