/* * 축의 값을 **어디서 읽나** — 규칙 한 곳. (design/profiles/ems.md §10 6.5단계) * * ── 왜 함수로 빼나 ───────────────────────────────────────────────────────── * 개념 지도는 상태 축을 `state[축이름]` 으로 읽고 있었다. 에너지의 수요 구간은 `state.energy.closed` * 에 살아서 그 규칙으로는 **언제나 `undefined`** 다 — 카드가 「관측 없음」으로 굳고, 원인은 화면도 * 서버도 말해 주지 않는다(축은 선언돼 있고 상태에도 값이 있는데 이름이 안 맞을 뿐이다). * * 커널 선언이 이미 그 자리를 말한다(`TwinAxisInfo.path`). 그래서 규칙은 한 줄이다: * **경로가 있으면 그 경로로, 없으면 축 이름으로.** 리졸버 안에 두면 시험이 DB 를 물어야 하므로 * 순수 함수로 빼 둔다. */ /** 중첩 경로를 따라간다 — 중간이 없으면 `undefined`(빈 객체로 메우지 않는다). */ export function atPath(obj: any, path: string): any { return path.split('.').reduce((o: any, k: string) => (o == null ? undefined : o[k]), obj) } /** * 그 축의 값 — 배열이면 목록, 객체면 그릇, 없으면 `undefined`. * * `undefined` 와 빈 배열은 **다르다**: 앞은 「모른다·아직 안 옴」이고 뒤는 「없다」다. 이 함수는 그 * 구별을 지우지 않는다 — 소비처가 `emptyBecause` 를 그 차이로 만든다. */ export function axisValueOf(state: any, axis: { axis: string; path?: string }): any { if (!state || !axis?.axis) return undefined return axis.path ? atPath(state, axis.path) : state[axis.axis] }