기능 요구사항 (FR-*)

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

이 페이지는 LogiNippon이 무엇을 해야 하는가를 테스트 가능한 단위로 고정한다. 인증·데이터 수집·인제스트·지오펜싱·트래킹 엔진(ETA·예외)·전달 계층·규제 리포트·프로비저닝의 모든 정규(normative) 기능 요구사항을 고유 ID 카드로 열거하고, 각 카드에 진술(반드시/권장)·우선순위(P0/P1/P2)·단계(Phase)·근거·수용기준(Given/When/Then)·구현상태(DONE/STUB/ABSENT) 6요소를 부여한다. 모든 임계 수치는 마스터 정규화 수치표(§4)에서 글자 그대로 인용했으며, 전부 운영 데이터가 없는 진입 단계의 초기 확정 목표(설계)이고 운영 베이스라인으로 분기별 조정·명시적 개정 트리거를 가진다 — 달성치가 아니다. 규제 산출물의 컬럼·포맷 등 상세 명세는 regulatory.html이 소유하며, 여기 FR-RPT 카드는 고위(高位) 수준에서 무엇을 만들지만 고정하고 링크한다.

읽는 법. 우선순위 P0·Must=MVP 출시에 반드시(MUST), P1·Should=권장(SHOULD), P2·Could=선택(MAY). 단계 배지 Phase 0(제휴·하드게이트) / Phase 1(MVP) / Phase 2(확장). 검증 배지 T 테스트·D 데모·A 분석·I 점검. 구현상태는 마스터 §7 as-built 사실에 따라 DONE(구현+테스트) / STUB(껍데기·ack-only·501) / ABSENT(미존재)로 표기한다.

FR 그룹 요약

본 페이지가 소유·정의하는 FR 그룹과 카드 수, 대표 임계·구현 성숙도다. 타 페이지가 소유한 ID(NFR/DM/IR/SR/RR/SLO)는 정의하지 않고 링크만 한다.

그룹범위카드대표 임계·계약(§4 인용)구현 성숙도(§7)
FR-AUTH인증·토큰·권한·자격 프로비저닝001–005access 3600s / refresh 2592000s / ingest 604800s, revocation ≤60s, PBKDF2 ≥100k대체로 DONE
FR-ACQ스마트폰 GPS 수집·동의·오프라인·案件 자동공유GPS/API/CONSENT/OFFLINE/AUTOSHARE샘플링 30/60/180s, 배치 500/100, 동의 하드게이트ABSENT(app stub)
FR-INGEST인제스트 수신·검증·멱등001–003202 ACK, 하드캡 500/req(413), at-least-onceDONE
FR-GEOH3 지오펜싱·히스테리시스·dwell·CRUD001–003/CRUD/DEBOUNCE/H3RESgridDisk(+1), dwell, res8/9/10/11, ENTER 단일핑엔진 DONE / CRUD ABSENT
FR-ENG-ETA룰 ETA·ML 전환 트리거001–002시드 80/30/50 km/h·dwell 30min, 전환 ≥3,000 & MAPE>20%STUB(ack-only)
FR-ENG-EXC예외 감지(지연·荷待ち·추적손실·이탈·정차·픽업)001–006+30min, >120min, 45min→TRACKING_LOST, 2km, 15min부분(cron sweep STUB)
FR-DLV웹훅·WebSocket·알림·추적링크WH/WS/NOTIFY/TRACK재시도 8회 래더 24h·auto-disable, HMAC-SHA256STUB(ack-only/501)
FR-RPT규제 리포트·범위·스코어카드(고위)NIMACHI/JITSUUNSO/KOSOKU/RANGE/ANALYTICS≤92일·서명URL 300s — 상세는 regulatory.htmlNIMACHI DONE / 나머지 501·ABSENT
FR-PROV운영자 프로비저닝(테넌트·캐리어·드라이버·지오펜스 UI)001–002console Provisioning, admin-provisioned 자격서버 라우트 ABSENT
≤200ms
인제스트 accept p95(NFR-PERF-001)
500/req
인제스트 하드캡(초과 413)
120min
荷待ち 법정 임계(dwell)
45min
추적 손실→TRACKING_LOST
3600s
access 토큰 TTL
8회·24h
웹훅 재시도 래더

FR-AUTH — 인증·인가·자격

API Key·토큰·권한 매트릭스·운영자 프로비저닝 자격을 고정한다. 토큰 TTL·로테이션·PBKDF2·revocation 전파 수치는 마스터 §4.5(SR/RET) 표의 FR-AUTH-001 행에서 인용한다. 권한 매트릭스(scope→endpoint→role) 자체는 SR-AUTHZ-001이 소유하며 여기서는 "강제(enforcement)" 요구만 정의한다. 토큰 저장 하드닝은 SR-TOKEN-STORE-001·OD-003으로 연결된다.

FR-AUTH-001 토큰 발급·TTL 정책 (access·refresh·ingest) P0Phase 1T

요구. 시스템은 세 종류의 토큰을 명시된 TTL로 반드시 발급해야 한다(MUST): access 3600s, refresh 2592000s, ingest 604800s(스코프 positions:write 한정). 비밀번호 해시는 PBKDF2 ≥100k 반복반드시 사용한다. 값은 진입 단계 초기 확정치이며 운영 베이스라인으로 조정한다(설계).

수용기준

  • Given 유효 자격, When POST /v1/auth/token, Then expires_in=3600의 access 토큰과 refresh 토큰을 반환한다.
  • Given access 토큰 발급 3601초 후, When 보호 엔드포인트 호출, Then 401 UNAUTHENTICATED.
  • Given ingest 토큰, When shipments:read가 필요한 엔드포인트 호출, Then 403 PERMISSION_DENIED(positions:write 한정 강제).
근거 마스터 §4.5 (FR-AUTH-001) · server README · 구현 server/src/lib/auth.ts(JWT HS256·PBKDF2 100k·ingest-token) DONE · 검증 auth 테스트군(§7)
FR-AUTH-002 refresh 로테이션·재사용 탐지·KV revocation 전파 P0Phase 1T

요구. refresh 토큰은 로테이션-only여야 하며(매 갱신 시 새 refresh 발급·기존 무효화), 재사용 탐지 시 해당 토큰 패밀리를 반드시 폐기한다(MUST). 키/토큰 revocation은 KV를 통해 ≤60s 내 전파되어야 한다.

수용기준

  • Given 한 번 사용된(rotate된) refresh 토큰, When 그 토큰 재제출, Then 거부 + 토큰 패밀리 전체 무효화.
  • Given revocation 등록, When ≤60s 경과 후 동일 토큰으로 호출, Then 401(KV 전파 확인).
  • Given 정상 갱신, When refresh 사용, Then 새 access+새 refresh 발급, 직전 refresh는 무효.
근거 마스터 §4.5 (KV revocation ≤60s) · SR-CRYPTO-001(키 로테이션/revocation) · 구현 server/src/lib/auth.ts(refresh 로테이션) DONE · 검증 T
FR-AUTH-003 비밀번호 정책 P1Phase 1T

요구. 비밀번호는 PBKDF2 ≥100k 반복으로 솔트 해시 저장하고, 평문·약한 해시 저장을 반드시 금지한다(MUST). 자격은 admin-provisioned이며(가입 셀프서비스 없음 — FR-AUTH-005), 시드 비밀번호는 로컬 dev 외 사용 금지를 권장(SHOULD) 강제한다.

수용기준

  • Given 신규 자격 등록, When DB 검사, Then 비밀번호는 PBKDF2(≥100k)+salt 형태로만 저장.
  • Given 동일 평문 두 계정, When 해시 비교, Then 솔트로 서로 다른 해시.
근거 마스터 §4.5(PBKDF2 ≥100k) · server README(시드 자격 dev 전용) · 구현 server/src/lib/auth.ts · migrations/0002_auth.sql DONE · 검증 T
FR-AUTH-004 권한 매트릭스 강제 (scope·테넌트 격리) P0Phase 1T

요구. 모든 엔드포인트는 토큰의 scope와 tenant_id반드시 인가·필터링해야 한다(MUST). 타 테넌트 리소스는 존재를 비노출하기 위해 404로 응답한다(403 아님). 受取人(최종 고객) 스코프는 차량 위치를 노출하지 않는다(FR-DLV-TRACK-001). 매트릭스 정의는 SR-AUTHZ-001 소유, 본 카드는 강제만 요구한다.

수용기준

  • Given 화주 A 토큰, When 화주 B의 shipment 조회, Then 404(존재 비노출).
  • Given shipments:read만 가진 토큰, When POST /v1/shipments, Then 403 PERMISSION_DENIED.
  • Given 임의 보호 엔드포인트, When 토큰 없이 호출, Then 401 UNAUTHENTICATED.
근거 delivery.md(테넌트 스코프·404) · SR-TENANT-001 · 구현 server/src/middleware/auth.ts · server/src/lib/db.ts(테넌트 스코프 repo) DONE(테넌트 격리 회귀 테스트) · 검증 T
FR-AUTH-005 admin-provisioned 자격 (셀프서비스 가입 없음) P0Phase 1A

요구. 사용자 자격(드라이버·운영자)은 운영자 프로비저닝으로만 생성되어야 하며(FR-PROV-001와 정합), 공개 셀프서비스 가입을 반드시 제공하지 않는다(MUST NOT). B2B 운송사 계약 단위 도입 모델과 정합한다.

수용기준

  • Given 미등록 사용자, When 로그인 시도, Then 인증 실패 — 가입 경로 부재.
  • Given 운영자가 드라이버 자격 생성, When 드라이버 로그인, Then 발급된 자격으로만 성공.
근거 server README(시드 자격: 테넌트·운송사·드라이버·운영자) · 마스터 §0(운송사 계약 단위) · 구현 migrations/0002_auth.sql · 운영자 생성 라우트 POST /v1/admin/drivers ABSENT(§7) → FR-PROV-001 · 검증 A

FR-ACQ — 데이터 수집 (스마트폰 GPS 1차 소스)

스마트폰 앱 GPS를 1차 데이터 소스로 확정한 acquisition.md의 수집 전략을 요구로 고정한다. "버튼 의존 제거"(案件 자동 공유 + 지오펜스 자동 이벤트)와 개인정보 동의 하드게이트가 핵심. 본 그룹의 구현 주체는 Flutter app이며, 마스터 §7에 따라 핵심 GPS 서비스가 전부 주석 처리된 scaffold stub 상태다 — 카드는 계약을 동결해 unblock하는 역할이다.

as-built 갭(§7). Flutter app의 백그라운드 GPS(TransistorLocationService)는 100% 주석 처리, hasConsent→false/token→null 하드코딩, 권한·동의 온보딩·증빙사진·푸시·l10n 배선 부재. 또한 서버 consentGate()는 lib에 존재하나 ingest 경로에서 미호출(FR-ACQ-CONSENT-001의 APPI 갭). 이 카드들은 계약을 정의해 클라이언트·서버 구현을 잠금 해제한다.

FR-ACQ-GPS-001 적응형 GPS 샘플링 (배터리·데이터 예산) P0Phase 1T

요구. 드라이버 앱은 컨텍스트별 샘플링 주기를 반드시 적용해야 한다(MUST): 이동 중 & 다음 지오펜스 2km 내 30s, 주행 중 60s, 정지 >10min 180s. distanceFilter 40m, heartbeat 120s, stopTimeout 5min, stationaryRadius 30m. 자원 예산은 배터리 ≤6%/h, 데이터 ≤10MB/일을 목표로 한다(초기 확정치, 운영 조정).

수용기준

  • Given 차량이 다음 지오펜스 2km 내 주행, When 샘플링, Then 30s 주기로 좌표 생성.
  • Given 차량 정지 10분 초과, When 샘플링, Then 180s 주기로 감속.
  • Given 8시간 운행, When 자원 측정, Then 배터리 소모 ≤6%/h·데이터 ≤10MB/일 예산 내.
근거 마스터 §4.3 (FR-ACQ-GPS-001) · acquisition.md(30초~3분 주기, ADR-0006) · 구현 app TransistorLocationService ABSENT(주석 처리, §7) · 검증 T 디바이스 테스트
FR-ACQ-API-001 인제스트 배치 계약 (서버 500 / 클라 100) P0Phase 1T

요구. 클라이언트는 좌표를 배치로 묶어 전송해야 한다(MUST): 목표 배치 100건(100건 또는 3min flush). 서버 하드캡은 500 samples/req이며 초과 시 413으로 거부한다. 오프라인 버퍼는 ≤100 청크(FR-ACQ-OFFLINE-001).

수용기준

  • Given 100건 누적 또는 3분 경과, When flush, Then 단일 POST /v1/ingest/positions 배치 1건.
  • Given 501 samples 요청, When 인제스트, Then 413 거부.
  • Given 정상 배치, When 수신, Then 202 ACK(FR-INGEST-001).
근거 마스터 §4.3 (FR-ACQ-API-001) · acquisition.md(배치 전송) · 계약 상세 IR-API-001 · 구현 서버 server/src/ingest/positions.ts DONE / 클라 배치 ABSENT · 검증 T
FR-ACQ-OFFLINE-001 오프라인 버퍼·복구 시 일괄 전송 P1Phase 1T

요구. 통신 두절 구간의 좌표를 로컬 큐에 ≤100 청크로 버퍼링하고, 네트워크 복구 시 일괄 전송해야 한다(MUST). occurred_at(발생)과 received_at(수신)을 분리 저장해 늦게 도착한 핑도 정확한 시각으로 처리한다(DM-TS-001).

수용기준

  • Given 30분 통신 두절, When 복구, Then 버퍼된 좌표가 occurred_at 보존된 채 일괄 전송.
  • Given 버퍼 100 청크 초과, When 신규 청크, Then 가장 오래된 것부터 정책에 따라 처리(드롭/병합) 후에도 데이터 손상 없음.
근거 마스터 §4.3(오프라인 버퍼 ≤100 청크) · acquisition.md(연속성 완화·로컬 큐) · 구현 app 로컬 큐 ABSENT / 서버 occurred/received 분리 DONE · 검증 T
FR-ACQ-AUTOSHARE-001 案件 자동 공유 라이프사이클 (버튼 의존 제거) P0Phase 1D

요구. 드라이버가 案件을 수락하면 그 案件의 운송 구간 동안 위치 공유가 자동 활성화되어야 하고(MUST), 配達 완료 또는 案件 종료 시 자동 비활성화되어야 한다. "공유 시작" 수동 버튼에 의존하지 않는다. 공유는 案件 단위·기간 한정으로 個人情報保護法상 목적·기간을 명확히 한다(PickGo·project44 데이터 거버넌스 사상 확인).

수용기준

  • Given 드라이버가 案件 수락, When 수락 직후, Then 백그라운드 위치 공유 ON(수동 조작 없이).
  • Given 配達 완료/案件 종료, When 종료 이벤트, Then 위치 공유 자동 OFF.
  • Given 배정된 案件 없음, When 평상시, Then 위치 미공유(목적·기간 외 수집 없음).
근거 acquisition.md(案件 자동 공유, Research/implications-for-us) · delivery.md(드라이버 앱 플로우) · 구현 app 案件 라이프사이클 ABSENT(AuthController·JobsRepository stub, §7) · 검증 D

FR-INGEST — 인제스트 (수신·검증·멱등)

Ingest Worker는 인증·기본 검증 후 라이브(DO)와 비동기(Queues)로 분기하고 클라이언트에 빠르게 응답한다(acquisition.md·engine.md). 마스터 §7 기준 이 경로는 DONE(ingest→DO→queue→R2).

FR-INGEST-001 검증 → 202 ACK → DO push + enqueue P0Phase 1T

요구. POST /v1/ingest/positions는 인증·기본 검증을 통과한 배치에 반드시 202로 빠르게 ACK하고(MUST), 라이브 반영을 위해 Shipment DO에 push하며 비동기 처리를 위해 Queues에 enqueue해야 한다. accept 지연 목표는 NFR-PERF-001 p95 ≤ 200ms / p99 ≤ 400ms(SLI S1, 초기 확정치).

수용기준

  • Given 유효 배치, When 인제스트, Then 202 + DO 라이브 상태 갱신 + geofence-eval 큐 enqueue.
  • Given 배치 수신, When 측정, Then accept 지연 p95 ≤ 200ms(SLI S1) 목표 추적.
근거 engine.md(검증→DO push+enqueue) · 마스터 §4.1(S1) · 구현 server/src/ingest/positions.ts · server/src/do/shipment.ts DONE · METRICS writeDataPoint TODO → SLI 미측정(§7) · 검증 T
FR-INGEST-002 좌표·속도·단위·타임스탬프 검증 P0Phase 1T

요구. 인제스트는 좌표 범위(위/경도)·속도·단위·타임스탬프 형식을 반드시 검증해야 한다(MUST). 타임스탬프는 ISO 8601 +09:00 오프셋 명시를 요구하고 잘못된 형식은 거부한다(DM-TS-001 — 荷待ち 법정 계산 의존). 검증 실패는 422 VALIDATION_FAILED.

수용기준

  • Given 위도 100 같은 범위 외 좌표, When 인제스트, Then 422 거부.
  • Given 'Z'(UTC)만 있고 오프셋 모순 타임스탬프, When 인제스트, Then 거부(+09:00 규칙).
  • Given 비정상 속도 단위, When 검증, Then 거부 또는 정규화.
근거 acquisition.md(좌표 범위·타임스탬프 검증) · 마스터 §4.4(DM-TS-001) · 구현 server/src/ingest/positions.ts · server/src/middleware/error.ts(검증·엔벨로프) DONE · 검증 T
FR-INGEST-003 멱등 처리 (at-least-once + dedup_key) P0Phase 1T

요구. Queues는 at-least-once 전달이므로 같은 이벤트가 두 번 처리될 수 있다 — 시스템은 dedup_key반드시 멱등 처리해야 한다(MUST). 키는 sha256(source_type | shipment_id | canonical_code | floor(occurred_at→분) [| stop_id])이며 D1 UNIQUE로 중복을 흡수한다(DM-DEDUP-001).

수용기준

  • Given 동일 이벤트 2회 enqueue, When 소비, Then D1에 1건만 INSERT(UNIQUE 충돌 흡수).
  • Given 멀티소스 중복(예: GEOFENCE + APP_GPS 동분), When 정규화, Then 동일 dedup_key로 1건.
근거 engine.md(멱등·중복제거) · acquisition.md(Queue Consumer dedup) · 마스터 §4.4(DM-DEDUP-001) · 구현 server/src/consumers/queue.ts(정규화·dedup) DONE · 검증 T

FR-GEO — 지오펜싱 (H3 셀 멤버십)

지오펜스 판정을 폴리곤 PIP 대신 H3 셀 멤버십 + KV 역인덱스 O(1)로 치환한다(engine.md·ADR-0009). 히스테리시스는 gridDisk(+1) 링, 경계는 interior 확정 + boundary만 PIP 1회. 마스터 §7 기준 enter/exit/dwell 히스테리시스 엔진은 풀 구현 DONE이나, 온라인 지오펜스 CRUD 라우트는 ABSENT다.

FR-GEO-001 H3 cover + KV 역인덱스 (사전계산·핫경로 O(1)) P0Phase 1T

요구. 지오펜스 등록·수정 시 폴리곤을 polygonToCells로 덮는 H3 셀 집합을 만들고 역인덱스 cell → geofence_id[]를 KV에 반드시 기록해야 한다(MUST). 핑마다 latLngToCell 1회 + KV 조회 1회로 멤버십을 O(1)에 판정한다(핑당 PIP 제거). 셀은 D1 geofence_cellkind(interior/boundary)와 함께 저장한다.

수용기준

  • Given 지오펜스 등록, When 사전계산, Then KV에 h3:<cell>→geofence_id[] 역인덱스 생성.
  • Given 거점 내부 핑, When 판정, Then KV O(1) 조회로 멤버십 확정(PIP 없이).
  • Given 거점 밖 핑, When 판정, Then 히트 없음 = 상태 변화 없음.
근거 engine.md(H3 셀 멤버십·KV 역인덱스, ADR-0009) · server README(KV cell→geofence) · 구현 server/src/lib/h3.ts · server/src/lib/geofence.ts · migrations/0003_geofence_membership.sql DONE(h3 테스트) · 검증 T
FR-GEO-002 enter/exit 히스테리시스 (gridDisk +1 이탈 링) P0Phase 1T

요구. 경계 핑퐁(spurious ENTER/EXIT)을 막기 위해 진입은 정확한 덮개 셀에서, 이탈은 덮개 셀 + 1링(gridDisk(+1)) 밖으로 나가야만 EXITED를 발행해야 한다(MUST). 진입 반경 < 이탈 반경(50m<100m의 H3판). 경계 걸친 boundary 셀은 그 핑 하나에만 PIP 1회로 확정한다(하이브리드).

수용기준

  • Given INSIDE 상태, When 핑이 덮개+1링 안에 머무름, Then EXIT 발행 없음(플래핑 0).
  • Given INSIDE 상태, When 핑이 gridDisk(+1) 밖으로, Then EXITED 발행 + dwell 계산.
  • Given boundary 셀 적중, When 판정, Then 그 핑만 PIP 1회로 내/외부 확정.
근거 engine.md(히스테리시스 gridDisk·하이브리드 PIP) · 마스터 §4.3(FR-GEO-DEBOUNCE-001) · 구현 server/src/lib/geofence-engine.ts DONE(enter/exit/dwell 히스테리시스 풀) · 검증 T
FR-GEO-003 dwell 측정 (荷待ち 산출의 원천) P0Phase 1T

요구. EXITED 이벤트 시 dwell_minutes = actual_departure_at − actual_arrival_at(= EXITED ts − ENTERED ts)을 반드시 계산해 stop.dwell_minutes에 저장해야 한다(MUST). 이 값이 荷待ち 규제 리포트(FR-RPT-NIMACHI-001)와 過다체류 예외(FR-ENG-EXC-002)의 원천이다. 타임스탬프는 +09:00 명시(DM-TS-001).

수용기준

  • Given ENTER 09:12·EXIT 11:35, When dwell 계산, Then dwell_minutes=143 저장.
  • Given dwell > 임계(120), When 측정, Then 荷待ち 초과 예외 발행(FR-ENG-EXC-002).
근거 engine.md(dwell = EXIT − ENTER, 킬러 유즈케이스) · delivery.md(荷待ち 기록) · 구현 server/src/lib/geofence-engine.ts · server/src/lib/reports.ts DONE(荷待ち CSV end-to-end) · 검증 T
FR-GEO-CRUD-001 온라인 지오펜스 CRUD (원자적 KV/D1 재기록) P0Phase 1T

요구. 운영자는 지오펜스를 온라인으로 생성·수정·삭제할 수 있어야 하며(MUST), 변경 시 H3 덮개 셀과 KV 역인덱스·D1 geofence_cell을 원자적으로 재기록해야 한다(부분 갱신으로 KV/D1 불일치 금지). 핫 경로가 아닌 드문 쓰기로 설계한다(읽기 多·쓰기 少).

수용기준

  • Given 폴리곤 수정, When 저장, Then KV 역인덱스와 D1 geofence_cell이 동시에 새 덮개로 일관 갱신.
  • Given 재기록 중 실패, When 복구, Then KV/D1가 이전 또는 신규로 일관(부분 상태 없음).
  • Given 지오펜스 삭제, When 저장, Then 관련 KV 엔트리·셀 제거.
근거 engine.md(사전계산·재기록) · delivery.md/api-contracts(/v1/geofences[/:id]) · 구현 GET/POST/PUT/DELETE /v1/geofences ABSENT(라우트 부재, §7) · 시드 경로 scripts/seed KV/cell DONE · 검증 T
FR-GEO-DEBOUNCE-001 단일 핑 ENTER (法定 荷待ち 도착시계 무지연) P0Phase 1T

요구. ENTER는 단일 핑으로 즉시 발행해야 한다(MUST) — 법정 荷待ち 도착 시계를 지연시키지 않기 위함. 단, stop 스코프 + PIP로 오탐을 제거하고, EXIT는 gridDisk(+1) 이탈 링 통과를 요구한다(비대칭 디바운스). 경계 플래핑의 spurious ENTER/EXIT는 0이어야 한다.

수용기준

  • Given 차량이 거점 덮개 셀 첫 진입, When 단일 핑, Then 즉시 ENTERED(actual_arrival_at 기록) — 荷待ち 시계 시작.
  • Given 경계 근처 핑 흔들림, When 연속 판정, Then spurious ENTER/EXIT 0.
근거 마스터 §4.3(FR-GEO-DEBOUNCE-001) · engine.md(ENTER 즉시·EXIT 링) · 구현 server/src/lib/geofence-engine.ts DONE · 검증 T
FR-GEO-H3RES-001 H3 해상도 정책 (거점 면적별) P1Phase 1A

요구. 거점 폴리곤 면적별로 cover 해상도를 반드시 선택해야 한다(MUST): >50,000㎡→res8, 5,000–50,000㎡→res9, <5,000㎡→res10/11. 권위 변길이: res8≈0.53km, res9≈0.20km, res10≈76m, res11≈29m. 분석 컬럼은 h3_r10 고정. boundary 셀 비율은 <30%, ping-res와 cover-res는 다해상도 lookupCell로 정합한다.

수용기준

  • Given 60,000㎡ DC, When cover 생성, Then res8 셀 집합 + boundary 비율 <30%.
  • Given 3,000㎡ 도크, When cover 생성, Then res10/11 셀 집합.
  • Given 분석 GROUP BY, When 집계, Then h3_r10 고정 컬럼 사용.
근거 마스터 §4.3(FR-GEO-H3RES-001) · engine.md(해상도↔정밀도 트레이드오프) · 구현 server/src/lib/h3.ts(다해상도) · 분석 h3_r10 DONE · 자동 면적 라우팅 PARTIAL · 검증 A

FR-ENG-ETA — ETA 예측 (룰 → ML)

콜드 스타트로 운행 데이터가 없으므로 룰 기반으로 시작해 정직한 "추정치"로 표기(eta_is_estimate=1)하고, 데이터 누적 후 ML로 전환한다(engine.md·ADR-0007). MAPE 목표는 룰 ≤25%·ML 후 ≤15%(KPI-ETA-001, 하드 SLO 아님). 마스터 §7 기준 handleEtaBatchack-only STUB(룰 미구현).

FR-ENG-ETA-001 룰 ETA 공식 + 시드 속도 (80/30/50) P0Phase 1T

요구. 룰 ETA는 ETA = now + Σ(잔여거리/구간속도) + Σ(잔여 dwell)반드시 계산해야 한다(MUST). 시드 속도: 고속 80·도심 30·기본 50 km/h; 기본 정차 dwell 30min; 속도/임계는 KV CONFIG에서 런타임 read(현재 미read)하고, 월 1회 구간 median 갱신; 거리/교통은 Google Maps로 보정. ETA는 eta_is_estimate=1로 추정 표기한다.

수용기준

  • Given 고속 잔여 160km, When 룰 계산, Then 80km/h로 2h + 잔여 dwell 합산.
  • Given ETA 산출, When 응답, Then eta_is_estimate=true 표기.
  • Given KV CONFIG 속도 변경, When 재계산, Then 런타임 read한 새 값 반영(현재 갭).
근거 마스터 §4.3(FR-ENG-ETA-001) · engine.md(룰 공식·KV 핫 config) · 구현 server/src/consumers/queue.ts handleEtaBatch STUB(ack-only·룰 미구현) / KV CONFIG 시드됐으나 미read(§7) · 미read 권위는 OD-007 · 검증 T
FR-ENG-ETA-002 룰 → ML 전환 트리거 (≥3,000 & MAPE>20%) P1Phase 2A

요구. 레인-클래스별 누적 ≥ 3,000 완료 운행 AND 룰-MAPE > 20%를 모두 만족할 때 그 레인-클래스의 ETA를 ML로 전환해야 한다(SHOULD). 피처: 시간대·요일·공휴일·실시간 위치/속도·구간 교통·날씨(JMA 무료 API)·과거 분포. 추론은 Workers AI(미래).

수용기준

  • Given 레인-클래스 누적 2,999건, When 평가, Then 룰 유지(전환 안 함).
  • Given 누적 ≥3,000 AND 룰-MAPE>20%, When 평가, Then 해당 레인-클래스 ML 전환 자격.
  • Given 누적 ≥3,000 AND 룰-MAPE 18%, When 평가, Then 룰 유지(두 조건 AND).
근거 마스터 §4.3(FR-ENG-ETA-002) · engine.md(룰→ML, ADR-0007) · R2 GPS 아카이브 학습셋(RET-GPS-001) · 구현 ML 파이프라인 ABSENT(Phase 2) · 검증 A

FR-ENG-EXC — 예외 감지 (룰 엔진)

예외는 이벤트 도착 시(Queue Consumer)와 시간 경과 시(Cron)에서 평가한다(engine.md). "안 일어남"(추적 손실·픽업 미발생)은 Cron 5분 sweep으로 감지한다. 마스터 §7 기준 Cron sweepActive빈 구현 STUB이며, 예외 룰의 알림 발행은 웹훅 STUB(FR-DLV-WH-001)에 의존한다.

ID예외임계(§4.3)감지 위치canonical/코드
EXC-001배달 지연약속 ETA +30minConsumer/CronLATE_DELIVERY_PREDICTED
EXC-002荷待ち 초과dwell > 120minConsumerDWELL_THRESHOLD_EXCEEDED
EXC-003추적 손실45min → TRACKING_LOSTCron 5minTRACKING_LOST
EXC-004경로 이탈2kmConsumerROUTE_DEVIATION
EXC-005비정상 정차계획 외 15minConsumerABNORMAL_STOP
EXC-006픽업 실패planned_pickup +30minCronPICKUP_FAILED
FR-ENG-EXC-001 배달 지연 (약속 ETA +30min) P0Phase 1T

요구. 현재 ETA가 약속 ETA의 +30min 버퍼를 초과하면 배달 지연 예외(HIGH)를 반드시 발행해야 한다(MUST). 웹훅 SHIPMENT_EXCEPTION(canonical_code: LATE_DELIVERY_PREDICTED, delay_minutes)로 전파.

수용기준

  • Given 약속 17:00, When 예측 ETA 17:35(+35min), Then 지연 예외 발행.
  • Given 약속 17:00, When 예측 ETA 17:20(+20min), Then 예외 없음(버퍼 내).
근거 마스터 §4.3(FR-ENG-EXC-001) · engine.md(예외 룰) · delivery.md(웹훅 페이로드) · 구현 룰 평가 server/src/consumers/queue.ts / ETA 의존(FR-ENG-ETA-001 STUB) PARTIAL · 검증 T
FR-ENG-EXC-002 荷待ち 초과 (dwell > 120min) P0Phase 1T

요구. dwell이 > 120min(법정 2시간; geofence.dwell_threshold_min, 테넌트 override 가능)을 초과하면 荷待ち 초과 예외를 반드시 발행해야 한다(MUST). 이 임계는 物流効率化法 2시간 목표와 정합하며, 측정 원천은 FR-GEO-003 dwell이다.

수용기준

  • Given dwell 143분, When EXITED 처리, Then 荷待ち 초과 예외(DWELL_THRESHOLD_EXCEEDED) 발행.
  • Given 테넌트 override 90분, When dwell 100분, Then 초과 예외(override 임계 적용).
근거 마스터 §4.3(FR-ENG-EXC-002) · engine.md(dwell 120 임계) · 구현 server/src/lib/geofence-engine.ts(dwell은 D1 geofence.dwell_threshold_min에서 read) DONE · 검증 T
FR-ENG-EXC-003 추적 손실 (45min → TRACKING_LOST) P0Phase 1T

요구. 마지막 유효 핑 후 45min이 경과하면 Cron 5분 sweep이 TRACKING_LOST 예외를 반드시 발행해야 한다(MUST). 일본 도심 터널이 많아 너무 짧으면 허위 경보이므로 오경보율 <5%를 목표로 한다(초기 확정치, 운영 조정).

수용기준

  • Given 마지막 핑 후 46분, When Cron 5min sweep, Then TRACKING_LOST 발행.
  • Given 마지막 핑 후 30분, When sweep, Then 예외 없음(임계 미달).
  • Given 28일 운영, When 측정, Then 추적 손실 오경보율 <5% 목표 추적.
근거 마스터 §4.3(FR-ENG-EXC-003) · engine.md(추적 손실 Cron, 30~60분→45 확정) · 구현 server/src/consumers/cron.ts sweepActive STUB(빈 구현, §7) · Cron */5 * * * * 설정 존재 · 검증 T
FR-ENG-EXC-004 경로 이탈 (2km) P1Phase 1T

요구. 위치가 계획 경로에서 2km 벗어나면 경로 이탈 예외(MEDIUM~HIGH)를 발행해야 한다(SHOULD). Queue Consumer에서 이벤트 도착 시 평가한다.

수용기준

  • Given 계획 경로, When 위치가 2.3km 이탈, Then 경로 이탈 예외.
  • Given 계획 경로, When 위치가 1.5km 이내, Then 예외 없음.
근거 마스터 §4.3(FR-ENG-EXC-004) · engine.md(경로 이탈 N km) · 구현 server/src/consumers/queue.ts 룰 · 경로 기준 데이터 의존 PARTIAL · 검증 T
FR-ENG-EXC-005 비정상 정차 (계획 외 15min) P2Phase 1T

요구. 계획 외 지점에서 15min 이상 정지하면 비정상 정차 예외(LOW~MEDIUM)를 발행할 수 있다(MAY). Queue Consumer 평가.

수용기준

  • Given 계획 외 지점, When 16분 정지, Then 비정상 정차 예외.
  • Given 지오펜스 내 정지, When 임계 경과, Then 비정상 정차 아님(荷待ち로 처리).
근거 마스터 §4.3(FR-ENG-EXC-005) · engine.md(예정 외 N분 정지) · 구현 server/src/consumers/queue.ts PARTIAL · 검증 T
FR-ENG-EXC-006 픽업 실패 (planned_pickup +30min) P0Phase 1T

요구. planned_pickup_at+30min이 경과해도 PICKUP 이벤트가 없으면 픽업 실패 예외(HIGH)를 반드시 발행해야 한다(MUST). "안 일어남" 감지이므로 Cron으로 평가한다.

수용기준

  • Given planned_pickup 09:00, When 09:31에도 PICKUP 없음, Then 픽업 실패 예외.
  • Given planned_pickup 09:00, When 09:12 PICKUP_COMPLETED, Then 예외 없음.
근거 마스터 §4.3(FR-ENG-EXC-006) · engine.md(픽업 미발생 Cron) · 구현 server/src/consumers/cron.ts sweepActive STUB(§7) · 검증 T

FR-DLV — 전달 계층 (웹훅·WebSocket·알림·추적링크)

처리된 데이터를 외부·사용자에게 전달한다(delivery.md). 원칙: REST+웹훅은 외부 연동, WebSocket은 UI 전용. ADR-0010에 따라 실시간 전송을 분리한다 — Flutter 단일-shipment 뷰=WebSocket(NFR-PERF-003 ≤1.5s), console 플릿 개요=6s 폴링(NFR-PERF-003b staleness 예산). 웹훅 카탈로그/계약은 IR-WH-001·IR-WS-001이 소유한다. 마스터 §7 기준 웹훅 전달(handleNotifyBatch)은 ack-only STUB, 웹훅 CRUD는 501이다.

FR-DLV-WH-001 웹훅 구독·서명·재시도 래더·auto-disable P0Phase 1T

요구. 화주는 이벤트 타입별 웹훅을 구독할 수 있어야 하고(MUST), 발송은 HMAC-SHA256({timestamp}.{raw_body}, endpoint_secret) 서명·replay ±300s 검증을 포함하며, 재시도는 래더 [0, 60, 300, 1800, 7200, 21600, 43200, 86400]s(8회, max_retries=8, 최대 전달수명 24h)를 따른다. 발신 타임아웃은 5s hard. 8연속 실패 시 구독을 auto-disable하고 대시보드에서 재활성화한다. 전달 성공률 목표 NFR-RELY-001 ≥99.0%/28일(초기 확정치).

수용기준

  • Given 구독 + 이벤트 발생, When 전달, Then X-LogiNippon-Signature(HMAC-SHA256) 헤더 + 멱등 Event-Id 포함 POST.
  • Given 수신자 5xx 반복, When 재시도, Then 0→60→300→1800…86400s 래더로 최대 24h.
  • Given 8연속 실패, When 한도 소진, Then 구독 disabled + 운영 알림.
  • Given 서명 timestamp가 ±300s 밖, When 수신자 검증, Then replay로 거부.
근거 마스터 §4.5(FR-DLV-WH-001 래더·HMAC·replay) · delivery.md(웹훅 시맨틱스) · 구현 server/src/routes/webhooks.ts CRUD 501 · server/src/consumers/queue.ts handleNotifyBatch STUB(ack-only, 구독 테이블·HMAC POST·전달상태 없음) · 0004 마이그레이션 신설 권고(§7) · 검증 T(webhook 테스트 일부 DONE)
FR-DLV-WS-001 실시간 전송 분리 (WebSocket vs 폴링) P0Phase 1T

요구. 실시간 전송은 ADR-0010에 따라 반드시 분리되어야 한다(MUST): Flutter 단일-shipment 뷰는 Durable Object WebSocket(NFR-PERF-003 p95 ≤1.5s/p99 ≤3s), console 플릿 개요는 6s 폴링(NFR-PERF-003b >60s stale·>300s drop). WebSocket은 UI 전용이며 외부 연동 채널로 노출하지 않는다(외부는 웹훅/REST).

수용기준

  • Given Flutter 단일 shipment 뷰, When 위치 push, Then WebSocket으로 p95 ≤1.5s 반영(목표).
  • Given console FleetMap, When 폴링, Then GET /v1/shipments/tracking 6s 주기, 핀 >60s stale 표시.
  • Given 외부 시스템, When WS 연결 시도, Then 연동 채널로 노출 안 함(웹훅/REST로 유도).
근거 마스터 §6(ADR-0010) · 마스터 §4.1(S6·S3b) · delivery.md/overview · 구현 server/src/do/shipment.ts(WS fan-out·hibernation) DONE · GET /v1/shipments/tracking(console 6s 폴링·하드의존) DONE(as-built) · Flutter ShipmentWsClient STUB(auth/reconnect 없음) · 검증 T
FR-DLV-NOTIFY-001 알림 채널 (LINE 1순위 + 커스텀 규칙) P1Phase 1D

요구. 운영팀 실시간 예외 알림은 LINE / LINE Works를 1순위 채널로 해야 한다(SHOULD) — 일본 B2B 현장 지배적(이메일·SMS보다 즉각 반응 확인). 화주가 커스텀 알림 규칙("지연 30분 초과 시 이메일")을 설정할 수 있어야 한다. 예외 큐는 심각도·시간순으로 정렬한다.

수용기준

  • Given HIGH 예외, When 발생, Then LINE 즉시 알림 + 웹훅(채널 우선순위 적용).
  • Given 화주 규칙 "지연>30분→이메일", When 조건 충족, Then 규칙대로 이메일 발송.
근거 delivery.md(알림 채널 우선순위·커스텀 규칙) · 구현 secret LINE_CHANNEL_TOKEN 바인딩 존재 · notify 배선 STUB(handleNotifyBatch ack-only, §7) · 검증 D
FR-DLV-TRACK-001 추적 링크 (受取人 ETA-only, 위치 비노출) P0Phase 1T

요구. 최종 고객(受取人) 추적 링크는 ETA만 노출하고 차량 실시간 위치를 반드시 비노출해야 한다(MUST, 個人情報保護法). GET /v1/shipments/{id}/trackingposition 객체는 화주/운송사 스코프에서만 채워지고 受取人 스코프에서는 생략되며 ETA·status만 반환한다. staleness_seconds·tracking_rate는 정직하게 노출한다.

수용기준

  • Given 受取人 스코프 토큰, When tracking 조회, Then position 생략, ETA/status만.
  • Given 화주 스코프, When tracking 조회, Then position(lat/lon/h3_r10) + staleness_seconds 노출.
  • Given 어느 스코프든, When tracking 응답, Then tracking_rate 정직 노출.
근거 delivery.md(受取人 ETA-only·position 생략) · SR-PII-001 · 구현 console CustomerTrack 컴포넌트 존재 · 서버 스코프 마스킹 PARTIAL · tracking 엔드포인트 DONE(as-built /v1/shipments/tracking) · 검증 T

FR-RPT — 규제 리포트·분석 (고위)

규제 출력이 킬러 피처다(delivery.md). 여기서는 "무엇을 만들지"만 고위 수준으로 고정하고, 컬럼·인코딩·집계식·라이프사이클 등 상세 명세는 regulatory.html(RR-*)이 소유한다. 법정 공식 포맷이 미확정인 항목은 RR-LEGAL-001 법무 서명 플래그를 단다.

RR-LEGAL 플래그. 実運送体制管理簿·荷待ち·구속시간의 법정 공식 출력 포맷·필수 컬럼·CSV 인코딩(Shift_JIS 여부)은 법무 확인 전이다 추정. 본 페이지의 FR-RPT 카드는 "이 데이터를 생성·집계·보존한다"는 기능 요구만 고정하고, 포맷 준수 책임은 RR-LEGAL-001·OD-004로 이관한다.

FR-RPT-NIMACHI-001 荷待ち 기록 리포트 (고위) P0Phase 1T

요구. 시스템은 stop.dwell_minutes(FR-GEO-003)를 거점별·운송별·기간별로 집계해 荷待ち 기록 리포트를 반드시 생성해야 한다(MUST, 物流効率化法 2026.4 荷待ち 2시간 목표 확인). 임계 2시간 초과 건을 표시한다. 집계식·컬럼은 RR-NIMACHI-001 소유.

수용기준

  • Given GET /v1/reports/nimachi?from=&to=, When 호출, Then 메타 + R2 다운로드 URL(CSV).
  • Given dwell >120 건 포함 기간, When 생성, Then 초과 건 플래그된 집계.
근거 delivery.md(荷待ち 기록) · engine.md(dwell 원천) · 상세 RR-NIMACHI-001 · 구현 server/src/lib/reports.ts · server/src/routes/reports.ts DONE(荷待ち CSV end-to-end) · 검증 T
FR-RPT-JITSUUNSO-001 実運送体制管理簿 (고위) P0Phase 1T

요구. 시스템은 carrier.tier(元請/下請/孫請)·parent_carrier_id 계층에서 청부 체인을 구성하고 실시간 위치로 검증된 실운송 차량을 결합해 実運送体制管理簿를 반드시 자동 생성해야 한다(MUST, 改正物流法 2025.4 元請 의무화 확인). 컬럼·provenance는 RR-JITSUUNSO-001 소유.

수용기준

  • Given GET /v1/reports/jitsuunso?from=&to=, When 호출, Then 청부 체인 + 실운송 차량 결합 메타 + 다운로드 URL.
  • Given 다단계 하청 화물, When 생성, Then actual_carrier(실시간 검증)가 관리부에 반영.
근거 delivery.md(実運送体制管理簿 자동 생성) · 상세 RR-JITSUUNSO-001 · 구현 GET /v1/reports/jitsuunso 501(STUB, §7) · 검증 T
FR-RPT-KOSOKU-001 구속시간 관련 기록 (고위, 보조 데이터) P1Phase 1A

요구. 운행·체류 타임스탬프를 구속시간 산정의 객관적 보조 데이터로 제공해야 한다(SHOULD). 전용 노무관리 대체가 아니라 데이터 제공이다(2024년 문제 — 연 구속 3,400h·시간외 960h 상한 확인). 필드·집계는 RR-KOSOKU-001 소유.

수용기준

  • Given 운행·체류 타임스탬프, When 집계, Then 구속시간 산정용 일/기간별 데이터 산출.
  • Given 산출물, When 검토, Then "보조 데이터" 명시(노무관리 대체 아님).
근거 delivery.md(구속시간 기여) · 상세 RR-KOSOKU-001 · 법무 플래그 RR-LEGAL-001 · 구현 전용 산출 ABSENT · 검증 A
FR-RPT-RANGE-001 리포트 범위·서명 URL TTL P0Phase 1T

요구. 리포트 범위는 from < to AND ≤ 92일이어야 하며(초과 422), 기본은 최근 30일 JST다. R2 산출물 다운로드 서명 URL TTL은 300s반드시 제한한다(MUST). R2 egress 무료라 반복 다운로드 비용은 0이다.

수용기준

  • Given from~to 93일, When 리포트 요청, Then 422.
  • Given from/to 생략, When 요청, Then 최근 30일 JST 기본 적용.
  • Given 다운로드 서명 URL 발급 301초 후, When 접근, Then 만료(TTL 300s).
근거 마스터 §4.5(FR-RPT-RANGE-001 ≤92일·300s) · delivery.md(리포트 다운로드) · 구현 server/src/routes/reports.ts · server/src/lib/reports.ts(荷待ち 경로) DONE / 범위 강제·TTL 검증 PARTIAL · 검증 T
FR-RPT-ANALYTICS-001 캐리어 스코어카드 (OTD·dwell·tracking rate) P1Phase 1A

요구. 캐리어별·레인별 OTD·dwell(荷待ち)·tracking rate·예외율·CO₂ 추정 스코어카드를 제공해야 한다(SHOULD) — 대형 화주(특정 제1종 荷主) 도입 유도 핵심 도구. 지오 분석은 h3_r10GROUP BY로 공간 DB 없이 산출한다. KPI 공식은 north-star-kpi.html 소유.

수용기준

  • Given GET /v1/analytics/carriers/{id}, When 호출, Then OTD·dwell·tracking rate·예외율 지표 반환.
  • Given h3_r10 채워진 stop/event, When 집계, Then 체류 밀도 히트맵을 공간 DB 없이 산출.
근거 delivery.md(캐리어 스코어카드·지오 분석) · KPI 공식 KPI-OTD-001·KPI-TRACK-001 · 구현 GET /v1/analytics/carriers/:id ABSENT(§7) · console Reports 컴포넌트 존재 · 검증 A

FR-PROV — 운영자 프로비저닝

ADR-0010에 따라 console(Astro+Svelte)이 운영자/운송사 컨트롤 타워로 공식 채택되며, 프로비저닝(테넌트·캐리어·드라이버·차량·지오펜스 등록)을 담당한다. 마스터 §7 기준 console Provisioning 컴포넌트는 존재하나 서버 측 POST /v1/admin/drivers 등 라우트는 ABSENT다.

FR-PROV-001 운영자 프로비저닝 (테넌트·캐리어·드라이버·차량) P0Phase 1T

요구. 운영자는 console에서 테넌트·캐리어(tier·parent)·드라이버·차량을 프로비저닝할 수 있어야 한다(MUST). 생성된 드라이버 자격은 admin-provisioned(FR-AUTH-005)이며, 캐리어 계층은 FR-RPT-JITSUUNSO-001(実運送体制管理簿)의 청부 체인 원천이다.

수용기준

  • Given 운영자 console, When 드라이버 생성, Then 자격 발급 + 드라이버 로그인 가능(FR-AUTH-005).
  • Given 캐리어 등록, When tier/parent 지정, Then 청부 계층이 実運送体制管理簿에 반영.
  • Given 차량 등록, When 드라이버 배정, Then shipment-vehicle-driver 연결 가능.
근거 마스터 §6(ADR-0010 console 채택) · delivery.md(멀티테넌시·프로비저닝) · 구현 console Provisioning 컴포넌트 존재 / 서버 POST /v1/admin/driversABSENT(§7) · 시드 경로로 부트스트랩 scripts/seed DONE · 결정 OD-005 · 검증 T
FR-PROV-002 지오펜스 등록 UI P0Phase 1D

요구. 운영자는 console에서 거점 지오펜스 폴리곤을 등록·수정할 수 있어야 한다(MUST). 저장 시 서버는 H3 cover·KV/D1 재기록을 원자적으로 수행하고(FR-GEO-CRUD-001), 면적별 해상도 정책(FR-GEO-H3RES-001)을 적용한다.

수용기준

  • Given 운영자가 폴리곤 그림, When 저장, Then 지오펜스 + H3 cover + KV 역인덱스 생성(FR-GEO-CRUD-001).
  • Given 폴리곤 면적, When 저장, Then 면적별 해상도(res8/9/10/11) 자동 적용.
근거 마스터 §6(ADR-0010) · engine.md(지오펜스 등록 사전계산) · 구현 console 지오펜스 UI PARTIAL / 서버 CRUD 라우트 ABSENT(§7)FR-GEO-CRUD-001 · 검증 D

가칭·미프로비저닝 주의. 'LogiNippon'은 전 레포 가칭이며 모든 wrangler D1/KV/R2 ID는 placeholder(0000…000a)로 미프로비저닝 상태다. 사전 출시 확정/리네임은 OD-002(정식명)·OD-005(리소스 ID)에서 다룬다. 전 그룹의 구현 성숙도 종합과 검증 매핑은 acceptance-traceability.html 추적성 매트릭스를 참조.

근거·상호참조