|
6 | 6 | AGE_MAPPINGS, |
7 | 7 | VERIFIED_MAPPINGS, |
8 | 8 | } from './defaultMappings.ts' |
| 9 | +import { normalizeDateString, isDateField } from './dateUtils.ts' |
9 | 10 | import { COUNTRY_FIELD_MAPPINGS } from '../countryData/countryMappings.ts' |
10 | 11 | import { NAME_MAPPINGS } from '../countryData/nameMappings.ts' |
11 | 12 | import { ADDRESS_MAPPINGS } from '../countryData/addressMappings.ts' |
@@ -124,7 +125,11 @@ export function transformCorrection( |
124 | 125 |
|
125 | 126 | const v1OutputDeclaration = |
126 | 127 | historyItem.output?.reduce((acc: Record<string, any>, curr: any) => { |
127 | | - acc[`${event}.${curr.valueCode}.${curr.valueId}`] = curr.value |
| 128 | + // Normalize date strings in output to ensure proper zero-padding |
| 129 | + const value = isDateField(curr.valueId) |
| 130 | + ? normalizeDateString(curr.value) |
| 131 | + : curr.value |
| 132 | + acc[`${event}.${curr.valueCode}.${curr.valueId}`] = value |
128 | 133 | return acc |
129 | 134 | }, {}) || {} |
130 | 135 |
|
@@ -469,34 +474,114 @@ export function transform( |
469 | 474 | return postProcess(documents) |
470 | 475 | } |
471 | 476 |
|
| 477 | +/** |
| 478 | + * Deep merge two objects, with priority given to source values |
| 479 | + */ |
| 480 | +function deepMerge(target: any, source: any): any { |
| 481 | + const result = { ...target } |
| 482 | + |
| 483 | + for (const key in source) { |
| 484 | + if (source.hasOwnProperty(key)) { |
| 485 | + const sourceValue = source[key] |
| 486 | + const targetValue = target[key] |
| 487 | + |
| 488 | + // If both are plain objects, merge recursively |
| 489 | + if ( |
| 490 | + sourceValue && |
| 491 | + typeof sourceValue === 'object' && |
| 492 | + !Array.isArray(sourceValue) && |
| 493 | + targetValue && |
| 494 | + typeof targetValue === 'object' && |
| 495 | + !Array.isArray(targetValue) |
| 496 | + ) { |
| 497 | + result[key] = deepMerge(targetValue, sourceValue) |
| 498 | + } else { |
| 499 | + // Otherwise, use source value |
| 500 | + result[key] = sourceValue |
| 501 | + } |
| 502 | + } |
| 503 | + } |
| 504 | + |
| 505 | + return result |
| 506 | +} |
| 507 | + |
472 | 508 | function postProcess(documents: TransformedDocument): TransformedDocument { |
473 | 509 | const correctionResolverKeys = Object.keys(correctionResolver) |
474 | 510 |
|
| 511 | + // Step 1: Build a map of current declaration state at each action |
| 512 | + // We start from the final declaration and work backwards |
| 513 | + let currentDeclaration: Record<string, any> = {} |
| 514 | + |
| 515 | + // Find the final declaration by merging all DECLARE/REGISTER/VALIDATE declarations |
| 516 | + for (const action of documents.actions) { |
| 517 | + if ( |
| 518 | + (action.type === 'DECLARE' || |
| 519 | + action.type === 'REGISTER' || |
| 520 | + action.type === 'VALIDATE') && |
| 521 | + action.declaration && |
| 522 | + Object.keys(action.declaration).length > 0 |
| 523 | + ) { |
| 524 | + currentDeclaration = deepMerge(currentDeclaration, action.declaration) |
| 525 | + } |
| 526 | + } |
| 527 | + |
| 528 | + // Step 2: Process corrections in reverse order to reverse-engineer the original state |
| 529 | + const corrections: Array<{ index: number; action: Action }> = [] |
| 530 | + |
475 | 531 | for (let i = 0; i < documents.actions.length; i++) { |
476 | | - const action = documents.actions[i] |
| 532 | + if (documents.actions[i].type === 'REQUEST_CORRECTION') { |
| 533 | + corrections.push({ index: i, action: documents.actions[i] }) |
| 534 | + } |
| 535 | + } |
477 | 536 |
|
478 | | - if (action.type === 'REQUEST_CORRECTION' && action.annotation) { |
479 | | - // Filter out correctionResolver fields from the annotation |
480 | | - const filteredAnnotation = Object.fromEntries( |
481 | | - Object.entries(action.annotation).filter( |
482 | | - ([key]) => !correctionResolverKeys.includes(key) |
483 | | - ) |
| 537 | + // Process corrections from newest to oldest to reverse engineer |
| 538 | + for (let i = corrections.length - 1; i >= 0; i--) { |
| 539 | + const { action } = corrections[i] |
| 540 | + |
| 541 | + if (!action.annotation || !action.declaration) { |
| 542 | + continue |
| 543 | + } |
| 544 | + |
| 545 | + // Filter out correctionResolver metadata fields from annotation |
| 546 | + const filteredAnnotation = Object.fromEntries( |
| 547 | + Object.entries(action.annotation).filter( |
| 548 | + ([key]) => !correctionResolverKeys.includes(key) |
484 | 549 | ) |
| 550 | + ) |
485 | 551 |
|
486 | | - // Find the first action before this one with a non-empty declaration |
487 | | - for (let j = i - 1; j >= 0; j--) { |
488 | | - const previousAction = documents.actions[j] |
489 | | - |
490 | | - if ( |
491 | | - previousAction.declaration && |
492 | | - typeof previousAction.declaration === 'object' && |
493 | | - Object.keys(previousAction.declaration).length > 0 |
494 | | - ) { |
495 | | - previousAction.declaration = filteredAnnotation |
496 | | - break |
497 | | - } |
| 552 | + // First, reverse the correction: for each field in the correction's declaration, |
| 553 | + // replace the current state with the input value (from filteredAnnotation) |
| 554 | + for (const key of Object.keys(action.declaration)) { |
| 555 | + if (filteredAnnotation.hasOwnProperty(key)) { |
| 556 | + currentDeclaration[key] = filteredAnnotation[key] |
498 | 557 | } |
499 | 558 | } |
| 559 | + |
| 560 | + // Now set the annotation to the reversed state (which is the state BEFORE this correction) |
| 561 | + const newAnnotation = { |
| 562 | + ...currentDeclaration, |
| 563 | + ...Object.fromEntries( |
| 564 | + Object.entries(action.annotation).filter(([key]) => |
| 565 | + correctionResolverKeys.includes(key) |
| 566 | + ) |
| 567 | + ), |
| 568 | + } |
| 569 | + |
| 570 | + action.annotation = newAnnotation |
| 571 | + } |
| 572 | + |
| 573 | + // Step 3: Update the base DECLARE/REGISTER/VALIDATE actions with the reverse-engineered state |
| 574 | + // Find the first action with a non-empty declaration |
| 575 | + for (const action of documents.actions) { |
| 576 | + if ( |
| 577 | + (action.type === 'DECLARE' || |
| 578 | + action.type === 'REGISTER' || |
| 579 | + action.type === 'VALIDATE') && |
| 580 | + action.declaration && |
| 581 | + Object.keys(action.declaration).length > 0 |
| 582 | + ) { |
| 583 | + action.declaration = { ...currentDeclaration } |
| 584 | + } |
500 | 585 | } |
501 | 586 |
|
502 | 587 | return documents |
|
0 commit comments