경험 · UX 원칙
LogiNippon은 같은 트래킹 데이터를 역할별로 다른 맥락으로 보여주는 다중 역할 컨트롤 타워다. 이 페이지는 화면을 그리는 방식이 아니라 제품 UX 요구사항을 소유한다 — 역할 게이트 IA, '맵 never-empty', 고령(평균 50+) 드라이버를 위한 '버튼 없는' 자동화, ETA·tracking-rate를 숨기지 않는 정직 UI, LINE 1순위 알림, ja-first i18n, 受取人 추적 링크의 위치 비노출, 증빙사진·접근성. 기술 계약은 모두 TRD로 교차링크하고 여기서는 중복하지 않는다. 본 페이지는 새 PRD ID를 정의하지 않으며, 에픽·스토리·여정의 UX 측면을 묶는 원칙 레이어다.
이 페이지의 ID 정책. 경험·UX 원칙은 PRD 네임스페이스에서 별도 ID를 소유하지 않는다(마스터 스펙 §3). 모든 요구사항은 epics-stories.html의 US-*, journeys.html의 JRN-*, personas.html의 PRS-*로 추적되며, 구현 계약은 TRD FR-*/RR-*로 링크한다. 모든 SLO/KPI 수치는 설계 목표(진입 단계 초기 목표, 달성치 아님)이다.
1. 역할 게이트 IA — navForRole
IA는 역할이 화면 집합을 게이트하는 단일 원칙으로 정의된다. 라이브 ConsoleApp.svelte의 navForRole()가 공통 탭(map) + 역할별 분기를 반환하고, session.isAdmin 여부가 두 개의 완전히 다른 셸을 가른다: isAdmin 역할 = 다크 사이드바 콘솔(슬레이트 사이드바·탭 네비·드릴다운), 비-admin = 로그인 없는 self-track 뷰(CustomerTrack). 데이터 격리는 클라이언트가 아니라 서버 repo 게이트(테넌트 스코프, 타 테넌트 404)가 책임진다 — 네비를 숨기는 것은 보안이 아니라 UX다.
UX 요구사항(설계). 네비는 역할이 실제로 행동할 수 있는 화면만 노출한다(MUST). 예: CARRIER_OPS/SUPPORT에게 화주 책임인 규제(reports)·발급(issue) 탭을 보이지 않는다(코드 주석: "SHIPPER 화면(reports/issue)은 화주 책임이라 노출 안 함"). 공통 진입점은 항상 map이며, 모든 역할은 지도에서 시작한다(MUST).
| 역할(코드) | 셸 | 화면 집합(navForRole) | 핵심 컴포넌트 | 페르소나 |
|---|---|---|---|---|
| PLATFORM_ADMIN | 다크 사이드바 | map · admin · carriers · factory · ships |
FleetMap · AdminPanel · CarrierManager · ShipmentFactory · ShipmentsTable | PRS-PLATFORM |
| CONSIGNEE_ADMIN CONSIGNEE_VIEWER | 다크 사이드바 | map · inbound(到着便) · dashboard |
FleetMap · InboundView · Dashboard(캐리어 비교) | PRS-CONSIGNEE |
| SHIPPER_ADMIN SHIPPER_VIEWER | 다크 사이드바 | map · ships · reports(規制) · issue |
FleetMap · ShipmentsTable · Reports(NIMACHI) · Provisioning(stub) | PRS-SHIPPER |
| CARRIER_OPS SUPPORT | 다크 사이드바 | map · ships (최소 권한) |
FleetMap · ShipmentsTable | PRS-CARRIER |
| DRIVER | Flutter 앱 (콘솔 아님) | 案件 수신 · 受託 · 주행 · 完了報告 (앱 플로우) | 드라이버 앱 STUB(최대 갭) | PRS-DRIVER |
| 非admin (受取人 등 추적 링크 사용자) | self-track 뷰 | CustomerTrack — ETA만, 차량 위치 비노출 |
CustomerTrack | PRS-CONSIGNEE (수령인 서브) |
2. 핵심 UX 원칙 — '맵 never-empty'
관제 화면의 1차 실패 모드는 "회색 빈 지도"다. GPS가 1차 데이터원인데 드라이버 앱이 STUB이라 라이브 위치 핑이 0건일 수 있다 — 그 순간에도 지도가 비어 보이면 제품이 죽은 것처럼 보인다. 따라서 FleetMap은 라이브 차량 마커와 별개로, 진입 즉시 active 화물의 stop 핀(픽업/경유/배송)을 항상 그린다.
맵 never-empty (MUST). 라이브 위치 핑이 0건이어도 지도는 비어 보이지 않아야 한다. 진입 시 /shipments/map-overview 한 응답으로 active 화물 전체의 stop 좌표를 받아 stop pin을 그리고 fitBounds로 전체가 한눈에 보이도록 맞춘다(임의 1대로 점프 금지 — 다대 관제 UX). 코드 주석 그대로: "지도는 절대 빈 화면이 아님".
상태색 마커(MUST). 라이브 차량은 화물 상태(IN_TRANSIT/DELIVERED/EXCEPTION 등)를 statusHex 색으로 칩+핀에 인코딩한다 — 색이 곧 "지금 처리할 것 vs 모니터링"의 1차 신호다. 팝업은 화물 ID·상태·속도·ETA를 묶어 보여주고, 6초 폴링(/shipments/tracking)으로 위치를 갱신한다(WebSocket은 UI 전용, 외부 연동 아님).
클러스터링(설계, Later). 수백~수천 차량 규모에서는 마커 클러스터링이 필요하다(밀집 시 개별 핀 가독성 붕괴). 진입 단계 console은 클러스터링 미구현 설계 — 대규모는 WebGL 레이어로 확장 추정. 지도 베이스맵은 CARTO Voyager raster(OSM 기반)로 키 불필요·상업 사용 허용·한·일 도시 detail 충분이며, 타일 에러 시 회색 빈 화면을 방지하는 폴백 안내를 둔다.
3. 고령 드라이버 UX — '공유 버튼 없는' 자동화
대형 트럭 기사 평균 연령 50세 초과 확인. 고령 현장 드라이버에게 "위치 공유 버튼을 누르세요"는 실패하는 UX다 — 누르는 것을 잊거나, 끄는 것을 잊거나, 운전 중 조작이 위험하다. 따라서 드라이버 앱의 설계 원칙은 조작을 추가하는 것이 아니라 제거하는 것이다: 위치 공유는 버튼이 아니라 잡 라이프사이클의 부산물로 자동 ON/OFF 된다.
'공유 버튼 없는' 자동화 (MUST). 案件 受託(수락)=위치 공유 자동 ON, 完了(완료 보고)=공유 자동 OFF. 거점 진입/이탈은 드라이버가 버튼을 누르지 않아도 서버 지오펜스가 ENTER/EXIT/dwell을 자동 도출한다(H3 히스테리시스 엔진). 드라이버가 의식적으로 다루는 것은 "수락"과 "완료" 두 개의 큰 버튼뿐이다.
- 案件 푸시 수신FCM/APNs 푸시로 새 案件 도착. 드라이버는 알림 한 번으로 무엇을 어디로 옮기는지 안다. (푸시 배선 미완 STUB)
- 큰 버튼 한 번 = 受託대형 '수락' 버튼 1탭 → 위치 공유 자동 ON. 별도 '공유 켜기' 동작 없음.
- 주행 (핸즈프리)배경 GPS 적응 샘플링·오프라인 버퍼. 운전 중 조작 0. 터널/오프라인에서도 추적 유지(버퍼 후 복구).
- 거점 도착 (자동 이벤트)지오펜스 ENTER/EXIT를 서버가 자동 도출 → 도착/dwell 타임스탬프가 버튼 없이 찍힘.
- 完了報告 + 증빙사진대형 '완료' 버튼 1탭 + 증빙사진(R2 업로드). 완료 시 공유 자동 OFF.
구현 상태: 드라이버 앱 = scaffold STUB(최대 갭). Flutter 앱은 배경 위치 서비스가 주석처리되어 있고(hasConsent→false, token→null, JobsRepository [], 푸시·증빙·l10n 미배선) 잡 라이프사이클이 실제로 돌지 않는다. GPS가 1차 데이터원이므로 이 갭이 MVP exit를 막는 최우선 항목이다(PR-02). 위 UX는 요구사항(설계)이며, ShipmentFactory의 위치 시뮬로 실 GPS 전에 전 과정을 실증한다(JRN-FACTORY).
4. 정직 UI — ETA '추정치'·tracking_rate·staleness
스마트폰 GPS가 1차 센서이므로 100% 추적은 비현실이다(터널·실내·배터리·동의 미수신·오프라인). 정직은 단순한 윤리가 아니라 포지셔닝이다 — 한계를 숨기면 "트래킹이 또 틀렸다"는 신뢰 붕괴를 부른다. 따라서 UI는 불확실성을 숨기지 않고 명시적으로 노출한다.
| 노출 항목 | 의미 | UI 요구사항 |
|---|---|---|
eta_is_estimate: true | ETA는 약속이 아니라 추정치 | ETA를 항상 "추정치"로 라벨링한다(MUST). 약속 ETA(promised)와 시각적으로 구분하고, 지연(delay_minutes)은 별도 표기. |
tracking_rate | 이 화물의 추적 커버리지(0~1) | "얼마나 보이는가"를 정직하게 노출(MUST). 낮을수록 신뢰도가 낮음을 사용자가 알 수 있어야 한다. |
staleness_seconds | 마지막 핑 경과 시간 | 위치가 오래됐으면 "마지막 업데이트 N분 전"을 표시(MUST). 오래된 위치를 실시간처럼 보이지 않게 한다. |
| Tracking Rate (North Star) | received/expected (28일) | 100%를 목표로 광고하지 않는다(MUST). MVP exit floor ≥85%·정상 지향 ≥92%는 모두 설계 목표. |
정직 = 포지셔닝(설계). ETA를 약속처럼, 추적률을 100%처럼 제시하지 않는다. North Star인 Tracking Rate 자체가 "부산물-수집의 진실 척도"이며 staleness와 함께 정직하게 노출된다. 현재 METRICS.writeDataPoint 미구현으로 SLI는 미측정(PR-09) — 따라서 본 페이지의 모든 추적률 목표는 달성치가 아닌 진입 단계 초기 목표다.
5. 알림 UX — LINE/LINE Works 1순위
일본 B2B 현장에서 LINE / LINE Works가 지배적이다 확인 — 이메일·SMS보다 즉각 반응한다. 따라서 예외 알림의 1순위 채널은 LINE/LINE Works이고, 나머지는 사용 사례별로 우선순위화된다. 예외 큐는 심각도·시간순으로 정렬되며("지금 처리할 것 vs 모니터링"), 화주는 커스텀 알림 규칙을 설정할 수 있다("지연 30분 초과 시 이메일").
| 우선 | 채널 | 사용 사례 | 일본 특화 결정 |
|---|---|---|---|
| 1순위 | LINE / LINE Works | 운영팀 실시간 예외 | 일본 B2B 현장 지배적 — 즉각 반응 확인 |
| 2 | 이메일 | 일일 요약·중간 심각도 | 비동기 |
| 3 | 앱 푸시 | 드라이버 案件 지시 | 권한 관리 |
| 4 | 웹훅 | 화주 TMS/WMS 연동 | 재시도 필수(HMAC 서명·멱등) |
| 5 | SMS | 受取人 완료 알림·긴급 | 캐리어 규정 준수 |
커스텀 알림 규칙 (설계). 화주는 "어떤 예외를·어떤 심각도부터·어느 채널로" 받을지 규칙을 정의할 수 있어야 한다(SHOULD). 예외 큐는 처방적 추천("X 창고에서 재루팅 가능")을 곁들인다 — 단순 경보가 아니라 행동 가능한 인사이트.
구현 상태: 알림 전달 STUB. 예외 감지 엔진은 PARTIAL이나, 알림 전달(handleNotifyBatch는 ack-only)·룰 ETA(handleEtaBatch ack-only)·tracking-loss sweep(빈 cron)이 STUB로, 5개 STUB 해소가 MVP exit(GATE-P1) 게이트다. LINE 1순위는 요구사항(설계)이며 현 console에는 채널 배선이 없다.
6. i18n — ja 기본 · en/ko
제품의 사용자는 일본 현장이다. 따라서 콘솔 UI는 ja 기본이고 en/ko를 지원한다(LocaleSwitcher, console DONE). 단, 다국어화가 해서는 안 되는 것이 있다: 거점·법령·도메인 용어의 원어 유지.
원어 보존 (MUST). 法令·制度·拠点·役職 용어는 번역하지 않고 원어로 유지한다 — 荷主 · 荷待ち · 実運送体制管理簿 · 元請/下請/孫請 · 受取人 · 特定第一種荷主 · 到着便 · 案件 · 受託 · 完了報告. 監督官庁 제출·규제 협상 맥락에서 번역어는 오해·법적 리스크를 만든다. 화면 라벨(到着便·予定·到着 등)과 상태색은 locale에 따라 키 전환하되, 법령 고유명은 고정한다.
7. 受取人 추적 링크 — 로그인 없음 · 위치 비노출
受取人(최종 수령인)은 PRS-CONSIGNEE의 경량 서브-페르소나로, "몇 시에 오는가"만 궁금하다. 이들에게는 계정·콘솔이 과하다 — 로그인 없는 공유 추적 링크(CustomerTrack, 非admin self-track 뷰)를 준다. 그러나 결정적 제약이 있다: 차량 실시간 위치를 노출하지 않는다 — 드라이버 위치는 個人情報保護法상 개인정보가 될 수 있다.
受取人 ETA-only (MUST). 受取人 스코프에서는 position이 생략되고 ETA만 노출된다(차량 lat/lon 마스킹, 個人情報保護法). 로그인 불필요. 단 ETA는 4절의 정직 원칙을 그대로 적용 — '추정치' 라벨·staleness·tracking_rate를 숨기지 않고, 완료 시 SMS/메일로 알린다.
APPI 동의 게이트(설계, STUB). 드라이버 위치 수집은 APPI 동의가 전제다. 현재 ingest 경로에 consentGate가 미배선(APPI 노출, PR-06)이며, 라이브 전 하드 코드 게이트로 배선해야 한다. 受取人 마스킹·동의 책임은 PRS-LEGAL 거버넌스가 관장한다.
8. 접근성 · 증빙사진(R2)
사용자 스펙트럼이 넓다 — 고령 현장 드라이버, FAX·전화·수기에 익숙한 중소 캐리어, 다국어 화주 물류팀. 접근성은 부가가 아니라 채택의 전제다.
- 대형 터치 타깃·고대비(MUST). 드라이버 앱의 핵심 동작('수락'·'완료')은 운전 환경·노안을 고려해 대형 버튼·고대비·최소 텍스트로 둔다. 콘솔은 상태색을 색에만 의존하지 않고 라벨을 동반한다(색맹 고려).
- 증빙사진 (MUST). 完了報告 시 증빙사진을 R2에 업로드한다(egress 무료). 사진은 배달 증명이자 캐리어의 "실제 배달했음"의 객관 근거(JTBD-11)다. 현재 드라이버 앱 STUB로 증빙 업로드 미배선 STUB.
- 키보드·스크린리더(SHOULD). 콘솔의 네비·테이블·지도 상태 배지는
aria-current·aria-live등으로 보조기술에 노출한다(FleetMap 상태 배지는role="status"aria-live="polite"— console DONE). - 모바일 반응형(MUST). 다크 사이드바는 모바일에서 슬라이드인 드로어로, md+에서 고정으로 전환한다(현장·차량 내 사용 고려, console DONE).
근거·상호참조
- 마스터 스펙 §4(역할/스코프 매트릭스·navForRole IA)·§7(여정)·§10(정직 포지셔닝)·§14(as-built 상태) — 모든 역할명·수치·상태 라벨의 출처.
- 소스(verbatim):
console/src/components/ConsoleApp.svelte(navForRole·isAdmin다크 사이드바 vsCustomerTrack) ·console/src/components/FleetMap.svelte(맵 never-empty·/shipments/map-overviewstop pin·statusHex·CARTO Voyager·6s 폴링) ·techspec/05-delivery-layer/delivery.md(LINE 1순위 알림·受取人 ETA-only·정직 노출·증빙 R2·고령 드라이버) ·techspec/00-overview/vision.md(평균 50+·페인 우선·프라이버시 원칙). - PRD 상호참조: personas.html(PRS-*) · journeys.html(JRN-*) · jobs-to-be-done.html(JTBD-*) · epics-stories.html(EPIC-*/US-*) · success-metrics-kpi.html(North Star) · risks-open-questions.html(PR-*).
- TRD(외부): FR-AUTH-004 · FR-DLV-WS-001 · FR-DLV-TRACK-001 · FR-ACQ-AUTOSHARE-001 · FR-ACQ-CONSENT-001 · FR-DLV-NOTIFY-001 · KPI-TRACK-001 · SLO-S7 · SR-001.
- 라벨 규약: 시장·법령 사실 확인/추정, 제품 목표·결정 설계(진입 단계 초기 목표, 달성치 아님). 모든 SLO/KPI 수치는 설계. 공식 규제 포맷 미확정은 RR-LEGAL-001 게이트.