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:
Jesse_Chen
2026-10-02 12:32:02 +08:00
co-authored by Claude Opus 5.5
parent 3a40d42208
commit 7806c72f2c
11 changed files with 135 additions and 23 deletions
@@ -30,6 +30,10 @@
* [--model deepseek-flash] [--base-url https://api.deepseek.com] [--concurrency 4] [--dump-cards]
* [--correction barack_obama,marilyn_monroe] [--correction-text 我爸其实没怎么管过我]
* [--correction-domain career] [--correction-texts "zinedine_zidane=我是足球运动员|barack_obama=我是政治家"]
* Several follow-up domains in one run: --correction-domain parents,career with
* domain-qualified entries, e.g. --correction "parents:barack_obama,career:zinedine_zidane"
* --correction-texts "career:zinedine_zidane=我是足球运动员" (an unqualified entry or text
* applies to every listed domain).
*
* --dump-cards writes the model-visible tool result per figure x domain and
* calls no model. --correction adds, for each listed figure's parents answer,
@@ -133,7 +137,7 @@ function parseArgs(argv: readonly string[]) {
correctionText: value("correction-text") ?? "我爸其实没怎么管过我",
// Follow-up domain (default parents) and optional per-figure texts
// "figure=text|figure=text" (career follow-up: the user states the real job).
correctionDomain: value("correction-domain") ?? "parents",
correctionDomains: (value("correction-domain") ?? "parents").split(",").filter(Boolean),
correctionTexts: Object.fromEntries((value("correction-texts") ?? "").split("|").filter(Boolean).map((pair) => {
const at = pair.indexOf("=");
return [pair.slice(0, at), pair.slice(at + 1)];
@@ -552,13 +556,15 @@ async function main() {
return { figure: job.figure.id, domain: job.domain, run: job.run, chars: result.output.length, ...result, output: undefined, checks };
});
const corrections = await pool(
jobs.filter((job) => job.domain === args.correctionDomain && args.correction.includes(job.figure.id)),
jobs.filter((job) => args.correctionDomains.includes(job.domain)
&& (args.correction.includes(job.figure.id) || args.correction.includes(`${job.domain}:${job.figure.id}`))),
args.concurrency,
async (prior) => {
const priorFile = join(args.out, prior.figure.id, `${prior.domain}-run${prior.run}.md`);
const priorText = readFileSync(priorFile, "utf8");
const priorAnswer = priorText.slice(priorText.indexOf("\n---\n\n") + 6).trim();
const job: Job = { ...prior, question: args.correctionTexts[prior.figure.id] ?? args.correctionText, followUp: { priorQuestion: prior.question, priorAnswer } };
const question = args.correctionTexts[`${prior.domain}:${prior.figure.id}`] ?? args.correctionTexts[prior.figure.id] ?? args.correctionText;
const job: Job = { ...prior, question, followUp: { priorQuestion: prior.question, priorAnswer } };
let result: Awaited<ReturnType<typeof runJob>>;
try {
result = await runJob(job, resolved);
@@ -581,7 +587,7 @@ async function main() {
"",
].join("\n"));
console.log(`${job.figure.id} ${job.domain} correction run${job.run}: ${result.output.length} chars, ${result.seconds.toFixed(0)} s${result.failure ? `, FAILED ${result.failure}` : ""}`);
return { figure: job.figure.id, run: job.run, chars: result.output.length, seconds: result.seconds, failure: result.failure, usage: result.usage };
return { figure: job.figure.id, domain: job.domain, run: job.run, chars: result.output.length, seconds: result.seconds, failure: result.failure, usage: result.usage };
},
);
const total: Usage = {};