/* * **저널에 그 축의 사실이 있나** — 「아직 없다」와 「왔는데 상태에 없다」를 가른다 (2026-08-22). * * ── 왜 필요했나 (실측) ────────────────────────────────────────────────────── * 화면이 「작업 — 아직 하나도 없습니다」라고 말하는데, 그 트윈의 저널에는 `task.status` 5,786건이 * 있었고 **마지막 상태가 `in-progress`** 였다. 사용자가 「작업 지시 835건인데 작업이 0인 이유」를 * 물어 반나절을 조사해 원인을 찾았다: 관측 모드 커널이 관측기의 작업을 스냅샷으로 내보내지 않는다 * (커널 자리이고 그 레인에 넘겼다). * * 화면 몫은 다른 것이다. **카드가 단정하면 안 된다.** 「아직 하나도 없습니다」는 「선언은 있고 사실이 * 아직 일어나지 않았다」는 뜻인데, 실제로는 「사실이 일어났는데 상태에 오지 못했다」였다 — 그 차이가 * 사용자를 반나절 조사로 보낸다. * * 그래서 상태가 빈 축에 대해 **저널을 한 건만 물어본다.** 있으면 화면이 그렇게 말한다. * * ── 어휘의 집 ─────────────────────────────────────────────────────────────── * 어느 사건이 어느 축을 채우는지는 **커널이 선언한 낱말**에서 온다(`OP_EVENT`·`ENERGY_EVENT`, 그리고 * EPCIS 접두사). 여기서 이름을 새로 만들지 않는다 — 커널이 낱말을 늘리면 이 표에 한 줄이 늘고, * 표에 없는 축은 **아무 말도 하지 않는다**(모르는 것을 지어내지 않는다). */ import { anyJournalRow } from '../twin-event/journal-count.js' /** * 상태 축 → 그 축을 채우는 사건 낱말. 표에 없으면 판단하지 않는다. * * 물품은 EPCIS 로 온다(사건 이름이 여러 개다) — 그래서 접두사로 본다. */ const AXIS_EVENTS: Record = { tasks: { types: ['task.status'] }, orders: { types: ['order.status'] }, items: { prefix: 'epcis.' }, demandWindows: { types: ['energy.demand.window'] } } /** 이 축의 사실이 저널에 한 건이라도 있나. 판단할 수 없으면 `null`(모른다). */ export async function axisSeenInJournal( repo: any, where: Record, axis: string ): Promise { const spec = AXIS_EVENTS[axis] if (!spec) return null if (spec.types) { for (const t of spec.types) { if (await anyJournalRow(repo, { ...where, eventType: t })) return true } return false } /* * 접두사로 찾는 축(물품) — 한 건만 있으면 된다. `Like` 를 쓰는 대신 **낱말 목록**을 쓰지 않는 이유는 * EPCIS 사건 이름이 커널에서 자라기 때문이다(객체·집약·변환·거래…). 이름을 여기 베끼면 커널이 * 하나 늘릴 때 이 자리가 알리지 않고 낡는다. */ const { Like } = await import('typeorm') return anyJournalRow(repo, { ...where, eventType: Like(`${spec.prefix}%`) }) }