/* * 스토커 게이지 세그먼트 계산 (2D/3D 공통 단일 소스). * * 2D(StockerCapacityBar) 와 3D(machine-3d buildGauge) 가 각자 세그먼트 비율/색상을 * 중복 계산하면 필드/색상 drift 가 발생한다(실제로 carrier 필드명이 어긋난 전례). * 이 모듈이 carrier/abnormal 세그먼트 계산의 단일 소스이며 순수 함수라 단위 테스트가 * 용이하다. 표현(라벨 포맷·watermark·테두리색)은 렌더러별로 다르므로 여기서 다루지 않는다. * * canonical 데이터 필드(2D 기준): * - carrier : CARRIER_FULL / CARRIER_EMPTY / CARRIER_EMPTY_EMPTY * - abnormal : CARRIER_NORMAL / CARRIER_ABNORMAL */ import { Legend } from '../features/mcs-status-default' export type StockerGaugeMode = 'capacity' | 'carrier' | 'abnormal' export interface GaugeSegment { /** 세그먼트 라벨(2D per-segment 표시용, '\n' 은 줄바꿈). */ label: string /** 카운트 원본값. */ count: number /** 전체 대비 비율 (0~1). total 이 0 이면 0. */ ratio: number /** 채움 색상. */ color: string } /** carrier 세그먼트 색상 (2D/3D 공통). LEGEND_CARRIER 기본값과 동일 — 테마 미지정 시 폴백. */ export const CARRIER_COLORS = { full: '#4AA7FE', empty: '#8BDA5B', emptyEmpty: '#019D59' } export interface CarrierColors { full: string empty: string emptyEmpty: string } /** * carrier legend(테마)에서 full/empty/emptyEmpty 색상 추출. * carrier 컴포넌트의 색 취득 로직(statusColor = legend[status] || legend.default)과 동일하게, * status 키(FULL/EMPTY/EMPTYEMPTY)로 직접 조회한다. 키/default 모두 없으면 CARRIER_COLORS 로 폴백. */ export function carrierColorsFromLegend(legend?: Legend): CarrierColors { const pick = (key: string, fallback: string) => (legend && (legend[key] || legend.default)) || fallback return { full: pick('FULL', CARRIER_COLORS.full), empty: pick('EMPTY', CARRIER_COLORS.empty), emptyEmpty: pick('EMPTYEMPTY', CARRIER_COLORS.emptyEmpty) } } /** abnormal 세그먼트 색상 (2D/3D 공통). */ export const ABNORMAL_COLORS = { normal: '#8BDA5B', abnormal: '#F4B4B4' } function num(v: unknown): number { const n = Number(v) return Number.isFinite(n) && n > 0 ? n : 0 } /** * 캐리어 상태 세그먼트 (Full / Empty / EmptyEmpty). total 0 이면 모든 ratio 0. * colors 미지정 시 CARRIER_COLORS(기본). 테마 색은 carrierColorsFromLegend 로 만들어 전달. */ export function carrierSegments( full: unknown, empty: unknown, emptyEmpty: unknown, colors: CarrierColors = CARRIER_COLORS ): GaugeSegment[] { const f = num(full) const e = num(empty) const ee = num(emptyEmpty) const total = f + e + ee const r = (c: number) => (total > 0 ? c / total : 0) return [ { label: 'Full', count: f, ratio: r(f), color: colors.full }, { label: 'Empty', count: e, ratio: r(e), color: colors.empty }, { label: 'Empty\nEmpty', count: ee, ratio: r(ee), color: colors.emptyEmpty } ] } /** 캐리어 정상/비정상 세그먼트 (Normal / Abnormal). total 0 이면 모든 ratio 0. */ export function abnormalSegments(normal: unknown, abnormal: unknown): GaugeSegment[] { const n = num(normal) const a = num(abnormal) const total = n + a const r = (c: number) => (total > 0 ? c / total : 0) return [ { label: 'Normal', count: n, ratio: r(n), color: ABNORMAL_COLORS.normal }, { label: 'Abnormal', count: a, ratio: r(a), color: ABNORMAL_COLORS.abnormal } ] } /** 캐리어 세그먼트 — canonical raw data(CARRIER_*) 에서 직접. 3D 처럼 raw data 소스용. */ export function carrierSegmentsFromData(data: any, colors?: CarrierColors): GaugeSegment[] { const d = data && typeof data === 'object' ? data : {} return carrierSegments(d.CARRIER_FULL, d.CARRIER_EMPTY, d.CARRIER_EMPTY_EMPTY, colors) } /** 비정상 세그먼트 — canonical raw data(CARRIER_*) 에서 직접. */ export function abnormalSegmentsFromData(data: any): GaugeSegment[] { const d = data && typeof data === 'object' ? data : {} return abnormalSegments(d.CARRIER_NORMAL, d.CARRIER_ABNORMAL) }