데이터 모델 · 식별자 계약 (DM-*)

02-requirements/data-model.html · LogiNippon · TRD · 2026-06-13 · 신뢰도 라벨 확인/추정/설계

이 문서는 LogiNippon의 D1 스키마를 규범 요구사항으로 동결한다. techspec 02-data-model/model.md의 설계와 as-built 마이그레이션(0001_init.sql·0002_auth.sql·0003_geofence_membership.sql)을 대조해 11개 핵심 테이블, 타임스탬프·식별자·dedup 계약, 이벤트 분류·정밀도 매트릭스, shipment 상태머신, 테넌트 격리·용량 한계, 그리고 표준 식별자 부재를 메우는 送り状番号 정규화 레이어를 고정한다. 모든 수치는 마스터 §4 정규화 수치표에서 글자 그대로 인용했으며, 진입 단계의 초기 확정 목표(설계)로서 운영 베이스라인으로 분기별 조정한다.

3계층 모델. 데이터는 Shipment → Stop → Event 3계층이다(ADR-0005). 일본 국내 트럭은 복합운송 구간이 1~2개라 5계층(Order→Shipment→Leg→Stop→Event) 대신 3계층으로 시작한다 추정. Order는 shipper_ref로 흡수, Leg(다중 캐리어 구간)는 Phase 2. 이벤트는 불변(immutable)이며 다중 소스에서 같은 사실이 중복 도착한다고 가정한다(Queues at-least-once 정합).

1. 스키마 개요 (DM-SCHEMA-001)

11
핵심 테이블(0001)
3
계층(Shipment·Stop·Event)
3 + (0004)
적용 마이그레이션 + 웹훅 신설 권고
100~200
선등록 지오펜스 거점 추정
DM-SCHEMA-001 정규 스키마 동결 — 11 테이블 + 주요 컬럼·관계 P0·MustPhase 1I 검사

요구. 시스템은 아래 11개 D1 테이블(tenant, carrier, vehicle, driver, geofence, geofence_cell, shipment, stop, event, event_code_map, regulatory_report)을 반드시(MUST) 마이그레이션 0001_init.sql이 정의한 컬럼·CHECK 제약·UNIQUE·인덱스 그대로 유지해야 한다. 적용된 마이그레이션 파일은 불변이며, 변경은 새 번호 마이그레이션으로만 전진(forward-only) 한다.

수용기준

  • Given 신규 D1 인스턴스, When 0001→0002→0003을 순서대로 적용, Then 11 코어 테이블 + auth 2테이블(auth_credential·api_key) + geofence_membership이 생성되고 모든 CHECK enum이 본 문서 §5·§6 카탈로그와 일치한다.
  • Given 스키마, When 컬럼/제약을 본 문서와 대조, Then 차이가 0이다(이 페이지가 단일 진실원).
근거 techspec 02-data-model/model.md · 구현 server/migrations/0001_init.sql DONE · 검증 마이그레이션 적용 + tenant-scoped repo 테스트

1.1 테이블 개요 · 마이그레이션 매핑

테이블역할PK주요 컬럼 / 관계마이그레이션상태
tenant멀티테넌시 격리 루트(화주/운송사/3PL)tenant_idtype IN ('SHIPPER','CARRIER','3PL')0001DONE
carrier운송사 · 多重下請け 계층carrier_idtier IN ('PRIME','SUB1','SUB2','SUB3') (元請/下請/孫請), parent_carrier_id 자기참조 → tenant0001DONE
vehicle차량(番号板)vehicle_idplate(개인정보), class IN ('LIGHT','MEDIUM','HEAVY')carrier0001DONE
driver운전자(個人情報保護法 마스킹)driver_iddisplay_name, consent_at(동의 시각) → carrier0001DONE
geofence거점 폴리곤 + 히스테리시스/荷待ち 임계geofence_idtype IN ('WAREHOUSE','CUSTOMER','PORT','BORDER'), geometry_geojson, h3_resolution, entry_buffer_m/exit_buffer_m, dwell_threshold_min0001DONE
geofence_cell폴리곤 H3 덮개 셀(진실 사본·재생성·분석)(h3_cell, geofence_id)kind IN ('interior','boundary'), resolutiongeofence0001DONE
shipment화물 1단위(Order 흡수)shipment_idtenant_id, shipper_ref(送り状番号), carrier/vehicle/driver_id, current_status, predicted_delivery_eta, eta_is_estimate0001DONE
stop정류점 · 荷待ち 측정 단위stop_idsequence, type IN ('PICKUP','DROPOFF','INTERMEDIATE'), actual_arrival_at/actual_departure_at/dwell_minutes, h3_r10, UNIQUE(shipment_id, sequence)0001DONE
event불변 이벤트(중복 수신 가정)event_idcanonical_code, raw_code, source_type, occurred_at/received_at, h3_r10, exception_flag, dedup_key UNIQUE0001DONE
event_code_mapraw→canonical 정규화 매핑(source_type, raw_code)canonical_code, confidence IN ('HIGH','MEDIUM','LOW')0001DONE
regulatory_report규제 산출물 메타(파일은 R2)report_idkind IN ('JITSUUNSO_KANRIBO','NIMACHI_RECORD','KOSOKU_TIME'), r2_keytenant0001DONE
아래는 as-built 보조 테이블(코어 11 외)
auth_credential사람 주체 로그인(PBKDF2)subject_idrole, pw_salt/hash/iterations, scopestenant/carrier0002DONE
api_key머신 클라이언트 키(해시 저장)key_idkey_hash, revoked, scopestenant0002DONE
geofence_membership지오펜스 OUTSIDE↔INSIDE 상태(히스테리시스/dwell)(shipment_id, geofence_id)state, entered_at, last_seen_at(out-of-order 가드), last_h3_cell0003DONE
웹훅 구독endpoint_secret·HMAC·전달상태(미정)구독 테이블 부재 → handleNotifyBatch가 ack-only stub0004 신설 권고ABSENT
0004 웹훅 마이그레이션 권고. 웹훅 구독·endpoint_secret·HMAC 서명·전달상태 테이블이 부재하여 handleNotifyBatch는 ack-only stub 상태다(as-built). 전달 계약(IR-WH-001)·재시도 래더(FR-DLV-WH-001) 구현 전, 0004_webhook_subscriptions.sql을 전진 마이그레이션으로 신설해야 한다.

1.2 핵심 DDL 발췌 (as-built, 0001)

CREATE TABLE shipment (
  shipment_id            TEXT PRIMARY KEY,
  tenant_id              TEXT NOT NULL REFERENCES tenant(tenant_id),
  shipper_ref            TEXT,        -- 送り状番号 또는 화주 PO (표준 식별자 부재 → 정규화 대상)
  carrier_id             TEXT REFERENCES carrier(carrier_id),
  vehicle_id             TEXT REFERENCES vehicle(vehicle_id),
  driver_id              TEXT REFERENCES driver(driver_id),
  mode                   TEXT NOT NULL DEFAULT 'TRUCK',
  current_status         TEXT NOT NULL DEFAULT 'CREATED',  -- canonical taxonomy
  planned_pickup_at      TEXT,
  planned_delivery_at    TEXT,
  predicted_delivery_eta TEXT,
  eta_is_estimate        INTEGER NOT NULL DEFAULT 1,
  created_at             TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ','now'))
);
CREATE INDEX idx_shipment_tenant_status ON shipment(tenant_id, current_status);

CREATE TABLE event (
  event_id        TEXT PRIMARY KEY,
  shipment_id     TEXT NOT NULL REFERENCES shipment(shipment_id),
  stop_id         TEXT REFERENCES stop(stop_id),
  canonical_code  TEXT NOT NULL,
  raw_code        TEXT,                                  -- 원본 코드 (감사 보존) 예: X12:214:AF
  source_type     TEXT NOT NULL CHECK (source_type IN
                  ('APP_GPS','GEOFENCE','TELEMATICS','CARRIER_API','EDI_X12','EDIFACT','MANUAL')),
  occurred_at     TEXT NOT NULL,                         -- 발생 시각(ISO8601+TZ)
  received_at     TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ','now')),
  lat REAL, lon REAL, h3_r10 TEXT, description TEXT,
  exception_flag  INTEGER NOT NULL DEFAULT 0,
  dedup_key       TEXT NOT NULL,
  UNIQUE (dedup_key)
);

2. 타임스탬프 계약 (DM-TS-001)

DM-TS-001 타임스탬프 +09:00 오프셋 통일 P0·MustPhase 1T 테스트

요구. 모든 시각 필드(occurred_at, actual_arrival_at, planned_* 등)는 ISO 8601 +09:00 오프셋을 명시해 저장(MUST)한다. 시스템은 오프셋이 없거나 잘못된 형식의 타임스탬프를 반드시 거부해야 한다. 荷待ち 법정 계산(dwell_minutes = actual_departure_at − actual_arrival_at)과 구속시간 산정이 이 정합성에 의존한다.

DDL DEFAULT의 'Z' 모순. 0001_init.sql의 모든 created_at/received_at DEFAULT는 strftime('%Y-%m-%dT%H:%M:%SZ','now') — 즉 UTC 'Z' 리터럴을 쓴다. 이는 +09:00 저장 규칙과 모순이다. +09:00 규칙으로 통일하고, 서버 기록 시각도 JST 오프셋으로 정규화한다(DEFAULT 리터럴은 0004+ 마이그레이션 또는 애플리케이션 계층 정규화로 교정). as-built geofence-engine.tsdedupKey()는 혼재 오프셋(Z, +09:00)을 Date.parse로 UTC 정규화 후 분 절단해 이 모순을 흡수한다(§4 참조).

수용기준

  • Given 인제스트/이벤트 페이로드, When 시각이 2026-06-13T09:00:00+09:00 형식, Then 그대로 저장된다.
  • Given 오프셋 없는 ...T09:00:00 또는 모호한 로컬 시각, When 인제스트, Then 422로 거부된다.
  • Given 같은 순간을 Z+09:00로 표현한 두 핑, When dedup, Then 동일 키로 1건만 저장(UTC 정규화 후 분 절단).
근거 마스터 §4.4 DM-TS-001 · model.md §D1 SQL DDL · 구현 server/migrations/0001_init.sql(DEFAULT 'Z' 모순) · server/src/lib/geofence-engine.ts dedupKey UTC 정규화 PARTIAL · 검증 잘못된 형식 거부 수용테스트(권장 신설)

3. 식별자 포맷 계약 (DM-ID-001)

DM-ID-001 불변 내부 ID + 외부 참조 분리 P0·MustPhase 1I 검사

요구. 내부 식별은 시스템이 발급한 불변 ID(shipment_id, stop_id, event_id, geofence_id, carrier_id 등)로만 한다(MUST). 외부 식별자(shipper_ref = 送り状番号 / 화주 PO)는 별개 필드로 보관하며 PK로 쓰지 않는다(SHOULD). event_id는 UUID, 모든 ID는 라우팅/API에서 안정적이어야 한다.

ID종류발급가변성
shipment_id내부 PK우리불변SHP-2025-00456
event_id내부 PK (UUID)우리불변UUID v4
shipper_ref외부 참조화주/운송사가변(정규화 대상)送り状番号 A-2025-0001, PO PO-98765
raw_code원본 이벤트 코드(감사)소스불변 보존X12:214:AF

수용기준

  • Given 동일 화물이 두 번 인입(상이한 shipper_ref), When 복합 키로 매핑, Then 단일 shipment_id로 수렴(§11 정규화 레이어).
  • Given 외부 시스템이 shipper_ref 변경, When 갱신, Then shipment_id는 불변 유지.
근거 model.md §送り状番号 · Research/data-model · 구현 0001_init.sql(shipment PK + shipper_ref 분리) DONE · 검증 repo 매핑 테스트

4. 중복 제거 키 계약 (DM-DEDUP-001)

DM-DEDUP-001 dedup_key 해시 공식 — at-least-once 멱등 P0·MustPhase 1T 테스트

요구. 이벤트 멱등 삽입을 위해 event.dedup_key반드시 다음 정규 공식으로 합성하고 D1 UNIQUE로 강제한다:

dedup_key = sha256( source_type | shipment_id | canonical_code | floor(occurred_at→분) [| stop_id] )

occurred_at분 단위 내림(floor) 후 사용한다(혼재 오프셋은 UTC 정규화 후 절단 — DM-TS-001 정합). 삽입은 INSERT ... ON CONFLICT(dedup_key) DO NOTHING으로 처리해 Queues at-least-once 재전달과 지오펜스 OUTSIDE→INSIDE + last_seen_at 단조 가드를 안전하게 만든다.

as-built 편차. 현재 geofence-engine.tsdedupKey()는 sha256 없이 평문 합성 GEOFENCE:{shipment_id}:{canonical}:{minute(UTC)}을 쓴다(소스 prefix 고정 GEOFENCE, stop_id 미포함). 정규 공식(sha256 · source_type 가변 · 선택적 stop_id)과 정렬은 후속 과제다. UNIQUE 제약·UTC 분 절단·멱등 의미는 이미 충족(Research/data-model 정합).

수용기준

  • Given 동일 (source_type, shipment_id, canonical_code, 분)의 핑 2건, When 두 번 삽입, Then 1건만 저장(ON CONFLICT DO NOTHING).
  • Given 같은 순간의 Z·+09:00 표현, When dedup, Then 동일 키.
  • Given 다른 분의 동일 이벤트, When 삽입, Then 별개 행(정상).
근거 마스터 §4.4 DM-DEDUP-001 · model.md §Event · 구현 server/src/lib/db.ts(ON CONFLICT 멱등 INSERT) · server/src/lib/geofence-engine.ts(dedupKey) PARTIAL · 검증 지오펜스 엔진 멱등 테스트

5. 소스 정밀도 매트릭스 (DM-EVT-PREC-001)

DM-EVT-PREC-001 동일 사실 충돌 시 소스 우선순위 P0·MustPhase 1T 테스트

요구. 동일 shipment에 여러 소스가 같은 canonical 이벤트를 보내 충돌할 때, 시스템은 반드시 아래 정밀도 순서로 "정답"을 고른다. 동률(같은 정밀도)은 received_at 최소값(먼저 도착)을 채택한다.

순위source_type정밀도비고
1EDI_X12HIGH캐리어 EDI 214 등
2CARRIER_APIHIGH운송사 직접 API
3EDIFACTHIGH국제 EDI
4GEOFENCEMED지오펜스 ENTER/EXIT 파생
5TELEMATICSMED차량 텔레매틱스
6APP_GPSMED드라이버 앱 GPS
7MANUAL역할 의존오퍼레이터/화주 수동 입력

event_code_map.confidence(HIGH/MEDIUM/LOW)는 매핑 신뢰도, 위 매트릭스는 소스 간 충돌 해소로 역할이 다르다. 일반적으로 캐리어 EDI > GPS 지오펜스 퇴출 추정.

수용기준

  • Given EDI_X12:DELIVEREDGEOFENCE:DELIVERED 동시 수신, When 충돌 해소, Then EDI_X12 채택.
  • Given 동률 두 소스, When 해소, Then received_at 최소가 우선.
근거 마스터 §4.4 DM-EVT-PREC-001 · model.md §정규화 매핑 · 구현 충돌-해소 선택 로직(현재 지오펜스 파생만) PARTIAL · 검증 매트릭스 후 충돌 0 목표(KPI-NORM-001)

6. Canonical 이벤트 enum + 지오펜스 파생 (DM-EVT-ENUM-001)

DM-EVT-ENUM-001 canonical 11종 동결 + geofence-type→canonical 파생표 P0·MustPhase 1T 테스트

요구. 모든 소스의 raw 코드는 반드시 아래 canonical 11종 집합으로 매핑된다(+ 예외 코드 TRACKING_LOST). geofence-type → canonical 파생표를 동결하며, PORT/BORDER 파생은 PARTIAL(Phase 2, 현재 미파생)이다.

canonical_code의미주 파생 소스
CREATED화물 등록MANUAL/API
DISPATCHED배차·경로 배정CARRIER_API / X12 AG
ARRIVED_AT_PICKUP픽업지 도착GEOFENCE(WAREHOUSE ENTERED)
PICKUP_COMPLETED픽업 완료GEOFENCE(WAREHOUSE EXITED) / X12 AF / 集荷完了
IN_TRANSIT운송 중APP_GPS / X12 X1
ARRIVED_AT_DELIVERY배달지 도착GEOFENCE(CUSTOMER ENTERED)
DELIVERED배달 완료GEOFENCE(CUSTOMER EXITED, dwell>0) / X12 D1 / 配達完了
OUT_FOR_DELIVERY배달 중X12 OA
DELIVERY_ATTEMPTED배달 시도X12 AE
EXCEPTION예외X12 X6 / 룰 엔진
CANCELLED취소X12 CA
TRACKING_LOST (예외코드)추적 손실(45min 무핑)룰 엔진(Cron sweep) — FR-ENG-EXC-003

geofence-type → canonical 파생표(동결)

geofence.typeENTERED →EXITED →상태
WAREHOUSEARRIVED_AT_PICKUPPICKUP_COMPLETEDDONE
CUSTOMERARRIVED_AT_DELIVERYDELIVEREDDONE
PORT(미파생)(미파생)PARTIAL · Phase 2
BORDER(미파생)(미파생)PARTIAL · Phase 2
as-built geofence.tsderiveCanonical()는 WAREHOUSE·CUSTOMER만 매핑하고 PORT/BORDER는 null 반환(// TODO: PORT/BORDER are Phase 2). 즉 PORT/BORDER 지오펜스 적중은 현재 canonical 이벤트를 만들지 않는다.

수용기준

  • Given WAREHOUSE 지오펜스 ENTER, When 파생, Then ARRIVED_AT_PICKUP.
  • Given CUSTOMER 지오펜스 EXIT(dwell>0), When 파생, Then DELIVERED + stop.actual_departure_at 채움.
  • Given PORT/BORDER 적중, When 파생, Then 이벤트 미생성(PARTIAL, Phase 2 트래킹).
근거 마스터 §4.4 DM-EVT-ENUM-001 · model.md §이벤트 분류 · 구현 server/src/lib/geofence.ts deriveCanonical PARTIAL(PORT/BORDER 미구현) · 검증 지오펜스 엔진 테스트

7. Shipment 상태머신 (DM-STATE-001)

DM-STATE-001 current_status 상태머신 + arrival/departure 기록 규칙 P0·MustPhase 1T 테스트

요구. shipment.current_status는 canonical 코드만 가지며(MUST), 정상 경로를 따라 단조 전진한다. 지오펜스 ENTER/EXIT가 current_status를 advance하고 stop.actual_arrival_at/actual_departure_at/dwell_minutes를 채운다.

CREATED → DISPATCHED → ARRIVED_AT_PICKUP → PICKUP_COMPLETED
        → IN_TRANSIT → ARRIVED_AT_DELIVERY → DELIVERED        (정상 종결)

분기:  (임의 상태) → EXCEPTION      (룰 엔진/X12 X6; exception_flag=1)
      (종결 전)   → CANCELLED      (X12 CA)
      배달 변형: OUT_FOR_DELIVERY → DELIVERY_ATTEMPTED → DELIVERED
      예외코드:  TRACKING_LOST    (45min 무핑, 상태머신 외 예외 표식)

arrival/departure 기록 규칙.

  • arrival: 지오펜스 OUTSIDE→INSIDE(ENTER) 확정 시 stop.actual_arrival_atfirst-write-wins(COALESCE)로 기록 — 荷待ち 도착 시계가 뒤로 밀리지 않게 함.
  • departure: INSIDE에서 핑 셀이 gridDisk(cover, +1)를 벗어나면(EXIT) actual_departure_at 기록 + dwell_minutes = departure − arrival 계산.
  • 순서 가드: geofence_membership.last_seen_at(단조)로 늦게 도착한 pre-exit 핑이 arrival/departure를 뒤집지 못하게 한다(out-of-order drop).
  • dwell > geofence.dwell_threshold_min(기본 120, 법정 2시간)이면 EXIT 이벤트에 荷待ち超過 플래그(FR-ENG-EXC-002).

수용기준

  • Given WAREHOUSE ENTER 후 EXIT, When 처리, Then ARRIVED_AT_PICKUP→PICKUP_COMPLETED, arrival/departure/dwell_minutes 채워짐.
  • Given 같은 stop의 두 번째 ENTER 핑, When 처리, Then actual_arrival_at은 첫 값 유지(COALESCE first-write-wins).
  • Given 늦게 도착한 pre-exit 핑(captured_at ≤ last_seen_at), When 처리, Then drop되어 departure가 arrival을 역전하지 않음.
근거 engine.md · model.md · 구현 server/src/lib/geofence-engine.ts(ENTER/EXIT·COALESCE·last_seen_at 가드) DONE · 0003_geofence_membership.sql · 검증 지오펜스 enter/exit/dwell 엔진 테스트

8. KV 역인덱스 값 계약 (DM-KV-001)

DM-KV-001 cell → geofence_id[] 역인덱스 값 계약 · 재기록 시점 P1·ShouldPhase 1T 테스트

요구. 핑당 O(1) 지오펜스 멤버십 판정을 위해 KV에 역인덱스 cell → geofence_id[]를 둔다(권장). 키는 H3 셀 ID(latLngToCell 결과, 16진), 값은 그 셀을 덮는 지오펜스 ID 배열이다. D1 geofence_cell이 진실, KV는 판정 가속 사본이다.

  • 값 형식: geofence_id 문자열 배열(빈 셀은 키 부재 = 매칭 없음).
  • 재기록 시점: 지오펜스 생성·수정·삭제 시에만(읽기 多·쓰기 少). 등록/수정 시 polygonToCellsgeofence_cell·KV 역인덱스를 함께 재생성한다.
  • 판정: interior 셀 적중 = 확정, boundary 셀 적중 = 그 핑만 point-in-polygon 1회 확인(하이브리드 PIP).

수용기준

  • Given 지오펜스 폴리곤 수정, When 저장, Then geofence_cell 재생성 + KV 역인덱스 동일 트랜잭션적 재기록.
  • Given 핑 셀 ID, When KV 조회, Then 1회 lookup으로 덮는 geofence_id[] 반환(폴리곤 PIP 미수행).
근거 engine.md · ADR-0009 · 구현 server/src/lib/h3.ts · geofence.ts · 검증 멤버십 lookup 테스트 · 지오펜스 CRUD/KV 재기록FR-GEO-CRUD-001 소유(현 /v1/geofences 라우트 부재)

9. 테넌트 격리 규칙 (DM-ISO-001)

DM-ISO-001 모든 쿼리 tenant_id 스코프 · 타테넌트 404 P0·MustPhase 1T 테스트

요구. 모든 shipment/stop/event/geofence(및 regulatory_report) 접근은 반드시 인증 컨텍스트의 tenant_id로 스코프된다. tenant_id토큰/API 키 클레임에서만 바인딩하고 클라이언트 입력에서 절대 받지 않는다. 타테넌트 행은 0행 → 404(존재 자체를 숨김; 403 아님).

  • DRIVER 역할은 추가로 driver_id 스코프(자기 案件만).
  • 모든 DB 접근을 단일 repo 계층으로 라우팅해 tenant_id 미바인딩 쿼리를 구조적으로 불가능하게 한다.

수용기준

  • Given 테넌트 A 토큰, When 테넌트 B의 shipment_id GET, Then 404(0행).
  • Given DRIVER 토큰, When 타 드라이버 案件 GET, Then 404.
  • Given 임의 쿼리 경로, When 코드 검사, Then tenant_id 바인딩이 auth 컨텍스트에서만 옴.
근거 security-privacy.md · 마스터 §7 as-built · 구현 server/src/lib/db.ts(tenant-scoped repo, 404 격리) DONE · 검증 tenant-scoped repo 404 격리 테스트(마스터 §7) · 권한 매트릭스는 SR-TENANT-001 소유

10. 용량 한계 가드 (DM-CAP-001)

DM-CAP-001 shipment당 stop/event 상한 + 비정상 폭증 가드 P1·ShouldPhase 1A 분석

요구. 단일 화물의 무한 폭증으로부터 D1·DO를 보호하기 위해 shipment당 stop·event 상한과 비정상 증가 알람을 둔다(권장). 일본 국내 트럭의 3계층 모델상 stop은 소수(1~2 구간)이므로 비정상 폭증은 데이터 오류·악용 신호다.

  • 인제스트 배치는 서버 하드캡 500 samples/req(초과 413); 클라 목표 배치 100(FR-ACQ-API-001) — 이벤트 유입의 1차 가드.
  • shipment당 stop/event 상한은 초기 확정 목표(설계)로 두고 운영 베이스라인으로 조정(OD-006 용량 베이스라인 post-launch).
  • 비정상 증가(예: 한 shipment에 분당 이벤트 급증)는 비용/한도 근접 대시보드 알람(NFR-COST-001)과 연계.

수용기준

  • Given 501 samples 단일 인제스트, When POST, Then 413.
  • Given 한 shipment의 이벤트 비정상 폭증, When 모니터, Then 알람 발화(미매핑 급증 >50/시간 알람과 동형).
근거 마스터 §4.3 FR-ACQ-API-001 · §4.6 NFR-COST-001 · 구현 인제스트 413 캡 PARTIAL; shipment당 상한 ABSENT(베이스라인 미정) · 검증 부하/용량 모델은 nonfunctional 소유

11. 送り状番号 정규화 레이어 (DM-NORM-001)

핵심 현실. 일본 국내 트럭에는 미국 SCAC/Pro Number 같은 업계 표준 식별자가 없다 추정. 각 운송사가 독자 送り状番号 체계를 쓰고 화주 PO·사내 관리번호가 혼재한다. 이 공백을 메우는 정규화 레이어가 시스템의 "접착제"다(Research/landscape-map의 화이트스페이스).
DM-NORM-001 복합키 매핑 · 운송사별 파서 · GS1 흡수 여지 P1·ShouldPhase 2T 테스트

요구. 외부에서 들어오는 화물을 우리 shipment_id(불변)로 수렴시키는 정규화 레이어를 둔다(SHOULD).

  • 복합 키 매핑: (화주 tenant + shipper_ref) 또는 (화주 PO + 운송사 + 날짜) 복합 키로 내부 shipment에 연결.
  • 운송사별 파서: 번호 형식 비표준 → 운송사별 파싱·정규화 규칙(설정 관리). 초기엔 대형 운송사(ヤマト·佐川·NX·セイノー) 위주 선구축 — 개발보다 협상·조사 공수가 큰 작업.
  • GS1 흡수 여지: GS1 Japan의 SSCC(출하포장 시리얼)·GLN(장소 식별) 보급 시 shipment.shipper_ref/geofence에 GLN을 표준 식별자로 승격할 수 있게 여지를 남긴다 추정(OD-008).
운송사 A 送り状番号: A-2025-0001  ┐
화주 PO: PO-98765                ├─→ [정규화 레이어 · 운송사별 파서]
운송사 B 전표: B/240515/77        ┘        │ 복합 키 매핑
                                          ▼
                          내부 shipment_id: SHP-2025-00456 (불변)

수용기준

  • Given 운송사 A 형식 shipper_ref, When 파서 적용, Then 정규 복합 키로 단일 shipment_id 매핑.
  • Given 동일 화물 중복 인입(상이 형식), When 매핑, Then 단일 shipment_id로 수렴(신규 행 생성 안 함).
  • Given GLN 포함 화주 데이터, When 인입, Then shipper_ref에 GLN 보존(흡수 여지).
근거 model.md §送り状番号 · Research/data-model · 구현 정규화 레이어 ABSENT(Phase 2; 대형 운송사 코드 수집 선행 과제) · event_code_map 시드 골격 존재 · 검증 운송사별 파서 단위 테스트(신설)

근거·상호참조