/* * 변환·담김의 정체가 **무엇이 무엇으로 되었나**로 갈리나. * * ── 왜 필요한가 (2026-09-07) ──────────────────────────────────────────────── * 이 자리가 없어서 **사실 하나가 없어졌습니다.** 거절이 아니라 되풀이로 세어져서 경고도 없었습니다. * * ``` * plant outbox seq 37 in LOT-CT-SIDE-001 24개 → out LOT-CT-MAIN-003 12개 * plant outbox seq 42 in LOT-CT-SIDE-001 18개 → out LOT-CT-MAIN-004 9개 * transformationID · bizStep · 초까지 전부 같다 * 저널에 남은 것 LOT-CT-MAIN-003 하나 * ``` * * `LOT-CT-MAIN-004` 를 어느 패널로 만들었는지 트윈이 답하지 못했습니다. 인티그레이션 레인이 * 원본과 저널의 건수를 맞춰 보다 찾았습니다 — 4건이 나갔고 3건이 앉았습니다. * * 아래 값은 그 두 봉투에서 온 것입니다. 지어낸 모양이 아닙니다. */ import test from 'node:test' import assert from 'node:assert/strict' import { factIdentity } from '../server/engine/ingest-dedupe.js' const AT = '2026-09-05T05:00:00.000Z' const transformation = (inQty: number, outClass: string, outQty: number) => ({ eventId: `evt-${outClass}-${outQty}`, eventType: 'epcis.TransformationEvent', eventTime: AT, data: { type: 'TransformationEvent', bizStep: 'urn:epcglobal:cbv:bizstep:commissioning', transformationID: 'WO-20260831-001', inputQuantityList: [{ epcClass: 'LOT-CT-SIDE-001', quantity: inQty }], outputQuantityList: [{ epcClass: outClass, quantity: outQty }] } }) test('산출 로트가 다르면 다른 사실이다 — 이것이 실제로 없어진 자리다', () => { const a = factIdentity(transformation(24, 'LOT-CT-MAIN-003', 12)) const b = factIdentity(transformation(18, 'LOT-CT-MAIN-004', 9)) assert.ok(a, '정체를 만들지 못하면 dedupe 밖이다') assert.notEqual(a, b, '같은 오더·같은 공정·같은 초라도 산출이 다르면 다른 변환이다') }) test('똑같은 봉투가 두 번 오면 한 사실이다 — 갈라 놓기만 하면 되풀이를 못 막는다', () => { assert.equal( factIdentity(transformation(24, 'LOT-CT-MAIN-003', 12)), factIdentity(transformation(24, 'LOT-CT-MAIN-003', 12)) ) }) test('산출 이름이 같고 수량만 다르면 다른 사실이다 — 분할이 그 모양이다', () => { const a = factIdentity(transformation(24, 'LOT-CT-MAIN-003', 12)) const b = factIdentity(transformation(24, 'LOT-CT-MAIN-003', 6)) assert.notEqual(a, b) }) test('입력 수량만 다르면 다른 사실이다 — 산출이 같아도 소비가 다르다', () => { const a = factIdentity(transformation(24, 'LOT-CT-MAIN-003', 12)) const b = factIdentity(transformation(18, 'LOT-CT-MAIN-003', 12)) assert.notEqual(a, b) }) test('오더가 다르면 다른 사실이다 — 같은 공정을 두 오더로 같은 초에 돌릴 수 있다', () => { const withTx = (tx: string) => { const e = transformation(24, 'LOT-CT-MAIN-003', 12) as any e.data.bizTransactionList = [{ type: 'urn:epcglobal:cbv:btt:prodorder', bizTransaction: tx }] return factIdentity(e) } assert.notEqual(withTx('urn:x:bt:WO-A'), withTx('urn:x:bt:WO-B')) }) test('담김의 자식이 다르면 다른 사실이다 — 한 종류만 고치면 나머지에 같은 결함이 남는다', () => { const aggregation = (child: string) => ({ eventId: `agg-${child}`, eventType: 'epcis.AggregationEvent', eventTime: AT, data: { type: 'AggregationEvent', action: 'ADD', bizStep: 'urn:epcglobal:cbv:bizstep:packing', parentID: 'urn:epc:id:sscc:0614141.1234567890', childQuantityList: [{ epcClass: child, quantity: 10 }] } }) assert.notEqual(factIdentity(aggregation('LOT-A')), factIdentity(aggregation('LOT-B'))) }) test('수량 목록 전체를 본다 — 첫 원소만 보면 뒤가 다른 둘이 뭉친다', () => { const twoLines = (second: string) => ({ eventId: `evt-${second}`, eventType: 'epcis.TransformationEvent', eventTime: AT, data: { type: 'TransformationEvent', bizStep: 'urn:epcglobal:cbv:bizstep:commissioning', transformationID: 'WO-20260831-001', outputQuantityList: [ { epcClass: 'LOT-SAME', quantity: 1 }, { epcClass: second, quantity: 1 } ] } }) assert.notEqual(factIdentity(twoLines('LOT-X')), factIdentity(twoLines('LOT-Y'))) })