修复铁路OCR空金额回退(07-28-travel-reimbursement-guide)
这个提交包含在:
@@ -165,6 +165,9 @@ const ocrRan = invoice.ocr_attempts > 0
|
||||
`sellerName`.
|
||||
- `electronic_train_ticket`: `invoiceNumber`, `invoiceDate`, `totalAmount`,
|
||||
`buyerName`.
|
||||
- Railway `totalAmount` uses the first individually parseable value from
|
||||
Tencent `Total`, then `Fare`. Parse each field before applying fallback:
|
||||
an empty or otherwise unparseable `Total` must not block a parseable `Fare`.
|
||||
- Never invent an invoice code from an invoice number or vice versa. Known
|
||||
no-code full-VAT and railway tickets use invoice-number-only duplicate
|
||||
detection; other categories retain code-plus-number semantics.
|
||||
@@ -225,6 +228,9 @@ const ocrRan = invoice.ocr_attempts > 0
|
||||
- OCR service/job tests assert category-specific required fields,
|
||||
number-only dedupe only for known no-code categories, and no retry on terminal
|
||||
errors.
|
||||
- Railway adapter tests must cover an empty `Total` with a parseable `Fare` and
|
||||
assert that normalized `totalAmount` uses `Fare` without emitting the source
|
||||
amount in diagnostics.
|
||||
- Cache tests seed a legacy Tencent result containing `raw` and sensitive
|
||||
fields, then assert both normal and race cache paths return and persist only
|
||||
the allow-listed shape.
|
||||
@@ -251,6 +257,16 @@ const persisted = sanitizeOcrResultForPersistence("tencent", normalized);
|
||||
await prisma.ocrResult.create({ data: { result_json: persisted } });
|
||||
```
|
||||
|
||||
For railway amount fallback, do not choose the raw source before parsing:
|
||||
|
||||
```js
|
||||
// Wrong: an empty string is not nullish, so it blocks Fare.
|
||||
toNumberString(info?.Total ?? info?.Fare);
|
||||
|
||||
// Correct: only a successfully parsed Total blocks Fare.
|
||||
toNumberString(info?.Total) ?? toNumberString(info?.Fare);
|
||||
```
|
||||
|
||||
## Scenario: Live OCR Sample Stdin Probe
|
||||
|
||||
### 1. Scope / Trigger
|
||||
|
||||
在新工单中引用
屏蔽一个用户