fix(report): classify writer failures and size CJK section budgets
Staging personal_full died as report_schema_invalid after the first chapter blocked. Keep assertWriterOutput, stop treating refs mismatch as missing evidence, and surface section error codes on the report APIs. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -11,6 +11,7 @@ import {
|
||||
type PersonalReportService,
|
||||
} from "@/lib/personal-report-service";
|
||||
import { createSupabasePersonalReportJobService } from "@/lib/personal-report-job-service";
|
||||
import { createPersonalReportSectionService } from "@/lib/personal-report-section-service";
|
||||
import { isSupabaseConfigurationError } from "@/lib/supabase/config";
|
||||
import { createServerSupabaseClient } from "@/lib/supabase/server";
|
||||
|
||||
@@ -38,15 +39,24 @@ async function resolvePersistenceForUser() {
|
||||
const supabase = await createServerSupabaseClient();
|
||||
const { data: { user }, error: authError } = await supabase.auth.getUser();
|
||||
if (authError || !user) {
|
||||
return { userId: null as string | null, persistence: null as PersonalReportService | null, jobs: null };
|
||||
return { userId: null as string | null, persistence: null as PersonalReportService | null, jobs: null, listSections: undefined };
|
||||
}
|
||||
const persistence = createSupabasePersonalReportService(supabase);
|
||||
return { userId: user.id, persistence, jobs: createSupabasePersonalReportJobService(supabase) };
|
||||
const sections = createPersonalReportSectionService(supabase as never);
|
||||
return {
|
||||
userId: user.id,
|
||||
persistence,
|
||||
jobs: createSupabasePersonalReportJobService(supabase),
|
||||
listSections: async (ownerId: string, requestId: string) => {
|
||||
const rows = await sections.list(ownerId, requestId);
|
||||
return rows.map((row) => ({ status: row.status, lastErrorCode: row.lastErrorCode }));
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export async function GET(request: Request, context: RouteContext) {
|
||||
try {
|
||||
const { userId, persistence, jobs } = await resolvePersistenceForUser();
|
||||
const { userId, persistence, jobs, listSections } = await resolvePersistenceForUser();
|
||||
const { reportId } = await context.params;
|
||||
if (!uuidPattern.test(reportId)) {
|
||||
return NextResponse.json(
|
||||
@@ -71,6 +81,7 @@ export async function GET(request: Request, context: RouteContext) {
|
||||
// before it is returned to the browser. Client-side validation is never
|
||||
// a substitute.
|
||||
jobs: jobs ?? undefined,
|
||||
listSections,
|
||||
validateReadyDocument: (document) => {
|
||||
const parsed = safeParseServerReportDocument(document);
|
||||
return parsed.ok
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
resolveSkillSnapshot,
|
||||
} from "@/lib/personal-report-generation";
|
||||
import { REPORT_STABLE_CODES } from "@/lib/personal-report-codes";
|
||||
import { summarizePersonalReportFailure } from "@/lib/personal-report-failure-summary";
|
||||
import { isProductEnabled } from "@/lib/product-access";
|
||||
import {
|
||||
isPersonalReportFeatureEnabled,
|
||||
@@ -54,7 +55,10 @@ function toNextResponse(response: { status: number; body: Record<string, unknown
|
||||
return NextResponse.json(response.body, { status: response.status });
|
||||
}
|
||||
|
||||
function listReportView(value: unknown) {
|
||||
function listReportView(
|
||||
value: unknown,
|
||||
failure?: ReturnType<typeof summarizePersonalReportFailure> | null,
|
||||
) {
|
||||
const row = value && typeof value === "object" ? value as Record<string, unknown> : {};
|
||||
return {
|
||||
id: typeof row.id === "string" ? row.id : "",
|
||||
@@ -71,6 +75,7 @@ function listReportView(value: unknown) {
|
||||
completedAt: row.completed_at == null || row.completed_at === ""
|
||||
? null
|
||||
: reportListTimestamp(row.completed_at) || null,
|
||||
...(failure?.summary ? { failureSummary: failure.summary } : {}),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -89,9 +94,39 @@ export async function GET() {
|
||||
.order("created_at", { ascending: false })
|
||||
.limit(20);
|
||||
if (error) throw error;
|
||||
return NextResponse.json({
|
||||
reports: Array.isArray(data) ? data.map(listReportView).filter((report) => report.id) : [],
|
||||
});
|
||||
const reports = Array.isArray(data) ? data.map((row) => listReportView(row)).filter((report) => report.id) : [];
|
||||
const failed = reports.filter((report) => report.status === "failed" && report.requestId);
|
||||
if (failed.length > 0) {
|
||||
const { data: sections, error: sectionError } = await supabase
|
||||
.from("personal_report_sections")
|
||||
.select("request_id, status, last_error_code")
|
||||
.eq("user_id", user.id)
|
||||
.in("request_id", failed.map((report) => report.requestId));
|
||||
if (sectionError) throw sectionError;
|
||||
const grouped = new Map<string, Array<{ status: string; lastErrorCode: string | null }>>();
|
||||
for (const row of Array.isArray(sections) ? sections : []) {
|
||||
const requestId = typeof row.request_id === "string" ? row.request_id : "";
|
||||
if (!requestId) continue;
|
||||
const list = grouped.get(requestId) ?? [];
|
||||
list.push({
|
||||
status: typeof row.status === "string" ? row.status : "pending",
|
||||
lastErrorCode: typeof row.last_error_code === "string" ? row.last_error_code : null,
|
||||
});
|
||||
grouped.set(requestId, list);
|
||||
}
|
||||
for (const [index, report] of reports.entries()) {
|
||||
if (report.status !== "failed") continue;
|
||||
const source = Array.isArray(data) ? data.find((row) => (
|
||||
row && typeof row === "object" && (row as { id?: unknown }).id === report.id
|
||||
)) : null;
|
||||
reports[index] = listReportView(source, summarizePersonalReportFailure({
|
||||
themeCount: report.themes.length,
|
||||
sections: grouped.get(report.requestId) ?? [],
|
||||
failureCode: report.failureCode,
|
||||
}));
|
||||
}
|
||||
}
|
||||
return NextResponse.json({ reports });
|
||||
} catch (error) {
|
||||
if (isSupabaseConfigurationError(error)) {
|
||||
return NextResponse.json(
|
||||
|
||||
Reference in New Issue
Block a user