feat(reports): English edition in the reader — projection, fact tables, charts, export, snapshot v2, route, language switch

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-09-29 19:25:57 +08:00
co-authored by Claude Opus 5.5
parent 08fee6b9be
commit b2ad772e4d
25 changed files with 750 additions and 142 deletions
@@ -6,6 +6,8 @@ import {
resolveReportRead,
} from "@/lib/personal-report-route-core";
import { safeParseServerReportDocument } from "@/lib/personal-report-contract.server";
import { readLongformSnapshot, type LongformSnapshotReader } from "@/lib/personal-report-longform-snapshot";
import type { EnglishEditionRead } from "@/lib/personal-report-route-core";
import {
LONGFORM_APPENDIX_TABLE,
parseLongformAppendixRow,
@@ -49,6 +51,7 @@ async function resolvePersistenceForUser() {
jobs: null,
listSections: undefined,
loadLongformAppendix: undefined,
loadLongformEnglish: undefined,
};
}
const persistence = createSupabasePersonalReportService(supabase);
@@ -84,12 +87,21 @@ async function resolvePersistenceForUser() {
markdown: row.markdown,
};
},
loadLongformEnglish: async (input: Readonly<{ userId: string; reportId: string; requestId: string }>): Promise<EnglishEditionRead> => {
const stored = await readLongformSnapshot(supabase as unknown as LongformSnapshotReader, input);
if (stored?.status !== "ready") return { status: "legacy" };
const snapshot = stored.snapshot;
if (snapshot.version === 1) return { status: "legacy" };
return snapshot.english
? { status: "ready", markdown: snapshot.english.markdown, factTables: snapshot.english.factTables }
: { status: "unavailable" };
},
};
}
export async function GET(request: Request, context: RouteContext) {
try {
const { userId, persistence, jobs, listSections, loadLongformAppendix } = await resolvePersistenceForUser();
const { userId, persistence, jobs, listSections, loadLongformAppendix, loadLongformEnglish } = await resolvePersistenceForUser();
const { reportId } = await context.params;
if (!uuidPattern.test(reportId)) {
return NextResponse.json(
@@ -116,6 +128,7 @@ export async function GET(request: Request, context: RouteContext) {
jobs: jobs ?? undefined,
listSections,
loadLongformAppendix,
loadLongformEnglish,
validateReadyDocument: (document) => {
const parsed = safeParseServerReportDocument(document);
return parsed.ok