增加铁路OCR金额字段安全诊断(07-28-travel-reimbursement-guide)
这个提交包含在:
@@ -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 }),
|
||||
|
||||
在新工单中引用
屏蔽一个用户