@things-factory/headless-twin 10.0.5 → 10.0.7
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/README.md +37 -24
- package/dist-server/engine/entity-delta.d.ts +13 -1
- package/dist-server/engine/entity-delta.js +130 -16
- package/dist-server/engine/entity-delta.js.map +1 -1
- package/dist-server/engine/index.d.ts +4 -0
- package/dist-server/engine/index.js +4 -0
- package/dist-server/engine/index.js.map +1 -1
- package/dist-server/engine/kpi-fold.d.ts +20 -3
- package/dist-server/engine/kpi-fold.js +45 -53
- package/dist-server/engine/kpi-fold.js.map +1 -1
- package/dist-server/engine/kpi-query.d.ts +16 -0
- package/dist-server/engine/kpi-query.js +129 -19
- package/dist-server/engine/kpi-query.js.map +1 -1
- package/dist-server/engine/kpi-target.d.ts +141 -0
- package/dist-server/engine/kpi-target.js +137 -0
- package/dist-server/engine/kpi-target.js.map +1 -0
- package/dist-server/engine/live-attentions.d.ts +4 -2
- package/dist-server/engine/live-attentions.js +5 -1
- package/dist-server/engine/live-attentions.js.map +1 -1
- package/dist-server/engine/measured-estimator.d.ts +50 -0
- package/dist-server/engine/measured-estimator.js +78 -0
- package/dist-server/engine/measured-estimator.js.map +1 -0
- package/dist-server/engine/model-basis.d.ts +23 -0
- package/dist-server/engine/model-basis.js +100 -0
- package/dist-server/engine/model-basis.js.map +1 -0
- package/dist-server/engine/oee-accumulator.d.ts +2 -2
- package/dist-server/engine/oee-accumulator.js +4 -4
- package/dist-server/engine/oee-accumulator.js.map +1 -1
- package/dist-server/engine/spec-coverage.d.ts +49 -0
- package/dist-server/engine/spec-coverage.js +60 -0
- package/dist-server/engine/spec-coverage.js.map +1 -0
- package/dist-server/engine/structure-diff.d.ts +25 -0
- package/dist-server/engine/structure-diff.js +63 -0
- package/dist-server/engine/structure-diff.js.map +1 -0
- package/dist-server/engine/travel-estimator.d.ts +57 -0
- package/dist-server/engine/travel-estimator.js +120 -0
- package/dist-server/engine/travel-estimator.js.map +1 -0
- package/dist-server/engine/twin-engine.d.ts +181 -18
- package/dist-server/engine/twin-engine.js +545 -96
- package/dist-server/engine/twin-engine.js.map +1 -1
- package/dist-server/engine/warm-start.d.ts +39 -0
- package/dist-server/engine/warm-start.js +37 -0
- package/dist-server/engine/warm-start.js.map +1 -0
- package/dist-server/index.js +8 -0
- package/dist-server/index.js.map +1 -1
- package/dist-server/service/index.d.ts +4 -2
- package/dist-server/service/index.js +21 -14
- package/dist-server/service/index.js.map +1 -1
- package/dist-server/service/reference/reference-live.js +2 -1
- package/dist-server/service/reference/reference-live.js.map +1 -1
- package/dist-server/service/reference/reference-master.d.ts +307 -1
- package/dist-server/service/reference/reference-master.js +96 -8
- package/dist-server/service/reference/reference-master.js.map +1 -1
- package/dist-server/service/reference/reference-resolver.js +3 -3
- package/dist-server/service/reference/reference-resolver.js.map +1 -1
- package/dist-server/service/reference/template-registry.d.ts +1 -1
- package/dist-server/service/reference/template-registry.js.map +1 -1
- package/dist-server/service/twin-attention/twin-attention-query.js +1 -1
- package/dist-server/service/twin-attention/twin-attention-query.js.map +1 -1
- package/dist-server/service/twin-control/twin-control-mutation.js +1 -1
- package/dist-server/service/twin-control/twin-control-mutation.js.map +1 -1
- package/dist-server/service/twin-event/backfill-keys.d.ts +11 -0
- package/dist-server/service/twin-event/backfill-keys.js +63 -0
- package/dist-server/service/twin-event/backfill-keys.js.map +1 -0
- package/dist-server/service/twin-event/twin-event-keys.d.ts +35 -0
- package/dist-server/service/twin-event/twin-event-keys.js +95 -0
- package/dist-server/service/twin-event/twin-event-keys.js.map +1 -0
- package/dist-server/service/twin-event/twin-event-type.d.ts +6 -0
- package/dist-server/service/twin-event/twin-event-type.js +32 -0
- package/dist-server/service/twin-event/twin-event-type.js.map +1 -0
- package/dist-server/service/twin-event/twin-event.d.ts +16 -0
- package/dist-server/service/twin-event/twin-event.js +50 -0
- package/dist-server/service/twin-event/twin-event.js.map +1 -1
- package/dist-server/service/twin-forecast/twin-forecast-query.js +50 -7
- package/dist-server/service/twin-forecast/twin-forecast-query.js.map +1 -1
- package/dist-server/service/twin-instance/twin-instance.js +1 -1
- package/dist-server/service/twin-instance/twin-instance.js.map +1 -1
- package/dist-server/service/twin-journal/twin-journal-query.d.ts +28 -0
- package/dist-server/service/twin-journal/twin-journal-query.js +127 -2
- package/dist-server/service/twin-journal/twin-journal-query.js.map +1 -1
- package/dist-server/service/twin-lifecycle/twin-lifecycle-mutation.d.ts +1 -1
- package/dist-server/service/twin-lifecycle/twin-lifecycle-mutation.js +4 -3
- package/dist-server/service/twin-lifecycle/twin-lifecycle-mutation.js.map +1 -1
- package/dist-server/service/twin-space/twin-space-area.js +1 -1
- package/dist-server/service/twin-space/twin-space-area.js.map +1 -1
- package/dist-server/service/twin-space/twin-space-resolver.js +3 -3
- package/dist-server/service/twin-space/twin-space-resolver.js.map +1 -1
- package/dist-server/service/twin-space/twin-space.d.ts +11 -0
- package/dist-server/service/twin-space/twin-space.js +5 -0
- package/dist-server/service/twin-space/twin-space.js.map +1 -1
- package/dist-server/service/twin-structure/index.d.ts +2 -0
- package/dist-server/service/twin-structure/index.js +6 -0
- package/dist-server/service/twin-structure/index.js.map +1 -0
- package/dist-server/service/twin-structure/twin-structure.d.ts +28 -0
- package/dist-server/service/twin-structure/twin-structure.js +97 -0
- package/dist-server/service/twin-structure/twin-structure.js.map +1 -0
- package/dist-server/service/twin-target/index.d.ts +4 -0
- package/dist-server/service/twin-target/index.js +8 -0
- package/dist-server/service/twin-target/index.js.map +1 -0
- package/dist-server/service/twin-target/twin-target-resolver.d.ts +7 -0
- package/dist-server/service/twin-target/twin-target-resolver.js +143 -0
- package/dist-server/service/twin-target/twin-target-resolver.js.map +1 -0
- package/dist-server/service/twin-target/twin-target.d.ts +25 -0
- package/dist-server/service/twin-target/twin-target.js +112 -0
- package/dist-server/service/twin-target/twin-target.js.map +1 -0
- package/dist-server/tsconfig.tsbuildinfo +1 -1
- package/package.json +6 -6
- package/server/engine/entity-delta.ts +135 -15
- package/server/engine/index.ts +4 -0
- package/server/engine/kpi-fold.ts +62 -59
- package/server/engine/kpi-query.ts +140 -19
- package/server/engine/kpi-target.ts +226 -0
- package/server/engine/live-attentions.ts +12 -2
- package/server/engine/measured-estimator.ts +91 -0
- package/server/engine/model-basis.ts +94 -0
- package/server/engine/oee-accumulator.ts +5 -5
- package/server/engine/spec-coverage.ts +85 -0
- package/server/engine/structure-diff.ts +88 -0
- package/server/engine/travel-estimator.ts +133 -0
- package/server/engine/twin-engine.ts +587 -106
- package/server/engine/warm-start.ts +53 -0
- package/server/index.ts +9 -0
- package/server/service/index.ts +7 -0
- package/server/service/reference/reference-live.ts +2 -1
- package/server/service/reference/reference-master.ts +383 -10
- package/server/service/reference/reference-resolver.ts +3 -3
- package/server/service/reference/template-registry.ts +1 -1
- package/server/service/twin-attention/twin-attention-query.ts +1 -1
- package/server/service/twin-control/twin-control-mutation.ts +1 -1
- package/server/service/twin-event/backfill-keys.ts +72 -0
- package/server/service/twin-event/twin-event-keys.ts +102 -0
- package/server/service/twin-event/twin-event-type.ts +27 -0
- package/server/service/twin-event/twin-event.ts +62 -0
- package/server/service/twin-forecast/twin-forecast-query.ts +56 -8
- package/server/service/twin-instance/twin-instance.ts +1 -1
- package/server/service/twin-journal/twin-journal-query.ts +129 -5
- package/server/service/twin-lifecycle/twin-lifecycle-mutation.ts +4 -3
- package/server/service/twin-space/twin-space-area.ts +1 -1
- package/server/service/twin-space/twin-space-resolver.ts +3 -3
- package/server/service/twin-space/twin-space.ts +14 -0
- package/server/service/twin-structure/index.ts +3 -0
- package/server/service/twin-structure/twin-structure.ts +93 -0
- package/server/service/twin-target/index.ts +5 -0
- package/server/service/twin-target/twin-target-resolver.ts +124 -0
- package/server/service/twin-target/twin-target.ts +97 -0
- package/test/capability-mapping.test.ts +5 -5
- package/test/duration-estimators.test.ts +144 -0
- package/test/entity-delta.test.ts +150 -19
- package/test/ingest-bench.test.ts +9 -9
- package/test/kpi-fold.test.ts +254 -3
- package/test/live-mirror-parity.test.ts +53 -19
- package/test/master-to-twin.test.ts +87 -13
- package/test/model-basis.test.ts +86 -0
- package/test/oee-accumulator.test.ts +9 -9
- package/test/scale-twin-bench.test.ts +22 -22
- package/test/spec-coverage.test.ts +113 -0
- package/test/streamline-e2e.test.ts +10 -10
- package/test/structure-revision-db.test.ts +310 -0
- package/test/twin-event-keys.test.ts +108 -0
- package/test/vocabulary-guard.test.ts +43 -0
- package/test/warm-start.test.ts +78 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"twin-target-resolver.js","sourceRoot":"","sources":["../../../server/service/twin-target/twin-target-resolver.ts"],"names":[],"mappings":";;;;AAAA,+CAAoF;AAEpF,iDAAqD;AAErD,qDAA6C;AAC7C,8DAAsE;AAEtE;;;;;GAKG;AAEI,IAAM,kBAAkB,GAAxB,MAAM,kBAAkB;IAMvB,AAAN,KAAK,CAAC,iBAAiB;QACrB,OAAO,MAAM,CAAC,IAAI,CAAC,2BAAW,CAAC,CAAA;IACjC,CAAC;IAOK,AAAN,KAAK,CAAC,WAAW,CACR,OAAwB,EACK,OAAgB,EACb,UAAmB;QAE1D,MAAM,QAAQ,GAAG,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,CAAA;QACxC,MAAM,IAAI,GAAG,MAAM,IAAA,qBAAa,EAAC,2BAAU,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,QAAQ,EAAE,EAAE,EAAE,CAAC,CAAA;QAC1F,OAAO,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,OAAO,IAAI,CAAC,CAAC,OAAO,KAAK,OAAO,CAAC,IAAI,CAAC,UAAU,IAAI,CAAC,CAAC,UAAU,KAAK,UAAU,CAAC,CAAC,CAAA;IAC5G,CAAC;IAOK,AAAN,KAAK,CAAC,cAAc,CACH,MAAc,EACC,MAAc,EACrC,OAAwB,EACK,OAAgB,EACb,UAAmB,EACzB,IAAa,EACV,OAAgB;QAEpD,IAAI,CAAC,IAAA,4BAAY,EAAC,MAAM,CAAC;YAAE,MAAM,IAAI,KAAK,CAAC,wBAAwB,MAAM,2BAA2B,CAAC,CAAA;QACrG,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC;YAAE,MAAM,IAAI,KAAK,CAAC,gCAAgC,CAAC,CAAA;QAC/E,IAAI,CAAC,OAAO,IAAI,CAAC,UAAU;YAAE,MAAM,IAAI,KAAK,CAAC,mDAAmD,CAAC,CAAA;QACjG;iDACyC;QACzC,IAAI,IAAI,IAAI,CAAC,OAAO;YAAE,MAAM,IAAI,KAAK,CAAC,wCAAwC,CAAC,CAAA;QAC/E,IAAI,CAAC,IAAI,IAAI,OAAO;YAAE,MAAM,IAAI,KAAK,CAAC,wCAAwC,CAAC,CAAA;QAC/E,MAAM,QAAQ,GAAG,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,CAAA;QACxC,MAAM,IAAI,GAAG,IAAA,qBAAa,EAAC,2BAAU,CAAC,CAAA;QACtC,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC;YAClC,KAAK,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,QAAQ,EAAE,EAAE,OAAO,EAAE,OAAO,IAAI,IAAI,EAAE,UAAU,EAAE,UAAU,IAAI,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,IAAI,IAAI,EAAE,OAAO,EAAE,OAAO,IAAI,IAAI,EAAS;SAC3J,CAAC,CAAA;QACF,MAAM,IAAI,CAAC,IAAI,CACb,IAAI,CAAC,MAAM,CAAC;YACV,GAAG,CAAC,QAAQ,IAAI,EAAE,CAAC;YACnB,MAAM,EAAE,EAAE,EAAE,EAAE,QAAQ,EAAS;YAC/B,OAAO,EAAE,OAAO,IAAI,IAAW;YAC/B,UAAU,EAAE,UAAU,IAAI,IAAW;YACrC,IAAI,EAAE,IAAI,IAAI,IAAW;YACzB,OAAO,EAAE,OAAO,IAAI,IAAW;YAC/B,MAAM;YACN,MAAM;YACN,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,OAAO,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,OAAO,CAAC,KAAK,CAAC,IAAI,EAAE,OAAO,EAAE,OAAO,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC;SAC/G,CAAC,CACH,CAAA;QACD,OAAO,IAAI,CAAA;IACb,CAAC;IAIK,AAAN,KAAK,CAAC,gBAAgB,CACL,MAAc,EACtB,OAAwB,EACK,OAAgB,EACb,UAAmB,EACzB,IAAa,EACV,OAAgB;QAEpD,MAAM,QAAQ,GAAG,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,CAAA;QACxC,MAAM,IAAI,GAAG,IAAA,qBAAa,EAAC,2BAAU,CAAC,CAAA;QAEtC;;;;;;;;;;;;;WAaG;QACH,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC;YAC7B,KAAK,EAAE;gBACL,MAAM,EAAE,EAAE,EAAE,EAAE,QAAQ,EAAE;gBACxB,OAAO,EAAE,OAAO,IAAI,IAAI;gBACxB,UAAU,EAAE,UAAU,IAAI,IAAI;gBAC9B,IAAI,EAAE,IAAI,IAAI,IAAI;gBAClB,OAAO,EAAE,OAAO,IAAI,IAAI;gBACxB,MAAM;aACA;SACT,CAAC,CAAA;QACF,6DAA6D;QAC7D,IAAI,CAAC,GAAG;YAAE,OAAO,KAAK,CAAA;QACtB,MAAM,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAA;QACzB,OAAO,IAAI,CAAA;IACb,CAAC;CACF,CAAA;AA7GY,gDAAkB;AAMvB;IALL,IAAA,wBAAS,EAAC,kDAAkD,CAAC;IAC7D,IAAA,oBAAK,EAAC,OAAO,CAAC,EAAE,CAAC,CAAC,MAAM,CAAC,EAAE;QAC1B,WAAW,EACT,kIAAkI;KACrI,CAAC;;;;2DAGD;AAOK;IALL,IAAA,wBAAS,EAAC,kDAAkD,CAAC;IAC7D,IAAA,oBAAK,EAAC,OAAO,CAAC,EAAE,CAAC,CAAC,2BAAU,CAAC,EAAE;QAC9B,WAAW,EACT,+JAA+J;KAClK,CAAC;IAEC,mBAAA,IAAA,kBAAG,GAAE,CAAA;IACL,mBAAA,IAAA,kBAAG,EAAC,SAAS,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAA;IAClC,mBAAA,IAAA,kBAAG,EAAC,YAAY,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAA;;;;qDAKvC;AAOK;IALL,IAAA,wBAAS,EAAC,qDAAqD,CAAC;IAChE,IAAA,uBAAQ,EAAC,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE;QAC5B,WAAW,EACT,qNAAqN;KACxN,CAAC;IAEC,mBAAA,IAAA,kBAAG,EAAC,QAAQ,CAAC,CAAA;IACb,mBAAA,IAAA,kBAAG,EAAC,QAAQ,EAAE,IAAI,CAAC,EAAE,CAAC,oBAAK,CAAC,CAAA;IAC5B,mBAAA,IAAA,kBAAG,GAAE,CAAA;IACL,mBAAA,IAAA,kBAAG,EAAC,SAAS,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAA;IAClC,mBAAA,IAAA,kBAAG,EAAC,YAAY,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAA;IACrC,mBAAA,IAAA,kBAAG,EAAC,MAAM,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAA;IAC/B,mBAAA,IAAA,kBAAG,EAAC,SAAS,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAA;;;;wDA4BpC;AAIK;IAFL,IAAA,wBAAS,EAAC,qDAAqD,CAAC;IAChE,IAAA,uBAAQ,EAAC,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,WAAW,EAAE,0IAA0I,EAAE,CAAC;IAEvL,mBAAA,IAAA,kBAAG,EAAC,QAAQ,CAAC,CAAA;IACb,mBAAA,IAAA,kBAAG,GAAE,CAAA;IACL,mBAAA,IAAA,kBAAG,EAAC,SAAS,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAA;IAClC,mBAAA,IAAA,kBAAG,EAAC,YAAY,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAA;IACrC,mBAAA,IAAA,kBAAG,EAAC,MAAM,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAA;IAC/B,mBAAA,IAAA,kBAAG,EAAC,SAAS,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAA;;;;0DAiCpC;6BA5GU,kBAAkB;IAD9B,IAAA,uBAAQ,GAAE;GACE,kBAAkB,CA6G9B","sourcesContent":["import { Arg, Ctx, Directive, Float, Mutation, Query, Resolver } from 'type-graphql'\n\nimport { getRepository } from '@things-factory/shell'\n\nimport { TwinTarget } from './twin-target.js'\nimport { TWIN_METRIC, isTwinMetric } from '../../engine/kpi-target.js'\n\n/*\n * 운영 목표 조회·편집 — **현장 관리자가 트윈 화면에서** 고친다(사용자 결정 2026-08-02).\n *\n * 경영 KPI 관리메뉴가 아니라 여기인 이유: 목표가 숫자 옆에 있어야 맥락이 살고, 운영 임계는 자주\n * 조정된다. 조직 단위 기간 평가는 `@things-factory/kpi` 의 일이고 이쪽으로 베껴 오지 않는다.\n */\n@Resolver()\nexport class TwinTargetResolver {\n @Directive('@privilege(category: \"twin\", privilege: \"query\")')\n @Query(returns => [String], {\n description:\n 'The metric ids a twin target may refer to. Fixed in one place so a target and the fact it judges can never drift into two names.'\n })\n async twinTargetMetrics(): Promise<string[]> {\n return Object.keys(TWIN_METRIC)\n }\n\n @Directive('@privilege(category: \"twin\", privilege: \"query\")')\n @Query(returns => [TwinTarget], {\n description:\n 'Operational targets in scope — site-wide targets for the space plus targets bound to the given instance. Callers judge with the narrower one when both exist.'\n })\n async twinTargets(\n @Ctx() context: ResolverContext,\n @Arg('spaceId', { nullable: true }) spaceId?: string,\n @Arg('instanceId', { nullable: true }) instanceId?: string\n ): Promise<TwinTarget[]> {\n const domainId = context.state.domain.id\n const rows = await getRepository(TwinTarget).find({ where: { domain: { id: domainId } } })\n return rows.filter(r => (spaceId && r.spaceId === spaceId) || (instanceId && r.instanceId === instanceId))\n }\n\n @Directive('@privilege(category: \"twin\", privilege: \"mutation\")')\n @Mutation(returns => Boolean, {\n description:\n 'Create or update one operational target. The metric must be one of twinTargetMetrics — an unknown name is rejected rather than stored, because a target nobody can evaluate reads as \"no target\" and hides the gap.'\n })\n async saveTwinTarget(\n @Arg('metric') metric: string,\n @Arg('target', type => Float) target: number,\n @Ctx() context: ResolverContext,\n @Arg('spaceId', { nullable: true }) spaceId?: string,\n @Arg('instanceId', { nullable: true }) instanceId?: string,\n @Arg('axis', { nullable: true }) axis?: string,\n @Arg('axisKey', { nullable: true }) axisKey?: string\n ): Promise<boolean> {\n if (!isTwinMetric(metric)) throw new Error(`unknown twin metric \"${metric}\" — see twinTargetMetrics`)\n if (!Number.isFinite(target)) throw new Error('target must be a finite number')\n if (!spaceId && !instanceId) throw new Error('a target must be scoped to a space or an instance')\n /* 축만 있고 값이 없으면 **어디에 거는 목표인지 알 수 없다** — 조용히 전체 목표로 만들지 않는다\n (그러면 사용자는 구역 목표를 걸었다고 믿는데 전체가 바뀐다). */\n if (axis && !axisKey) throw new Error('axisKey is required when axis is given')\n if (!axis && axisKey) throw new Error('axis is required when axisKey is given')\n const domainId = context.state.domain.id\n const repo = getRepository(TwinTarget)\n const existing = await repo.findOne({\n where: { domain: { id: domainId }, spaceId: spaceId ?? null, instanceId: instanceId ?? null, metric, axis: axis ?? null, axisKey: axisKey ?? null } as any\n })\n await repo.save(\n repo.create({\n ...(existing ?? {}),\n domain: { id: domainId } as any,\n spaceId: spaceId ?? null as any,\n instanceId: instanceId ?? null as any,\n axis: axis ?? null as any,\n axisKey: axisKey ?? null as any,\n metric,\n target,\n ...(existing ? { updater: context.state.user } : { creator: context.state.user, updater: context.state.user })\n })\n )\n return true\n }\n\n @Directive('@privilege(category: \"twin\", privilege: \"mutation\")')\n @Mutation(returns => Boolean, { description: 'Remove an operational target. Removing a target removes the judgement, not the fact — the metric keeps being reported without a verdict.' })\n async deleteTwinTarget(\n @Arg('metric') metric: string,\n @Ctx() context: ResolverContext,\n @Arg('spaceId', { nullable: true }) spaceId?: string,\n @Arg('instanceId', { nullable: true }) instanceId?: string,\n @Arg('axis', { nullable: true }) axis?: string,\n @Arg('axisKey', { nullable: true }) axisKey?: string\n ): Promise<boolean> {\n const domainId = context.state.domain.id\n const repo = getRepository(TwinTarget)\n\n /*\n * **찾아서 그 행만 지운다.** 예전에는 조건 객체를 `delete()` 에 그대로 넘겼는데 **아무 행도\n * 안 지워지면서 `true` 를 돌려줬다.** 사용자는 목표를 지웠다고 믿고 화면을 떠나는데 판정은 그대로\n * 남는다(실화면에서 그렇게 당했다).\n *\n * 무엇이 조건을 깨뜨렸는지는 단정하지 않는다 — 이 조건에는 `domain` 관계 객체와 `null` 값들이\n * 함께 들어 있었고(`instanceId`·`axis`·`axisKey`), 둘 다 `delete()` 의 조건 해석에서 흔한 함정이다.\n * 같은 관계-객체 패턴을 쓰는 `resetJournal` 은 실제로 잘 지우므로 관계 객체 하나만으로 설명되지\n * 않는다. `null` 조건이 `IS NULL` 이 아니라 `= NULL` 로 나갔을 가능성이 크다.\n *\n * 그래서 조건을 고쳐 맞추는 대신 **행을 찾아 id 로 지운다**: 저장 경로와 **같은 조회 규칙**을\n * 쓰므로 규칙이 갈라지지 않고(`findOne` 은 `null` 을 제대로 다룬다), 지우는 대상이 정확히 한\n * 행이라 넓게 지울 위험도 없다.\n */\n const row = await repo.findOne({\n where: {\n domain: { id: domainId },\n spaceId: spaceId ?? null,\n instanceId: instanceId ?? null,\n axis: axis ?? null,\n axisKey: axisKey ?? null,\n metric\n } as any\n })\n /* 지울 것이 없으면 **없었다고 답한다.** 지우지 않고 true 를 주면 위와 같은 거짓말이 된다. */\n if (!row) return false\n await repo.delete(row.id)\n return true\n }\n}\n"]}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { Domain } from '@things-factory/shell';
|
|
2
|
+
import { User } from '@things-factory/auth-base';
|
|
3
|
+
export declare class TwinTarget {
|
|
4
|
+
readonly id: string;
|
|
5
|
+
domain?: Domain;
|
|
6
|
+
domainId?: string;
|
|
7
|
+
spaceId?: string;
|
|
8
|
+
instanceId?: string;
|
|
9
|
+
/**
|
|
10
|
+
* 관점 축과 그 값 — **"이 구역만", "이 교대만" 목표가 다르다.**
|
|
11
|
+
*
|
|
12
|
+
* 둘 다 비어 있으면 **전체 목표**다(창 전체의 집계를 판정한다). 축 목표는 전체 목표를 덮지 않는다 —
|
|
13
|
+
* 다른 질문이기 때문이다: "현장 전체가 100건" 과 "용접 구역이 30건" 은 동시에 참일 수 있다.
|
|
14
|
+
*/
|
|
15
|
+
axis?: string;
|
|
16
|
+
axisKey?: string;
|
|
17
|
+
metric: string;
|
|
18
|
+
target: number;
|
|
19
|
+
creator?: User;
|
|
20
|
+
creatorId?: string;
|
|
21
|
+
updater?: User;
|
|
22
|
+
updaterId?: string;
|
|
23
|
+
createdAt?: Date;
|
|
24
|
+
updatedAt?: Date;
|
|
25
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.TwinTarget = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
const typeorm_1 = require("typeorm");
|
|
6
|
+
const type_graphql_1 = require("type-graphql");
|
|
7
|
+
const shell_1 = require("@things-factory/shell");
|
|
8
|
+
const auth_base_1 = require("@things-factory/auth-base");
|
|
9
|
+
/*
|
|
10
|
+
* TwinTarget — **운영 목표.** "이 구간의 처리량이 목표에 닿았나" 를 판단하는 기준선.
|
|
11
|
+
*
|
|
12
|
+
* ── 왜 트윈이 갖나 (사용자 결정 2026-08-02) ─────────────────────────────────
|
|
13
|
+
* `@things-factory/kpi` 와 **경계를 나눈다**:
|
|
14
|
+
* 트윈(여기) 이 구간·이 교대·이 공간의 **즉시 판단**. 현장 관리자가 트윈 화면에서 고친다.
|
|
15
|
+
* 값은 저널 폴드에서 그때그때 계산된다(저장된 값을 읽지 않는다).
|
|
16
|
+
* packages/kpi **기간 평가**(조직·분기·점수·가중치·알림). 값이 저장돼 있어야 도는 체계다.
|
|
17
|
+
*
|
|
18
|
+
* 지키는 규칙 하나: **트윈은 경영 목표를 복제하지 않는다.** 조직 단위 평가가 필요하면 트윈의 사실을
|
|
19
|
+
* 내보내 그쪽이 소비하고, 그쪽 목표를 여기로 베껴 오지 않는다 — 베끼는 순간 두 개의 진실이 된다.
|
|
20
|
+
*
|
|
21
|
+
* ── 범위 (scope) ───────────────────────────────────────────────────────────
|
|
22
|
+
* `spaceId` 로 현장 전체에, `instanceId` 로 트윈 하나에 건다. 둘 다 있으면 **인스턴스가 이긴다**
|
|
23
|
+
* (좁은 선언이 넓은 선언을 덮는다 — 예외를 표현할 수 있어야 한다).
|
|
24
|
+
*
|
|
25
|
+
* 관점 축별 목표(구역마다 다른 목표)는 **아직 아니다** — 축 교차는 목표가 화면에서 쓰이기 시작한 뒤의
|
|
26
|
+
* 일이고, 처음부터 넣으면 쓰이지 않는 구조가 먼저 굳는다(performance.md §5.4-5).
|
|
27
|
+
*
|
|
28
|
+
* (CLAUDE.md: 모든 @ObjectType/@Field 영문 description 필수.)
|
|
29
|
+
*/
|
|
30
|
+
let TwinTarget = class TwinTarget {
|
|
31
|
+
};
|
|
32
|
+
exports.TwinTarget = TwinTarget;
|
|
33
|
+
tslib_1.__decorate([
|
|
34
|
+
(0, typeorm_1.PrimaryGeneratedColumn)('uuid'),
|
|
35
|
+
(0, type_graphql_1.Field)(type => type_graphql_1.ID, { description: 'Unique identifier of the target record.' }),
|
|
36
|
+
tslib_1.__metadata("design:type", String)
|
|
37
|
+
], TwinTarget.prototype, "id", void 0);
|
|
38
|
+
tslib_1.__decorate([
|
|
39
|
+
(0, typeorm_1.ManyToOne)(type => shell_1.Domain),
|
|
40
|
+
(0, type_graphql_1.Field)(type => shell_1.Domain, { nullable: true, description: 'Owning tenant domain.' }),
|
|
41
|
+
tslib_1.__metadata("design:type", shell_1.Domain)
|
|
42
|
+
], TwinTarget.prototype, "domain", void 0);
|
|
43
|
+
tslib_1.__decorate([
|
|
44
|
+
(0, typeorm_1.RelationId)((e) => e.domain),
|
|
45
|
+
tslib_1.__metadata("design:type", String)
|
|
46
|
+
], TwinTarget.prototype, "domainId", void 0);
|
|
47
|
+
tslib_1.__decorate([
|
|
48
|
+
(0, typeorm_1.Column)({ nullable: true }),
|
|
49
|
+
(0, type_graphql_1.Field)({ nullable: true, description: 'Space this target applies to (site-wide). Empty when the target is bound to a single instance.' }),
|
|
50
|
+
tslib_1.__metadata("design:type", String)
|
|
51
|
+
], TwinTarget.prototype, "spaceId", void 0);
|
|
52
|
+
tslib_1.__decorate([
|
|
53
|
+
(0, typeorm_1.Column)({ nullable: true }),
|
|
54
|
+
(0, type_graphql_1.Field)({ nullable: true, description: 'Instance this target applies to. When both space and instance match, the instance target wins so an exception can be expressed.' }),
|
|
55
|
+
tslib_1.__metadata("design:type", String)
|
|
56
|
+
], TwinTarget.prototype, "instanceId", void 0);
|
|
57
|
+
tslib_1.__decorate([
|
|
58
|
+
(0, typeorm_1.Column)({ nullable: true }),
|
|
59
|
+
(0, type_graphql_1.Field)({ nullable: true, description: 'Perspective axis this target applies to (area, shift, resource, taskKind, location, order). Empty means the target judges the window total. An axis target does not override the total — "the site does 100" and "the weld area does 30" are both true at once.' }),
|
|
60
|
+
tslib_1.__metadata("design:type", String)
|
|
61
|
+
], TwinTarget.prototype, "axis", void 0);
|
|
62
|
+
tslib_1.__decorate([
|
|
63
|
+
(0, typeorm_1.Column)({ nullable: true }),
|
|
64
|
+
(0, type_graphql_1.Field)({ nullable: true, description: 'The value on that axis (an area id, a shift name…). Required when axis is set.' }),
|
|
65
|
+
tslib_1.__metadata("design:type", String)
|
|
66
|
+
], TwinTarget.prototype, "axisKey", void 0);
|
|
67
|
+
tslib_1.__decorate([
|
|
68
|
+
(0, typeorm_1.Column)(),
|
|
69
|
+
(0, type_graphql_1.Field)({ description: 'Metric id from the twin metric vocabulary (throughput.tasks, workTime.p50Ms, …). Names are fixed in one place so target and fact never drift into two dialects.' }),
|
|
70
|
+
tslib_1.__metadata("design:type", String)
|
|
71
|
+
], TwinTarget.prototype, "metric", void 0);
|
|
72
|
+
tslib_1.__decorate([
|
|
73
|
+
(0, typeorm_1.Column)({ type: 'float' }),
|
|
74
|
+
(0, type_graphql_1.Field)(type => type_graphql_1.Float, { description: 'The target value, in the metric\'s own unit (count, milliseconds, ratio). Whether higher or lower is better is a property of the metric, not of this record — so a target can never be declared in the wrong direction.' }),
|
|
75
|
+
tslib_1.__metadata("design:type", Number)
|
|
76
|
+
], TwinTarget.prototype, "target", void 0);
|
|
77
|
+
tslib_1.__decorate([
|
|
78
|
+
(0, typeorm_1.ManyToOne)(type => auth_base_1.User, { nullable: true }),
|
|
79
|
+
(0, type_graphql_1.Field)(type => auth_base_1.User, { nullable: true, description: 'User who created this target.' }),
|
|
80
|
+
tslib_1.__metadata("design:type", auth_base_1.User)
|
|
81
|
+
], TwinTarget.prototype, "creator", void 0);
|
|
82
|
+
tslib_1.__decorate([
|
|
83
|
+
(0, typeorm_1.RelationId)((e) => e.creator),
|
|
84
|
+
tslib_1.__metadata("design:type", String)
|
|
85
|
+
], TwinTarget.prototype, "creatorId", void 0);
|
|
86
|
+
tslib_1.__decorate([
|
|
87
|
+
(0, typeorm_1.ManyToOne)(type => auth_base_1.User, { nullable: true }),
|
|
88
|
+
(0, type_graphql_1.Field)(type => auth_base_1.User, { nullable: true, description: 'User who last updated this target.' }),
|
|
89
|
+
tslib_1.__metadata("design:type", auth_base_1.User)
|
|
90
|
+
], TwinTarget.prototype, "updater", void 0);
|
|
91
|
+
tslib_1.__decorate([
|
|
92
|
+
(0, typeorm_1.RelationId)((e) => e.updater),
|
|
93
|
+
tslib_1.__metadata("design:type", String)
|
|
94
|
+
], TwinTarget.prototype, "updaterId", void 0);
|
|
95
|
+
tslib_1.__decorate([
|
|
96
|
+
(0, typeorm_1.CreateDateColumn)(),
|
|
97
|
+
(0, type_graphql_1.Field)({ nullable: true, description: 'Timestamp when the target was created.' }),
|
|
98
|
+
tslib_1.__metadata("design:type", Date)
|
|
99
|
+
], TwinTarget.prototype, "createdAt", void 0);
|
|
100
|
+
tslib_1.__decorate([
|
|
101
|
+
(0, typeorm_1.UpdateDateColumn)(),
|
|
102
|
+
(0, type_graphql_1.Field)({ nullable: true, description: 'Timestamp when the target was last updated.' }),
|
|
103
|
+
tslib_1.__metadata("design:type", Date)
|
|
104
|
+
], TwinTarget.prototype, "updatedAt", void 0);
|
|
105
|
+
exports.TwinTarget = TwinTarget = tslib_1.__decorate([
|
|
106
|
+
(0, typeorm_1.Entity)(),
|
|
107
|
+
(0, typeorm_1.Index)('ix_twin_target_0', (e) => [e.domain, e.spaceId, e.instanceId, e.metric, e.axis, e.axisKey], { unique: true }),
|
|
108
|
+
(0, type_graphql_1.ObjectType)({
|
|
109
|
+
description: 'An operational performance target for a twin metric — the line that turns a measured number into a judgement. Scoped to a space or to a single instance (instance wins over space, so an exception can be expressed). Distinct from @things-factory/kpi targets, which evaluate organizations over periods; twin targets are for immediate, on-site judgement and are never copied from the management KPI system.'
|
|
110
|
+
})
|
|
111
|
+
], TwinTarget);
|
|
112
|
+
//# sourceMappingURL=twin-target.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"twin-target.js","sourceRoot":"","sources":["../../../server/service/twin-target/twin-target.ts"],"names":[],"mappings":";;;;AAAA,qCAAkI;AAClI,+CAA2D;AAE3D,iDAA8C;AAC9C,yDAAgD;AAEhD;;;;;;;;;;;;;;;;;;;;GAoBG;AAOI,IAAM,UAAU,GAAhB,MAAM,UAAU;CA+DtB,CAAA;AA/DY,gCAAU;AAGZ;IAFR,IAAA,gCAAsB,EAAC,MAAM,CAAC;IAC9B,IAAA,oBAAK,EAAC,IAAI,CAAC,EAAE,CAAC,iBAAE,EAAE,EAAE,WAAW,EAAE,yCAAyC,EAAE,CAAC;;sCAC3D;AAInB;IAFC,IAAA,mBAAS,EAAC,IAAI,CAAC,EAAE,CAAC,cAAM,CAAC;IACzB,IAAA,oBAAK,EAAC,IAAI,CAAC,EAAE,CAAC,cAAM,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,EAAE,uBAAuB,EAAE,CAAC;sCACvE,cAAM;0CAAA;AAGf;IADC,IAAA,oBAAU,EAAC,CAAC,CAAa,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC;;4CACvB;AAIjB;IAFC,IAAA,gBAAM,EAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;IAC1B,IAAA,oBAAK,EAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,EAAE,gGAAgG,EAAE,CAAC;;2CACzH;AAIhB;IAFC,IAAA,gBAAM,EAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;IAC1B,IAAA,oBAAK,EAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,EAAE,iIAAiI,EAAE,CAAC;;8CACvJ;AAUnB;IAFC,IAAA,gBAAM,EAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;IAC1B,IAAA,oBAAK,EAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,EAAE,iQAAiQ,EAAE,CAAC;;wCAC7R;AAIb;IAFC,IAAA,gBAAM,EAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;IAC1B,IAAA,oBAAK,EAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,EAAE,gFAAgF,EAAE,CAAC;;2CACzG;AAIhB;IAFC,IAAA,gBAAM,GAAE;IACR,IAAA,oBAAK,EAAC,EAAE,WAAW,EAAE,iKAAiK,EAAE,CAAC;;0CAC5K;AAId;IAFC,IAAA,gBAAM,EAAC,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC;IACzB,IAAA,oBAAK,EAAC,IAAI,CAAC,EAAE,CAAC,oBAAK,EAAE,EAAE,WAAW,EAAE,yNAAyN,EAAE,CAAC;;0CACnP;AAId;IAFC,IAAA,mBAAS,EAAC,IAAI,CAAC,EAAE,CAAC,gBAAI,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;IAC3C,IAAA,oBAAK,EAAC,IAAI,CAAC,EAAE,CAAC,gBAAI,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,EAAE,+BAA+B,EAAE,CAAC;sCAC5E,gBAAI;2CAAA;AAGd;IADC,IAAA,oBAAU,EAAC,CAAC,CAAa,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC;;6CACvB;AAIlB;IAFC,IAAA,mBAAS,EAAC,IAAI,CAAC,EAAE,CAAC,gBAAI,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;IAC3C,IAAA,oBAAK,EAAC,IAAI,CAAC,EAAE,CAAC,gBAAI,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,EAAE,oCAAoC,EAAE,CAAC;sCACjF,gBAAI;2CAAA;AAGd;IADC,IAAA,oBAAU,EAAC,CAAC,CAAa,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC;;6CACvB;AAIlB;IAFC,IAAA,0BAAgB,GAAE;IAClB,IAAA,oBAAK,EAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,EAAE,wCAAwC,EAAE,CAAC;sCACrE,IAAI;6CAAA;AAIhB;IAFC,IAAA,0BAAgB,GAAE;IAClB,IAAA,oBAAK,EAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,EAAE,6CAA6C,EAAE,CAAC;sCAC1E,IAAI;6CAAA;qBA9DL,UAAU;IANtB,IAAA,gBAAM,GAAE;IACR,IAAA,eAAK,EAAC,kBAAkB,EAAE,CAAC,CAAa,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,UAAU,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC;IAChI,IAAA,yBAAU,EAAC;QACV,WAAW,EACT,oZAAoZ;KACvZ,CAAC;GACW,UAAU,CA+DtB","sourcesContent":["import { CreateDateColumn, Entity, Index, Column, RelationId, ManyToOne, PrimaryGeneratedColumn, UpdateDateColumn } from 'typeorm'\nimport { ObjectType, Field, ID, Float } from 'type-graphql'\n\nimport { Domain } from '@things-factory/shell'\nimport { User } from '@things-factory/auth-base'\n\n/*\n * TwinTarget — **운영 목표.** \"이 구간의 처리량이 목표에 닿았나\" 를 판단하는 기준선.\n *\n * ── 왜 트윈이 갖나 (사용자 결정 2026-08-02) ─────────────────────────────────\n * `@things-factory/kpi` 와 **경계를 나눈다**:\n * 트윈(여기) 이 구간·이 교대·이 공간의 **즉시 판단**. 현장 관리자가 트윈 화면에서 고친다.\n * 값은 저널 폴드에서 그때그때 계산된다(저장된 값을 읽지 않는다).\n * packages/kpi **기간 평가**(조직·분기·점수·가중치·알림). 값이 저장돼 있어야 도는 체계다.\n *\n * 지키는 규칙 하나: **트윈은 경영 목표를 복제하지 않는다.** 조직 단위 평가가 필요하면 트윈의 사실을\n * 내보내 그쪽이 소비하고, 그쪽 목표를 여기로 베껴 오지 않는다 — 베끼는 순간 두 개의 진실이 된다.\n *\n * ── 범위 (scope) ───────────────────────────────────────────────────────────\n * `spaceId` 로 현장 전체에, `instanceId` 로 트윈 하나에 건다. 둘 다 있으면 **인스턴스가 이긴다**\n * (좁은 선언이 넓은 선언을 덮는다 — 예외를 표현할 수 있어야 한다).\n *\n * 관점 축별 목표(구역마다 다른 목표)는 **아직 아니다** — 축 교차는 목표가 화면에서 쓰이기 시작한 뒤의\n * 일이고, 처음부터 넣으면 쓰이지 않는 구조가 먼저 굳는다(performance.md §5.4-5).\n *\n * (CLAUDE.md: 모든 @ObjectType/@Field 영문 description 필수.)\n */\n@Entity()\n@Index('ix_twin_target_0', (e: TwinTarget) => [e.domain, e.spaceId, e.instanceId, e.metric, e.axis, e.axisKey], { unique: true })\n@ObjectType({\n description:\n 'An operational performance target for a twin metric — the line that turns a measured number into a judgement. Scoped to a space or to a single instance (instance wins over space, so an exception can be expressed). Distinct from @things-factory/kpi targets, which evaluate organizations over periods; twin targets are for immediate, on-site judgement and are never copied from the management KPI system.'\n})\nexport class TwinTarget {\n @PrimaryGeneratedColumn('uuid')\n @Field(type => ID, { description: 'Unique identifier of the target record.' })\n readonly id: string\n\n @ManyToOne(type => Domain)\n @Field(type => Domain, { nullable: true, description: 'Owning tenant domain.' })\n domain?: Domain\n\n @RelationId((e: TwinTarget) => e.domain)\n domainId?: string\n\n @Column({ nullable: true })\n @Field({ nullable: true, description: 'Space this target applies to (site-wide). Empty when the target is bound to a single instance.' })\n spaceId?: string\n\n @Column({ nullable: true })\n @Field({ nullable: true, description: 'Instance this target applies to. When both space and instance match, the instance target wins so an exception can be expressed.' })\n instanceId?: string\n\n /**\n * 관점 축과 그 값 — **\"이 구역만\", \"이 교대만\" 목표가 다르다.**\n *\n * 둘 다 비어 있으면 **전체 목표**다(창 전체의 집계를 판정한다). 축 목표는 전체 목표를 덮지 않는다 —\n * 다른 질문이기 때문이다: \"현장 전체가 100건\" 과 \"용접 구역이 30건\" 은 동시에 참일 수 있다.\n */\n @Column({ nullable: true })\n @Field({ nullable: true, description: 'Perspective axis this target applies to (area, shift, resource, taskKind, location, order). Empty means the target judges the window total. An axis target does not override the total — \"the site does 100\" and \"the weld area does 30\" are both true at once.' })\n axis?: string\n\n @Column({ nullable: true })\n @Field({ nullable: true, description: 'The value on that axis (an area id, a shift name…). Required when axis is set.' })\n axisKey?: string\n\n @Column()\n @Field({ description: 'Metric id from the twin metric vocabulary (throughput.tasks, workTime.p50Ms, …). Names are fixed in one place so target and fact never drift into two dialects.' })\n metric: string\n\n @Column({ type: 'float' })\n @Field(type => Float, { description: 'The target value, in the metric\\'s own unit (count, milliseconds, ratio). Whether higher or lower is better is a property of the metric, not of this record — so a target can never be declared in the wrong direction.' })\n target: number\n\n @ManyToOne(type => User, { nullable: true })\n @Field(type => User, { nullable: true, description: 'User who created this target.' })\n creator?: User\n\n @RelationId((e: TwinTarget) => e.creator)\n creatorId?: string\n\n @ManyToOne(type => User, { nullable: true })\n @Field(type => User, { nullable: true, description: 'User who last updated this target.' })\n updater?: User\n\n @RelationId((e: TwinTarget) => e.updater)\n updaterId?: string\n\n @CreateDateColumn()\n @Field({ nullable: true, description: 'Timestamp when the target was created.' })\n createdAt?: Date\n\n @UpdateDateColumn()\n @Field({ nullable: true, description: 'Timestamp when the target was last updated.' })\n updatedAt?: Date\n}\n"]}
|