增加铁路OCR金额字段安全诊断(07-28-travel-reimbursement-guide)
ci-e2e / e2e (push) Canceled after 0s
ci-minimal / check-minimal (push) Canceled after 0s

这个提交包含在:
2026-07-29 15:07:58 +08:00
父节点 26562255a0
当前提交 5f2788ccac
共修改 7 个文件,包含 179 行新增和 1 行删除
@@ -106,6 +106,60 @@ describe("check-ocr-provider-samples", () => {
expect(summary.missingRequiredFields).toEqual(["buyerName"]);
});
it("exposes only boolean railway amount diagnostics", async () => {
const summary = await liveRunner.runLiveSampleCheck({
alias: "SAMPLE-PDF-TRAIN-DIAGNOSTIC",
buffer: syntheticPdf(),
config: providerConfig(),
runOcr: vi.fn().mockResolvedValue(
normalizedResult({
documentSubtype: "ElectronicTrainTicketFull",
documentCategory: "electronic_train_ticket",
invoiceCode: null,
totalAmount: null,
sellerName: null,
diagnostics: {
trainAmountFields: {
total: {
present: true,
isString: true,
isNumber: false,
nonEmpty: true,
parseable: false,
value: "forbidden-total-marker",
},
fare: {
present: false,
isString: false,
isNumber: false,
nonEmpty: false,
parseable: false,
value: "forbidden-fare-marker",
},
},
},
}),
),
});
const serialized = JSON.stringify(summary);
expect(summary.status).toBe("HOLD");
expect(summary.trainAmountDiagnostics).toEqual({
totalPresent: true,
totalIsString: true,
totalIsNumber: false,
totalNonEmpty: true,
totalParseable: false,
farePresent: false,
fareIsString: false,
fareIsNumber: false,
fareNonEmpty: false,
fareParseable: false,
});
expect(serialized).not.toContain("forbidden-total-marker");
expect(serialized).not.toContain("forbidden-fare-marker");
});
it("never exposes provider raw fields or normalized business values", async () => {
const summary = await liveRunner.runLiveSampleCheck({
alias: "SAMPLE-PDF-PRIVACY",
@@ -139,12 +139,66 @@ describe("Tencent general invoice OCR", () => {
totalAmount: "553.00",
sellerName: null,
buyerName: "合成公司",
diagnostics: {
trainAmountFields: {
total: {
present: true,
isString: true,
isNumber: false,
nonEmpty: true,
parseable: true,
},
fare: {
present: false,
isString: false,
isNumber: false,
nonEmpty: false,
parseable: false,
},
},
},
});
expect(buildMissingRequiredOcrError("vat", normalized)).toBeNull();
expect(shouldDeduplicateByInvoiceNumberOnly(normalized)).toBe(true);
expect(JSON.stringify(normalized)).not.toContain("SENSITIVE-");
});
it("reports only boolean railway amount field diagnostics", () => {
const normalized = normalizeGeneralInvoiceResult({
MixedInvoiceItems: [
buildInvoiceItem("ElectronicTrainTicketFull", "ElectronicTrainTicketFull", {
Number: "251234",
Date: "2026-07-26",
Fare: "not-a-number-sensitive-marker",
Buyer: "合成公司",
}),
],
});
expect(normalized.diagnostics).toEqual({
trainAmountFields: {
total: {
present: false,
isString: false,
isNumber: false,
nonEmpty: false,
parseable: false,
},
fare: {
present: true,
isString: true,
isNumber: false,
nonEmpty: true,
parseable: false,
},
},
});
expect(JSON.stringify(normalized.diagnostics)).not.toContain("sensitive-marker");
expect(sanitizeOcrResultForPersistence("tencent", normalized)).not.toHaveProperty(
"diagnostics",
);
});
it("returns safe terminal quality results for zero, multiple and unsupported invoices", () => {
expect(normalizeGeneralInvoiceResult({ MixedInvoiceItems: [] })).toEqual(
expect.objectContaining({ qualityError: "invoice_not_detected", documentCount: 0 }),