Release/test 合并上生产 #2
@@ -56,6 +56,8 @@ describe('CarrierRatesView price-book source contract', () => {
|
||||
expect(source).toContain('sourceDestinationName')
|
||||
expect(source).toContain('autoSelectExactWarehouseMappings')
|
||||
expect(source).toContain("'DUPLICATE_RATE'")
|
||||
expect(source).toContain('.channel-tabs { width: 100%; min-width: 0;')
|
||||
expect(source).toContain('.channel-preview :deep(.el-table) { width: 100%; max-width: 100%; min-width: 0; }')
|
||||
expect(source).not.toContain('tenantId')
|
||||
expect(source).not.toContain('客户选择')
|
||||
})
|
||||
|
||||
@@ -847,7 +847,8 @@ onMounted(() => {
|
||||
.mapping-load-error { display: flex; align-items: center; justify-content: space-between; gap: 14px; padding: 14px; border: 1px solid color-mix(in srgb, var(--cc-danger) 24%, var(--cc-border)); border-radius: 8px; background: #fff; }
|
||||
.mapping-load-error strong { color: var(--cc-danger); font-size: 13px; }
|
||||
.mapping-load-error p { margin: 4px 0; color: var(--cc-text); font-size: 12px; }
|
||||
.channel-tabs { margin-bottom: 0; }
|
||||
.channel-tabs { width: 100%; min-width: 0; margin-bottom: 0; overflow: hidden; }
|
||||
.channel-preview :deep(.el-table) { width: 100%; max-width: 100%; min-width: 0; }
|
||||
.channel-meta { display: flex; flex-wrap: wrap; gap: 6px 14px; margin: -4px 0 8px; color: var(--cc-muted); font-size: 12px; }
|
||||
@media (max-width: 720px) {
|
||||
.table-actions :deep(.el-button) { min-height: 40px; }
|
||||
|
||||
@@ -5,7 +5,9 @@ import {
|
||||
correctWeightResult,
|
||||
createWeightResult,
|
||||
downloadWeightImage,
|
||||
getWeightOcrRawText,
|
||||
getWeightWorkspace,
|
||||
normalizeWeightConfidence,
|
||||
retryWeightRecognition,
|
||||
uploadWeightImage,
|
||||
} from './weightOcr'
|
||||
@@ -23,6 +25,31 @@ describe('inbound weight OCR API', () => {
|
||||
vi.mocked(http.put).mockReset()
|
||||
})
|
||||
|
||||
it('normalizes OCR confidence to the firstleg decimal contract', () => {
|
||||
expect(normalizeWeightConfidence(0.5097191334)).toBe(0.50972)
|
||||
expect(normalizeWeightConfidence(1.2)).toBe(1)
|
||||
expect(normalizeWeightConfidence(-0.2)).toBe(0)
|
||||
expect(normalizeWeightConfidence(Number.NaN)).toBeNull()
|
||||
expect(normalizeWeightConfidence(null)).toBeNull()
|
||||
})
|
||||
|
||||
it('accepts current and compatible OCR raw-text response fields', () => {
|
||||
const upload = {
|
||||
fileId: '1',
|
||||
fileName: 'weight.jpg',
|
||||
contentType: 'image/jpeg',
|
||||
sizeBytes: 100,
|
||||
recognitionStatus: 'SUCCESS',
|
||||
weightValue: 12.5,
|
||||
weightUnit: 'KG',
|
||||
confidence: 0.98,
|
||||
error: null,
|
||||
}
|
||||
expect(getWeightOcrRawText({ ...upload, ocrRawText: ' 12.5 KG ' })).toBe('12.5 KG')
|
||||
expect(getWeightOcrRawText({ ...upload, rawText: 'display: 12.5' })).toBe('display: 12.5')
|
||||
expect(getWeightOcrRawText(upload)).toBeNull()
|
||||
})
|
||||
|
||||
it('loads the measurement workspace using the real firstleg path', async () => {
|
||||
const detail = { inboundId: '9007199254740993', weightResults: [] }
|
||||
vi.mocked(http.get).mockResolvedValue({ code: '200', message: 'success', data: detail })
|
||||
@@ -61,13 +88,13 @@ describe('inbound weight OCR API', () => {
|
||||
ocrRawText: null,
|
||||
ocrWeightValue: 12.5,
|
||||
ocrWeightUnit: 'KG',
|
||||
confidence: 0.98,
|
||||
confidence: 0.5097191334,
|
||||
}
|
||||
await createWeightResult('10001', '20001', createPayload)
|
||||
expect(http.post).toHaveBeenNthCalledWith(
|
||||
1,
|
||||
'/platform/v1/first-leg/inbounds/20001/weight-results',
|
||||
createPayload,
|
||||
{ ...createPayload, confidence: 0.50972 },
|
||||
{ params: { tenantId: '10001' } },
|
||||
)
|
||||
|
||||
@@ -97,4 +124,3 @@ describe('inbound weight OCR API', () => {
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@@ -12,11 +12,25 @@ import type {
|
||||
const measurementBasePath = '/platform/v1/first-leg/inbounds'
|
||||
const imageBasePath = '/platform/v1/files/measurements'
|
||||
const maxErrorEnvelopeBytes = 1024 * 1024
|
||||
const confidenceScale = 100_000
|
||||
|
||||
function segment(value: string) {
|
||||
return encodeURIComponent(value)
|
||||
}
|
||||
|
||||
export function normalizeWeightConfidence(value: number | null) {
|
||||
if (value == null || !Number.isFinite(value)) return null
|
||||
const clamped = Math.min(1, Math.max(0, value))
|
||||
return Math.round(clamped * confidenceScale) / confidenceScale
|
||||
}
|
||||
|
||||
export function getWeightOcrRawText(result: WeightImageUploadResult) {
|
||||
const rawText = result.ocrRawText ?? result.rawText
|
||||
if (typeof rawText !== 'string') return null
|
||||
const normalized = rawText.trim()
|
||||
return normalized ? normalized.slice(0, 1000) : null
|
||||
}
|
||||
|
||||
async function parseBlobError(blob: Blob, status?: number) {
|
||||
if (!blob.type.toLowerCase().includes('json')) return null
|
||||
if (blob.size > maxErrorEnvelopeBytes) {
|
||||
@@ -85,9 +99,13 @@ export async function createWeightResult(
|
||||
inboundId: string,
|
||||
payload: CreateWeightResultRequest,
|
||||
) {
|
||||
const normalizedPayload = {
|
||||
...payload,
|
||||
confidence: normalizeWeightConfidence(payload.confidence),
|
||||
}
|
||||
const response = await http.post<ApiEnvelope<WeightResultResource>, ApiEnvelope<WeightResultResource>>(
|
||||
`${measurementBasePath}/${segment(inboundId)}/weight-results`,
|
||||
payload,
|
||||
normalizedPayload,
|
||||
{ params: { tenantId } },
|
||||
)
|
||||
return unwrapApiResponse(response)
|
||||
@@ -120,4 +138,3 @@ export async function confirmWeightResult(
|
||||
)
|
||||
return unwrapApiResponse(response)
|
||||
}
|
||||
|
||||
|
||||
@@ -29,6 +29,7 @@ describe('WeightOcrPanel contract', () => {
|
||||
expect(source).toContain("['image/jpeg', 'image/png', 'image/bmp']")
|
||||
expect(source).toContain('5 * 1024 * 1024')
|
||||
expect(source).toContain('图片服务未返回 OCR 原文')
|
||||
expect(source).toContain('ocrRawText: getWeightOcrRawText(upload)')
|
||||
expect(source).toContain("adjustmentReason: 'OCR 重试识别结果'")
|
||||
})
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ import {
|
||||
correctWeightResult,
|
||||
createWeightResult,
|
||||
downloadWeightImage,
|
||||
getWeightOcrRawText,
|
||||
getWeightWorkspace,
|
||||
retryWeightRecognition,
|
||||
uploadWeightImage,
|
||||
@@ -153,7 +154,7 @@ async function handleFileChange(event: Event) {
|
||||
await createWeightResult(customerId.value, props.inboundId, {
|
||||
fileId: String(upload.fileId),
|
||||
ocrProvider: 'FILE_WEIGHT_OCR',
|
||||
ocrRawText: null,
|
||||
ocrRawText: getWeightOcrRawText(upload),
|
||||
ocrWeightValue: upload.weightValue,
|
||||
ocrWeightUnit: upload.weightUnit,
|
||||
confidence: upload.confidence,
|
||||
|
||||
@@ -37,6 +37,8 @@ export interface WeightImageUploadResult {
|
||||
weightValue: number | null
|
||||
weightUnit: string | null
|
||||
confidence: number | null
|
||||
ocrRawText?: string | null
|
||||
rawText?: string | null
|
||||
error: string | null
|
||||
}
|
||||
|
||||
@@ -59,4 +61,3 @@ export interface CorrectWeightResultRequest {
|
||||
export interface ConfirmWeightResultRequest {
|
||||
version: number
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user