/** 로컬 선언이 겨눌 수 있는 대상 — 자원 둘과 **공정 종류**(자원이 아니라 명세다). */ export type DeclarationTarget = 'location' | 'equipment' | 'operation'; /** 한 대상(자리·설비·공정)에 얹을 로컬 선언. */ export interface LocalDeclaration { target: DeclarationTarget; /** 대상 id — 모델에 있는 그 이름. */ id: string; properties: { id: string; value: string | number; /** * 단위(UN/CEFACT 코드) — **어떤 값은 단위 없이는 뜻이 없다.** * * 속도가 그렇다: 단위가 없으면 이동 시간 추정기가 그 설비를 **거절한다**(그러면 공정 시간이 알리지 않고 * 사라진다). 명세가 단위를 요구하면(`uomChoices`) 받고, 그 목록에 없는 코드는 거절한다 — 읽는 쪽이 * 모르는 단위를 받아 두면 화면은 넣었다고 말하고 계산은 그 값을 버린다. */ uom?: string; /** * 이 선언이 **덮은 원천 값** — 철회하면 이것으로 되돌린다. * * ── 왜 기억해야 하나 (2026-08-18) ──────────────────────────────────────── * 선언은 모델에 얹혀 **저장된 모델에 구워진다.** 그래서 겹에서 줄만 지워도 원래 값이 돌아오지 * 않는다 — 실제로 되돌릴 방법이 없어서, 잘못 넣은 값을 「원래 값을 다시 선언하는」 우회로만 * 고칠 수 있었다(사용자가 원래 값을 알고 있어야 한다는 뜻이다). * * · 문자열 — 원천이 그 자리에 갖고 있던 값이다. * · `null` — 원천에는 그 속성이 **없었다**(철회하면 속성을 지운다). * · 없음 — **우리가 모른다**(이 필드가 생기기 전에 선언된 것). 지어내지 않고 그렇게 말한다. * * 사용자가 넣는 값이 아니라 얹을 때 우리가 채우는 값이다(매 원천 재읽기마다 새로 채워져 낡지 않는다). */ replaced?: string | null; }[]; } export interface RejectedDeclaration { target: string; id: string; propertyId: string; /** * 왜 받지 않았나 — 언어중립 코드(화면이 옮긴다). * · `unknown-property` — 커널이 읽지 않는 속성 id 다 * · `out-of-range` — 명세가 정한 범위 밖이다(단위를 잘못 읽은 값이 대개 여기 걸린다) * · `not-a-number` — 수를 요구하는 속성에 수가 아닌 값이 왔다 * · `empty-value` — 값이 비어 있다(지우려면 그 뜻을 따로 말해야 한다) * · `no-property-vocabulary` — 이 종류의 트윈에는 선언 어휘가 아직 없다 * · `no-target-id` — 어느 대상에 얹을지 말하지 않았다(선언의 모양이 틀렸다) * · `no-property-id` — 어느 속성인지 말하지 않았다(선언의 모양이 틀렸다) * * 뒤의 둘이 있는 이유: 예전에는 대상 id·속성 id 가 비면 **아무 말 없이 건너뛰었다.** 그래서 필드 이름을 * 틀린 호출이 「받아준 선언이 없다」는 답만 받고 `rejected` 는 비어 있었다 — 무엇이 틀렸는지 알 길이 * 없는 침묵이다(실제로 그 침묵에 걸렸다). */ reason: 'unknown-property' | 'out-of-range' | 'not-a-number' | 'empty-value' | 'no-property-vocabulary' | 'no-target-id' | 'no-property-id' /** 단위를 요구하는 속성인데 오지 않았다(그 값은 읽는 쪽이 버린다). */ | 'uom-required' /** 읽는 쪽이 모르는 단위 코드다(명세가 받는 목록에 없다). */ | 'unknown-uom' /** * 이 속성은 그 종류의 대상에 얹히지 않는다 — 읽는 코드가 다른 축을 본다. * * 예전에는 지게차에 계약전력을 넣을 수 있었다(문은 받고 화면은 「넣었습니다」라고 말하는데 읽는 * 쪽은 자리만 본다). 어느 축의 속성인지는 효과 등록부가 이미 알고 있으므로 그것으로 거절한다. */ | 'wrong-target' /** * 그 이름의 공정을 이 트윈에서 확인할 수 없다 — 커널이 돌린 적도 없고 명세 행에도 없다. * * 공정 선언은 자리·설비와 달리 **모델에 행이 없어도 성립한다**(창고·야드에는 행이 아예 없다). * 그래서 오타를 잡아 줄 것이 이름 대조뿐이다 — 그마저 없으면 값은 알리지 않고 아무 일도 하지 않는다. */ | 'unknown-operation'; /** 무엇을 기대했나 — 사람이 고칠 수 있게(명세의 문장). */ expected?: string; } /** 이 종류의 트윈에 선언 어휘가 있나 — 없으면 검사할 근거가 없으므로 받지 않는다. */ export declare function hasPropertyVocabulary(kind: string | undefined): boolean; /** 이 종류에 선언할 수 있는 것이 하나라도 있나 — 공용 어휘가 있으므로 대개 참이다. */ export declare function hasAnyDeclarable(kind: string | undefined): boolean; /** * 이 종류의 트윈에 **선언할 수 있는 속성 목록** — 화면이 도메인의 모양을 알지 않게. * * 화면이 「에너지 트윈이면 계약전력·요금 단가…」를 알고 있으면, 도메인이 늘 때마다 화면을 고쳐야 하고 * 단위·범위가 두 곳에 적힌다(ADR-0031: 카탈로그가 선언하고 화면은 그린다). 그래서 커널의 명세를 그대로 * 낸다 — 화면은 받은 것을 렌더할 뿐이다. * * 어휘가 없는 종류는 **빈 목록**이다(「아직 없다」와 「아무 값이나 받는다」는 다르다). */ export interface DeclarablePropertySpec { id: string; /** 사람이 읽는 단위(명세가 정한 것) — 값에 붙여 보인다. */ uom?: string; dataType: string; range?: [number, number]; note: string; /** * 값과 **함께 단위를 골라야 하는** 속성 — 그 목록(UN/CEFACT 코드). * * 어떤 값은 단위 없이는 뜻이 없다: 속도는 단위가 없으면 추정기가 **거절한다**(「speed has no unit of * measure」). 그래서 명세가 「단위를 받아야 한다」와 「무엇을 받나」를 함께 말한다 — 화면이 목록을 * 지어내면 화면이 받아 준 단위를 읽는 쪽이 모르는 일이 생긴다. */ uomChoices?: string[]; /** * 어느 종류의 대상에 얹히나(효과 등록부의 축) — 없으면 **우리가 제한을 모른다**(받아 준다). * * 화면은 이것으로 속성 목록을 대상에 맞게 좁힌다: 공정 시간을 설비에 넣으려는 선택지를 아예 보이지 * 않게 하는 것이, 넣게 하고 거절하는 것보다 낫다. */ targets?: string[]; } export declare function isoDurationOf(value: string | number, uom: string): string | undefined; /** * 이 종류의 트윈에 **선언할 수 있는 속성 목록** — 화면이 도메인의 모양을 알지 않게. * * 종류와 무관한 것(효과 등록부에서 온다) + 그 종류의 어휘(지금은 EMS 뿐). 어휘가 없는 종류도 **빈손이 * 아니다**: 속도는 어느 트윈에서나 읽히므로 창고·야드·생산 트윈에서도 넣을 수 있어야 한다. */ export declare function declarablePropertySpecs(kind: string | undefined): DeclarablePropertySpec[]; /** * 로컬 선언을 검사한다 — **받은 것과 받지 않은 것을 함께** 돌려준다. * * 알리지 않고 걸러내지 않는 이유: 사용자는 값을 넣었다고 믿는다. 무엇이 왜 빠졌는지 말해야 고칠 수 있다. */ export declare function validateLocalDeclarations(kind: string | undefined, declarations: readonly LocalDeclaration[] | undefined | null, opts?: { /** * 이 트윈에서 확인된 공정 이름들 — 있으면 그 밖의 이름을 거절한다(오타를 잡는 유일한 수단이다). * * **모르면 넘기지 않고 모른다고 둔다**: 아직 돌지 않은 트윈은 어떤 공정을 돌릴지 알 수 없으므로 * 목록을 주지 않고(=undefined) 받아 준다. 부르는 쪽이 「확인하지 못했다」는 사실을 답에 싣는다. */ knownOperations?: readonly string[]; }): { accepted: LocalDeclaration[]; rejected: RejectedDeclaration[]; }; /** * 겹을 합친다 — 같은 대상·같은 속성이면 **새 값이 이긴다**(현장이 값을 고치는 일은 흔하다). * * 대상이나 속성을 지우는 일은 여기서 하지 않는다: 지움은 「값을 모르는 상태로 되돌린다」는 뜻이고, * 그것은 값을 넣는 것과 다른 결정이므로 다른 문으로 말해야 한다. */ export declare function mergeLocalDeclarations(current: readonly LocalDeclaration[] | undefined | null, incoming: readonly LocalDeclaration[]): LocalDeclaration[]; /** * 모델 위에 로컬 선언을 얹는다 — **모델을 바꾸지 않고 새 모델을 돌려준다.** * * 원천이 같은 속성을 이미 말했으면 **로컬 값이 이긴다**: 계약전력을 게이트웨이가 말하더라도, 우리가 * 고쳐 넣었다는 것은 「원천의 값이 낡았다」는 뜻이다. 다만 그 사실이 보여야 하므로 `source: 'local'` 를 * 값에 붙인다 — 화면·감사가 「누가 말한 값인가」를 되짚을 수 있다. * * 모델에 없는 대상에 얹으려 하면 **알리지 않고 버리지 않고** 그 사실을 돌려준다(구조가 바뀌어 그 설비가 * 사라졌을 수 있고, 그때 사용자의 선언은 갈 곳이 없다). */ export declare function applyLocalDeclarations; localParams?: Record>; }>(model: T, declarations: readonly LocalDeclaration[] | undefined | null): { model: T; missingTargets: { target: string; id: string; }[]; /** * 읽을 수 있는 값으로 바꿀 수 없었던 선언 — **알리지 않고 버리지 않기 위한 자리.** * * 지금은 공정 시간에서만 난다(단위가 없거나 수가 아니면 ISO 8601 기간을 만들 수 없다). 문에서 이미 * 검사하므로 정상 경로에서는 비어 있고, 옛 겹이나 손으로 넣은 데이터에서 드러난다. */ unreadable: { target: string; id: string; propertyId: string; }[]; layer: LocalDeclaration[]; }; /** 철회 요청 하나 — 어느 대상의 어느 속성을 원천으로 되돌리나. */ export interface DeclarationRevocation { target: DeclarationTarget; id: string; propertyId: string; } /** 철회된 항목 — 되돌릴 값을 함께 든다(모르면 `replaced` 가 없다). */ export interface RevokedDeclaration extends DeclarationRevocation { replaced?: string | null; } /** * 겹에서 선언을 **뺀다** — 값을 넣는 것과 다른 결정이므로 다른 문이다. * * 없는 것을 뺐다고 말하지 않는다: 겹에 없던 대상·속성은 `notFound` 로 돌려준다(알리지 않고 성공하면 * 사용자는 지웠다고 믿고 값은 그대로 남는다 — 가장 비싼 종류의 침묵이다). * * 속성이 하나도 남지 않은 대상은 겹에서 사라진다(빈 껍데기를 남기지 않는다). */ export declare function revokeLocalDeclarations(current: readonly LocalDeclaration[] | undefined | null, revocations: readonly DeclarationRevocation[] | undefined | null): { layer: LocalDeclaration[]; revoked: RevokedDeclaration[]; notFound: DeclarationRevocation[]; }; /** * 철회한 속성을 **모델에서 원천 값으로 되돌린다** — 저장된 모델에 값이 구워져 있기 때문이다. * * · 기억한 값이 있으면 그 값으로 되돌리고 `source: 'local'` 표식을 뗀다(다시 원천이 말한 값이다). * · `null` 이면 원천에 없던 속성이므로 **지운다**. * · 기억이 없으면(옛 선언) **손대지 않고** `unknownSource` 로 말한다 — 원래 값을 지어내는 것은 * 잘못 넣은 값을 그대로 두는 것보다 나쁘다(다음 원천 재읽기가 그 자리를 바로잡는다). */ export declare function restoreRevoked; localParams?: Record>; }>(model: T, revoked: readonly RevokedDeclaration[]): { model: T; restored: RevokedDeclaration[]; unknownSource: DeclarationRevocation[]; missingTargets: { target: string; id: string; }[]; }; /** 선언이 어느 범위의 것인가 — 좁은 것이 이긴다(`space` 먼저, `instance` 나중). */ export type DeclarationScope = 'space' | 'instance'; /** * 두 겹을 **순서대로** 얹는다 — 현장 먼저, 트윈이 나중. * * ── 왜 순서가 규율인가 (2026-08-18) ───────────────────────────────────────── * 요금 단가는 현장의 계약이므로 그 현장의 트윈 전부가 같은 값을 읽어야 한다. 그런데 한 트윈만 다른 * 값으로 실험하는 일이 있다(인상된 단가로 what-if). 그때 현장 값을 고치게 하면 **나머지 트윈이 함께 * 흔들린다.** 그래서 더 좁은 선언이 이긴다 — 넓은 값은 그대로 두고 좁은 자리에서만 달라진다. * * 기억(`replaced`)은 각 겹이 **자기가 덮은 값**을 적는다: 트윈 겹의 기억은 「현장 값을 덮었다」가 되고, * 철회하면 현장 값으로 돌아간다(원천까지 가지 않는다). 그것이 사람이 기대하는 되돌림이다. */ export declare function applyDeclarationLayers; localParams?: Record>; }>(model: T, spaceLayer: readonly LocalDeclaration[] | undefined | null, instanceLayer: readonly LocalDeclaration[] | undefined | null): { model: T; /** 현장 겹을 얹은 결과 — 저장해야 기억이 낡지 않는다. */ spaceLayerApplied: LocalDeclaration[]; instanceLayerApplied: LocalDeclaration[]; /** 갈 곳이 없던 대상 — 어느 범위에서 났는지 함께 낸다(현장 겹은 그 트윈에 없을 수 있다). */ missingTargets: { scope: DeclarationScope; target: string; id: string; }[]; /** 읽을 수 있는 값으로 바꿀 수 없던 선언 — 범위와 함께(알리지 않고 버리지 않는다). */ unreadable: { scope: DeclarationScope; target: string; id: string; propertyId: string; }[]; };