报告开头改成重点摘要卡

新报告用围栏画出大运和事业、财富、婚恋,标题下写名字与日期。旧报告不变。

自测:pytest test_report_full_data_fix.py 10 通过;相关四份报告测试退出码 0;前端相关测试 147 通过、0 失败、0 取消;tsc 退出码 0;lint 0 error。

Co-Authored-By: Grok (agent loop)
This commit is contained in:
jesse-ux
2026-10-13 21:45:43 +08:00
co-authored by Grok (agent loop)
parent 963276ca2f
commit 8883f8475f
20 changed files with 856 additions and 82 deletions
@@ -0,0 +1,9 @@
{
"fixtureProvenance": {
"fictional": true,
"edition": "full_data",
"command": "From tests.test_report_english_edition CASES day, edition full_data, both languages. Fictional chart only."
},
"zh": "## 重点\n\n```jyotish-focus\n{\"version\":1,\"periods\":[{\"level\":\"md\",\"lord\":\"Rahu\",\"start\":\"2023-07-08\",\"end\":\"2041-07-08\"},{\"level\":\"ad\",\"lord\":\"Jupiter\",\"start\":\"2026-03-21\",\"end\":\"2028-08-13\"},{\"level\":\"pd\",\"lord\":\"Jupiter\",\"start\":\"2026-03-21\",\"end\":\"2026-07-15\"}],\"domains\":[{\"key\":\"career\",\"house\":10,\"lord\":\"Saturn\",\"placed\":9,\"dignity\":\"入庙\",\"role\":\"功能吉星\"},{\"key\":\"wealth\",\"house\":2,\"lord\":\"Mercury\",\"placed\":12,\"dignity\":\"入友\",\"role\":\"功能吉星\"},{\"key\":\"relationship\",\"house\":7,\"lord\":\"Mars\",\"placed\":2,\"dignity\":\"中性\",\"role\":\"功能中性\"}],\"caveat\":\"这段时间可能突出这些宫主所守护的主题,不是确定会发生的事。\"}\n```",
"en": "## Focus\n\n```jyotish-focus\n{\"version\":1,\"periods\":[{\"level\":\"md\",\"lord\":\"Rahu\",\"start\":\"2023-07-08\",\"end\":\"2041-07-08\"},{\"level\":\"ad\",\"lord\":\"Jupiter\",\"start\":\"2026-03-21\",\"end\":\"2028-08-13\"},{\"level\":\"pd\",\"lord\":\"Jupiter\",\"start\":\"2026-03-21\",\"end\":\"2026-07-15\"}],\"domains\":[{\"key\":\"career\",\"house\":10,\"lord\":\"Saturn\",\"placed\":9,\"dignity\":\"Own Sign\",\"role\":\"functional benefic\"},{\"key\":\"wealth\",\"house\":2,\"lord\":\"Mercury\",\"placed\":12,\"dignity\":\"Friendly Sign\",\"role\":\"functional benefic\"},{\"key\":\"relationship\",\"house\":7,\"lord\":\"Mars\",\"placed\":2,\"dignity\":\"Neutral\",\"role\":\"functional neutral\"}],\"caveat\":\"This period may bring those house-lord topics forward. It is not a certain event.\"}\n```"
}
@@ -1165,6 +1165,33 @@ test("core read: returns a legitimate ready document after canonical re-validati
assert.ok(response.body.reportDocument);
});
test("core read: owner response includes the display name and not the birth fingerprint", async () => {
const persistence = new MemoryPersistence();
const row = {
...seedRecord(),
reportDocument: {
schemaVersion: "report_document.v2",
subject: { displayName: "虚构甲", birthFingerprint: "ab".repeat(16) },
executiveSummary: { headline: "摘要", summary: "正文" },
} as unknown as PersonalReportRecord["reportDocument"],
};
persistence.rows.set(REPORT_ID, row);
const response = await resolveReportRead({
requestUrl: "https://jyotisha.chat/api/reports/x",
origin: null,
allowedOrigins: [],
userId: UUID_A,
reportId: REPORT_ID,
persistence,
loadLongformMarkdown: async () => "# 长报告\n\n## 重点\n正文",
validateReadyDocument: acceptAnyDocument,
});
assert.equal(response.status, 200);
assert.equal(response.body.subjectDisplayName, "虚构甲");
assert.equal(JSON.stringify(response.body).includes("birthFingerprint"), false);
assert.equal(JSON.stringify(response.body.reportDocument).includes("虚构甲"), false);
});
test("core read: markdown loader returns longform and hides the five-chapter body when it is missing", async () => {
const persistence = new MemoryPersistence();
persistence.rows.set(REPORT_ID, seedRecord());
+112
View File
@@ -0,0 +1,112 @@
import assert from "node:assert/strict";
import { readFileSync } from "node:fs";
import test from "node:test";
import React from "react";
import { renderToStaticMarkup } from "react-dom/server";
import { PersonalReportMarkdownView } from "../src/components/personal-report/personal-report-markdown-view.tsx";
import { classifyReportEnvelope } from "../src/components/personal-report/personal-report-page.tsx";
import { buildReportExportBlocks, joinExportBlocks } from "../src/lib/report-export-blocks.ts";
import { parseReportFocusBlock } from "../src/lib/report-focus-block.ts";
import { projectOrdinaryReportDocument, projectOrdinaryReportMarkdown } from "../src/lib/report-public-projection.ts";
Object.assign(globalThis, { React });
const fixture = JSON.parse(readFileSync(new URL("./fixtures/report-focus-fictional-day.json", import.meta.url), "utf8")) as {
fixtureProvenance: { fictional: boolean; command: string; edition: string };
zh: string;
en: string;
};
const OLD_MARKDOWN = `# 印度占星星盘报告
重点:当前主运 MD 北交点,子运 AD 木星,次子运 PD 木星。
## 重点
- 事业第10宫主 土星 落在第9宫,尊贵为入庙,功能吉凶为功能吉星。
## 出生资料与图盘
旧正文仍在。
`;
function markup(markdown: string, props: { subjectName?: string; createdAt?: string; language?: "zh" | "en" } = {}) {
return renderToStaticMarkup(React.createElement(PersonalReportMarkdownView, { markdown, drawnThrough: 0, ...props }));
}
test("fictional day-chart golden renders the focus card without MD labels", () => {
assert.equal(fixture.fixtureProvenance.fictional, true);
assert.match(fixture.fixtureProvenance.command, /full_data/);
assert.equal(fixture.fixtureProvenance.edition, "full_data");
const html = markup(fixture.zh);
assert.match(html, /现在走的大运/);
assert.match(html, /主运/);
assert.match(html, /子运/);
assert.match(html, /次子运/);
assert.match(html, /事业/);
assert.match(html, /财富/);
assert.match(html, /婚恋/);
assert.match(html, /这段时间可能突出这些宫主所守护的主题,不是确定会发生的事。/);
assert.doesNotMatch(html, /\bMD\b|\bAD\b|\bPD\b/);
assert.doesNotMatch(html, /\{"version"/);
const english = markup(fixture.en, { language: "en" });
assert.match(english, /Current period/);
assert.match(english, /Career/);
assert.match(english, /This period may bring those house-lord topics forward/);
assert.equal(parseReportFocusBlock("{"), null);
assert.match(markup("## 重点\n\n```jyotish-focus\n{not-json}\n```\n"), /重点数据无效/);
assert.match(markup("## Focus\n\n```jyotish-focus\n{not-json}\n```\n", { language: "en" }), /Focus data is invalid/);
});
test("export writes the focus as words and the public projection keeps the fence", () => {
const blocks = buildReportExportBlocks({ markdown: fixture.zh });
const text = joinExportBlocks(blocks);
assert.doesNotMatch(text, /\{"version"/);
assert.match(text, /现在走的大运/);
assert.match(text, /这段时间可能突出这些宫主所守护的主题/);
const projected = projectOrdinaryReportMarkdown(fixture.zh);
assert.match(projected, /```jyotish-focus/);
const document = projectOrdinaryReportDocument({
schemaVersion: "report_document.v2",
subject: { displayName: "虚构甲" },
executiveSummary: { headline: "摘要", summary: "正文" },
});
assert.equal(JSON.stringify(document).includes("虚构甲"), false);
});
test("old prose without a fence is unchanged", () => {
const html = markup(OLD_MARKDOWN);
assert.match(html, /重点:当前主运 MD 北交点,子运 AD 木星,次子运 PD 木星。/);
assert.match(html, /事业第10宫主 土星 落在第9宫,尊贵为入庙,功能吉凶为功能吉星。/);
assert.doesNotMatch(html, /重点数据无效|personal-report-focus-card|现在走的大运/);
});
test("title byline covers a name, no name, and a title that already includes the name", () => {
const createdAt = "2026-10-13T08:00:00.000Z";
const named = markup("# 印度占星星盘报告\n", { subjectName: "虚构甲", createdAt });
assert.match(named, /虚构甲 · 生成于 2026-10-13/);
const unnamed = markup("# 印度占星星盘报告\n", { createdAt });
assert.match(unnamed, /生成于 2026-10-13/);
assert.doesNotMatch(unnamed, /虚构甲/);
const already = markup("# 虚构甲的印度占星星盘报告\n", { subjectName: "虚构甲", createdAt });
assert.match(already, /生成于 2026-10-13/);
assert.doesNotMatch(already, /虚构甲 · 生成于/);
const english = markup("# Vedic Astrology Chart Report\n", { subjectName: "Fictional", createdAt, language: "en" });
assert.match(english, /Fictional · Generated 2026-10-13/);
});
test("the signed-in envelope carries the display name onto the reader", () => {
const ready = classifyReportEnvelope(200, {
report: {
id: "11111111-1111-1111-1111-111111111111",
status: "ready",
failureCode: null,
createdAt: "2026-10-13T00:00:00Z",
},
longformMarkdown: "# 印度占星星盘报告\n",
subjectDisplayName: "虚构甲",
});
assert.equal(ready.phase, "markdown-ready");
if (ready.phase === "markdown-ready") assert.equal(ready.subjectDisplayName, "虚构甲");
});