@operato/twin-kernel 0.0.6 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -12,6 +12,7 @@ export * from './domain-catalog.ts';
12
12
  export * from './allocation-policy.ts';
13
13
  export * from './duration-estimator.ts';
14
14
  export * from './state-projector.ts';
15
+ export * from './task-fold.ts';
15
16
  export * from './face2-adapter.ts';
16
17
  export * from './runtime.ts';
17
18
  export * from './yms-profile.ts';
package/dist/index.js CHANGED
@@ -12,6 +12,7 @@ export * from "./domain-catalog.js";
12
12
  export * from "./allocation-policy.js";
13
13
  export * from "./duration-estimator.js";
14
14
  export * from "./state-projector.js";
15
+ export * from "./task-fold.js";
15
16
  export * from "./face2-adapter.js";
16
17
  export * from "./runtime.js";
17
18
  export * from "./yms-profile.js";
@@ -0,0 +1,56 @@
1
+ import type { TaskStatusDelta } from './contract.ts';
2
+ /**
3
+ * 되읽을 저널 한 줄 — 필요한 것만.
4
+ *
5
+ * `data`(정본 봉투) 와 `payload`(호스트가 감싸 저장한 형태) 를 모두 받는다. 저장 계층마다 감싸는 방식이
6
+ * 달라 소비처가 각자 벗기고 있었고, 그 벗기는 규칙이 또 갈라졌다.
7
+ */
8
+ export interface TaskDeltaRow {
9
+ /** ISO 시각. 파싱 불가하면 그 줄은 버린다(조용히 0 으로 세지 않는다). */
10
+ eventTime?: string;
11
+ data?: Partial<TaskStatusDelta>;
12
+ payload?: {
13
+ data?: Partial<TaskStatusDelta>;
14
+ } | Partial<TaskStatusDelta>;
15
+ }
16
+ /** 작업에 붙어 있는 축의 값 — 어느 전이에서 왔든 모은다. */
17
+ export interface TaskFacets {
18
+ /** 수행 자원(무버). 설계상 자원을 쓰지 않는 공정(체류)에서는 없는 것이 정상이다. */
19
+ resource?: string;
20
+ /** 작업 종류(커널 어휘 — 사람이 읽는 라벨은 표현 계층의 몫). */
21
+ kind?: string;
22
+ /** 소속 오더. **없으면 없는 것이다** — 다른 식별자로 대체하지 않는다. */
23
+ order?: string;
24
+ /** 가장 최근 도착 지점. 진행에 따라 바뀌므로 마지막 것이 사실이다. */
25
+ node?: string;
26
+ }
27
+ /** 작업 하나의 이정표 — 소비처가 이것으로 구간·지표를 만든다. */
28
+ export interface TaskRecord {
29
+ taskId: string;
30
+ /** 생성 시각. 같은 전이가 여러 번 오면 **마지막** 것(등록 정보 정정이 뒤에 온다). */
31
+ createdMs?: number;
32
+ /** 착수 시각. **처음** 것 — 재시도로 다시 착수 전이가 와도 실제 시작은 처음이다. */
33
+ startedMs?: number;
34
+ /** 완료 시각. **나중** 것 — 완료는 종결 상태라 작업당 하나이고 재전송이면 나중 것이 사실이다. */
35
+ completedMs?: number;
36
+ facets: TaskFacets;
37
+ }
38
+ export interface TaskFoldResult {
39
+ /** 작업별 기록(입력에서 처음 등장한 순서). */
40
+ records: TaskRecord[];
41
+ /** 읽어들인 줄 수(시각을 파싱한 것만). 저널이 비었는지 구별하는 값. */
42
+ rowsRead: number;
43
+ /** 본 시각 중 가장 늦은 것(ms). 없으면 0 — 라이브 엣지·창 기준의 재료. */
44
+ latestMs: number;
45
+ }
46
+ /**
47
+ * `task.status` 델타를 작업 단위 기록으로 접는다.
48
+ *
49
+ * 입력은 **시간순일 필요가 없다**(revision 순·역순 모두 허용) — 이정표는 min/max 로 고르고 축의 값은
50
+ * 처음 본 것을 남기므로 순서에 의존하지 않는다. 단 `createdMs` 는 "마지막 생성 전이" 이므로 같은 작업의
51
+ * 생성이 두 번 오면 **입력 순서상 나중** 것이 남는다.
52
+ *
53
+ * 자원이 실리지 않은 전이도 읽는다 — 생성 전이에만 종류·오더가 실리는 구현이 있어, 그 줄을 건너뛰면
54
+ * 축이 통째로 빈다. "자원이 없어 그릴 수 없다" 는 판단은 소비처가 기록을 보고 한다.
55
+ */
56
+ export declare function foldTaskRecords(rows: TaskDeltaRow[]): TaskFoldResult;
@@ -0,0 +1,60 @@
1
+ function ms(value) {
2
+ if (!value)
3
+ return null;
4
+ const t = Date.parse(value);
5
+ return Number.isFinite(t) ? t : null;
6
+ }
7
+ /** 저장 형태 차이를 여기서 한 번만 벗긴다(소비처가 각자 벗기면 그 규칙이 갈라진다). */
8
+ function deltaOf(row) {
9
+ const p = row.payload;
10
+ return (row.data ?? p?.data ?? p ?? {});
11
+ }
12
+ /**
13
+ * `task.status` 델타를 작업 단위 기록으로 접는다.
14
+ *
15
+ * 입력은 **시간순일 필요가 없다**(revision 순·역순 모두 허용) — 이정표는 min/max 로 고르고 축의 값은
16
+ * 처음 본 것을 남기므로 순서에 의존하지 않는다. 단 `createdMs` 는 "마지막 생성 전이" 이므로 같은 작업의
17
+ * 생성이 두 번 오면 **입력 순서상 나중** 것이 남는다.
18
+ *
19
+ * 자원이 실리지 않은 전이도 읽는다 — 생성 전이에만 종류·오더가 실리는 구현이 있어, 그 줄을 건너뛰면
20
+ * 축이 통째로 빈다. "자원이 없어 그릴 수 없다" 는 판단은 소비처가 기록을 보고 한다.
21
+ */
22
+ export function foldTaskRecords(rows) {
23
+ const byTask = new Map();
24
+ let rowsRead = 0;
25
+ let latestMs = 0;
26
+ for (const row of rows) {
27
+ const at = ms(row.eventTime);
28
+ if (at === null)
29
+ continue;
30
+ const d = deltaOf(row);
31
+ const id = d.taskId;
32
+ if (!id)
33
+ continue;
34
+ rowsRead++;
35
+ latestMs = Math.max(latestMs, at);
36
+ let rec = byTask.get(id);
37
+ if (!rec) {
38
+ rec = { taskId: id, facets: {} };
39
+ byTask.set(id, rec);
40
+ }
41
+ /* 축의 값 — 처음 본 것을 남긴다(작업의 종류·오더·자원은 바뀌지 않는다). 노드는 예외로 마지막 것. */
42
+ const f = rec.facets;
43
+ if (f.resource === undefined && d.resourceRef)
44
+ f.resource = d.resourceRef;
45
+ if (f.kind === undefined && d.kind)
46
+ f.kind = d.kind;
47
+ if (f.order === undefined && d.orderId)
48
+ f.order = d.orderId;
49
+ const node = d.toNode ?? d.fromNode;
50
+ if (node)
51
+ f.node = node;
52
+ if (d.status === 'created')
53
+ rec.createdMs = at;
54
+ else if (d.status === 'in-progress')
55
+ rec.startedMs = rec.startedMs != null ? Math.min(rec.startedMs, at) : at;
56
+ else if (d.status === 'completed')
57
+ rec.completedMs = rec.completedMs != null ? Math.max(rec.completedMs, at) : at;
58
+ }
59
+ return { records: [...byTask.values()], rowsRead, latestMs };
60
+ }
@@ -61,6 +61,7 @@ __export(index_exports, {
61
61
  deriveAttentions: () => deriveAttentions,
62
62
  fefoPolicy: () => fefoPolicy,
63
63
  firstFitPolicy: () => firstFitPolicy,
64
+ foldTaskRecords: () => foldTaskRecords,
64
65
  gdtiUri: () => gdtiUri,
65
66
  graiUri: () => graiUri,
66
67
  ingest: () => ingest,
@@ -679,7 +680,47 @@ var fefoPolicy = {
679
680
  };
680
681
 
681
682
  // src/duration-estimator.ts
682
- var constantDuration = (ms) => ({ estimate: () => ms });
683
+ var constantDuration = (ms2) => ({ estimate: () => ms2 });
684
+
685
+ // src/task-fold.ts
686
+ function ms(value) {
687
+ if (!value) return null;
688
+ const t = Date.parse(value);
689
+ return Number.isFinite(t) ? t : null;
690
+ }
691
+ function deltaOf(row) {
692
+ const p = row.payload;
693
+ return row.data ?? p?.data ?? p ?? {};
694
+ }
695
+ function foldTaskRecords(rows) {
696
+ const byTask = /* @__PURE__ */ new Map();
697
+ let rowsRead = 0;
698
+ let latestMs = 0;
699
+ for (const row of rows) {
700
+ const at = ms(row.eventTime);
701
+ if (at === null) continue;
702
+ const d = deltaOf(row);
703
+ const id = d.taskId;
704
+ if (!id) continue;
705
+ rowsRead++;
706
+ latestMs = Math.max(latestMs, at);
707
+ let rec = byTask.get(id);
708
+ if (!rec) {
709
+ rec = { taskId: id, facets: {} };
710
+ byTask.set(id, rec);
711
+ }
712
+ const f = rec.facets;
713
+ if (f.resource === void 0 && d.resourceRef) f.resource = d.resourceRef;
714
+ if (f.kind === void 0 && d.kind) f.kind = d.kind;
715
+ if (f.order === void 0 && d.orderId) f.order = d.orderId;
716
+ const node = d.toNode ?? d.fromNode;
717
+ if (node) f.node = node;
718
+ if (d.status === "created") rec.createdMs = at;
719
+ else if (d.status === "in-progress") rec.startedMs = rec.startedMs != null ? Math.min(rec.startedMs, at) : at;
720
+ else if (d.status === "completed") rec.completedMs = rec.completedMs != null ? Math.max(rec.completedMs, at) : at;
721
+ }
722
+ return { records: [...byTask.values()], rowsRead, latestMs };
723
+ }
683
724
 
684
725
  // src/face2-adapter.ts
685
726
  function get(obj, path) {
@@ -1990,6 +2031,7 @@ var MesKernel = class extends FlowEngine {
1990
2031
  deriveAttentions,
1991
2032
  fefoPolicy,
1992
2033
  firstFitPolicy,
2034
+ foldTaskRecords,
1993
2035
  gdtiUri,
1994
2036
  graiUri,
1995
2037
  ingest,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@operato/twin-kernel",
3
- "version": "0.0.6",
3
+ "version": "0.1.0",
4
4
  "type": "module",
5
5
  "description": "Twin Domain Kernel — framework-agnostic, zero-dep (domain + sim + 3-channel contract). WMS/YMS/MES, EPCIS 2.0 · ISA-95.",
6
6
  "publishConfig": {