OINV1 加固真实发票OCR脱敏报告

这个提交包含在:
2026-05-23 21:03:23 +08:00
父节点 35db715aa1
当前提交 92b004592a
共修改 5 个文件,包含 129 行新增和 53 行删除
+46 -4
查看文件
@@ -17,6 +17,20 @@ export type LiveInvoiceOcrResult = {
invoiceDate: string | null;
};
export type SafeLiveInvoiceOcrEvidence = {
alias: string;
status: LiveInvoiceOcrStatus;
durationMs: number;
reasonCategory: "available" | "duplicate_or_same_invoice" | "already_used" | "ocr_failed" | "timeout_or_pending" | "unknown";
fieldHits: {
invoiceId: boolean;
invoiceCodeOrNumber: boolean;
sellerName: boolean;
totalAmount: boolean;
invoiceDate: boolean;
};
};
type UploadInvoiceResponse = {
data?: {
invoicePoolId?: string | null;
@@ -35,6 +49,35 @@ function extractField(detailText: string, label: string) {
return match?.[1]?.trim() || null;
}
function categorizeText(text: string) {
if (/重复|相同发票|duplicate/i.test(text)) return "duplicate_or_same_invoice";
if (/已用于报销|已使用|used/i.test(text)) return "already_used";
if (/超时|timeout|pending/i.test(text)) return "timeout_or_pending";
if (/失败|识别失败|ocr_failed|failed/i.test(text)) return "ocr_failed";
return "unknown";
}
export function categorizeLiveInvoiceOcrResult(result: LiveInvoiceOcrResult): SafeLiveInvoiceOcrEvidence["reasonCategory"] {
if (result.status === "available") return "available";
return categorizeText(`${result.bannerText}\n${result.detailText}`);
}
export function createSafeLiveInvoiceOcrEvidence(result: LiveInvoiceOcrResult, alias: string): SafeLiveInvoiceOcrEvidence {
return {
alias,
status: result.status,
durationMs: result.durationMs,
reasonCategory: categorizeLiveInvoiceOcrResult(result),
fieldHits: {
invoiceId: Boolean(result.invoiceId),
invoiceCodeOrNumber: Boolean(result.invoiceCode || result.invoiceNumber),
sellerName: Boolean(result.sellerName),
totalAmount: Boolean(result.totalAmount && result.totalAmount !== "-"),
invoiceDate: Boolean(result.invoiceDate),
},
};
}
export function resolveLiveOcrUser(fallback: E2EUser): E2EUser {
const identifier =
process.env.PLAYWRIGHT_OCR_LIVE_IDENTIFIER?.trim() ||
@@ -124,14 +167,13 @@ export async function uploadLiveInvoiceAndWait(page: Page, invoiceFilePath: stri
} catch (error) {
const detailText = await detailPanel.innerText().catch(() => "");
const bannerText = await page.getByTestId("invoice-ocr-status-banner").innerText().catch(() => "");
const invoiceId = uploadedInvoiceId ?? extractField(detailText, "票据ID:");
const reasonCategory = categorizeText(`${bannerText}\n${detailText}`);
throw new Error(
[
"live 腾讯 OCR 等待终态超时",
`invoiceId=${invoiceId ?? "unknown"}`,
`invoiceIdPresent=${Boolean(uploadedInvoiceId ?? extractField(detailText, "票据ID:"))}`,
`timeoutMs=${timeout}`,
`detail=${detailText || "<empty>"}`,
`banner=${bannerText || "<empty>"}`,
`reasonCategory=${reasonCategory}`,
`cause=${error instanceof Error ? error.message : String(error)}`,
].join("\n"),
);