@memberjunction/ng-dashboards 6.2.0-edge.1 → 6.2.0-edge.2

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.
Files changed (82) hide show
  1. package/dist/AI/components/autotagging/dialogs/source-type-form.dialog.component.d.ts.map +1 -1
  2. package/dist/AI/components/autotagging/dialogs/source-type-form.dialog.component.js +3 -1
  3. package/dist/AI/components/autotagging/dialogs/source-type-form.dialog.component.js.map +1 -1
  4. package/dist/AI/components/autotagging/tabs/sources-tab.component.d.ts.map +1 -1
  5. package/dist/AI/components/autotagging/tabs/sources-tab.component.js +1 -2
  6. package/dist/AI/components/autotagging/tabs/sources-tab.component.js.map +1 -1
  7. package/dist/AI/components/rubric-drift-series.d.ts +32 -0
  8. package/dist/AI/components/rubric-drift-series.d.ts.map +1 -0
  9. package/dist/AI/components/rubric-drift-series.js +49 -0
  10. package/dist/AI/components/rubric-drift-series.js.map +1 -0
  11. package/dist/AI/components/rubric-drift.component.d.ts +42 -0
  12. package/dist/AI/components/rubric-drift.component.d.ts.map +1 -0
  13. package/dist/AI/components/rubric-drift.component.js +197 -0
  14. package/dist/AI/components/rubric-drift.component.js.map +1 -0
  15. package/dist/AI/components/tags/tags-resource.component.d.ts.map +1 -1
  16. package/dist/AI/components/tags/tags-resource.component.js +4 -3
  17. package/dist/AI/components/tags/tags-resource.component.js.map +1 -1
  18. package/dist/AI/components/vectors/vector-management-resource.component.d.ts.map +1 -1
  19. package/dist/AI/components/vectors/vector-management-resource.component.js +5 -2
  20. package/dist/AI/components/vectors/vector-management-resource.component.js.map +1 -1
  21. package/dist/Integration/components/connections/connections.component.d.ts +5 -0
  22. package/dist/Integration/components/connections/connections.component.d.ts.map +1 -1
  23. package/dist/Integration/components/connections/connections.component.js +151 -136
  24. package/dist/Integration/components/connections/connections.component.js.map +1 -1
  25. package/dist/KnowledgeHub/components/config/knowledge-config-resource.component.d.ts.map +1 -1
  26. package/dist/KnowledgeHub/components/config/knowledge-config-resource.component.js +3 -2
  27. package/dist/KnowledgeHub/components/config/knowledge-config-resource.component.js.map +1 -1
  28. package/dist/KnowledgeHub/components/feature-pipelines/feature-pipeline.engine.d.ts +11 -0
  29. package/dist/KnowledgeHub/components/feature-pipelines/feature-pipeline.engine.d.ts.map +1 -1
  30. package/dist/KnowledgeHub/components/feature-pipelines/feature-pipeline.engine.js +17 -1
  31. package/dist/KnowledgeHub/components/feature-pipelines/feature-pipeline.engine.js.map +1 -1
  32. package/dist/KnowledgeHub/components/feature-pipelines/feature-pipelines-resource.component.js +52 -45
  33. package/dist/KnowledgeHub/components/feature-pipelines/feature-pipelines-resource.component.js.map +1 -1
  34. package/dist/QueryBrowser/query-browser-resource.component.d.ts +11 -0
  35. package/dist/QueryBrowser/query-browser-resource.component.d.ts.map +1 -1
  36. package/dist/QueryBrowser/query-browser-resource.component.js +47 -10
  37. package/dist/QueryBrowser/query-browser-resource.component.js.map +1 -1
  38. package/dist/Rubrics/catalog-grid.d.ts +4 -0
  39. package/dist/Rubrics/catalog-grid.d.ts.map +1 -0
  40. package/dist/Rubrics/catalog-grid.js +22 -0
  41. package/dist/Rubrics/catalog-grid.js.map +1 -0
  42. package/dist/Rubrics/rubrics-dashboard.component.d.ts +31 -0
  43. package/dist/Rubrics/rubrics-dashboard.component.d.ts.map +1 -0
  44. package/dist/Rubrics/rubrics-dashboard.component.js +285 -0
  45. package/dist/Rubrics/rubrics-dashboard.component.js.map +1 -0
  46. package/dist/Rubrics/rubrics-resource.component.d.ts +13 -0
  47. package/dist/Rubrics/rubrics-resource.component.d.ts.map +1 -0
  48. package/dist/Rubrics/rubrics-resource.component.js +42 -0
  49. package/dist/Rubrics/rubrics-resource.component.js.map +1 -0
  50. package/dist/Rubrics/scales-dashboard.component.d.ts +34 -0
  51. package/dist/Rubrics/scales-dashboard.component.d.ts.map +1 -0
  52. package/dist/Rubrics/scales-dashboard.component.js +204 -0
  53. package/dist/Rubrics/scales-dashboard.component.js.map +1 -0
  54. package/dist/Rubrics/scales-resource.component.d.ts +13 -0
  55. package/dist/Rubrics/scales-resource.component.d.ts.map +1 -0
  56. package/dist/Rubrics/scales-resource.component.js +42 -0
  57. package/dist/Rubrics/scales-resource.component.js.map +1 -0
  58. package/dist/Testing/components/testing-analytics.component.d.ts +15 -0
  59. package/dist/Testing/components/testing-analytics.component.d.ts.map +1 -1
  60. package/dist/Testing/components/testing-analytics.component.js +465 -296
  61. package/dist/Testing/components/testing-analytics.component.js.map +1 -1
  62. package/dist/Testing/components/testing-review.component.d.ts +6 -0
  63. package/dist/Testing/components/testing-review.component.d.ts.map +1 -1
  64. package/dist/Testing/components/testing-review.component.js +289 -173
  65. package/dist/Testing/components/testing-review.component.js.map +1 -1
  66. package/dist/Testing/components/widgets/test-run-detail-panel.component.d.ts +8 -1
  67. package/dist/Testing/components/widgets/test-run-detail-panel.component.d.ts.map +1 -1
  68. package/dist/Testing/components/widgets/test-run-detail-panel.component.js +68 -43
  69. package/dist/Testing/components/widgets/test-run-detail-panel.component.js.map +1 -1
  70. package/dist/module.d.ts +23 -22
  71. package/dist/module.d.ts.map +1 -1
  72. package/dist/module.js +7 -0
  73. package/dist/module.js.map +1 -1
  74. package/dist/public-api.d.ts +1 -0
  75. package/dist/public-api.d.ts.map +1 -1
  76. package/dist/public-api.js +1 -0
  77. package/dist/public-api.js.map +1 -1
  78. package/dist/rubrics-dashboards.module.d.ts +17 -0
  79. package/dist/rubrics-dashboards.module.d.ts.map +1 -0
  80. package/dist/rubrics-dashboards.module.js +76 -0
  81. package/dist/rubrics-dashboards.module.js.map +1 -0
  82. package/package.json +70 -64
@@ -55,6 +55,11 @@ export interface FeaturePipelineSummary {
55
55
  LastRunSuccess: number | null;
56
56
  /** Records that errored in the most recent run, or null. */
57
57
  LastRunErrors: number | null;
58
+ /**
59
+ * The pipeline type ('LLM' or 'Decision' or custom). Defaults to 'LLM' when unset. The two built-in
60
+ * names are given in their canonical case whatever case the spec uses, as the processor matches them.
61
+ */
62
+ PipelineType: string;
58
63
  }
59
64
  /**
60
65
  * **FeaturePipelineEngine** (Knowledge Hub, Angular) — the discovery + monitoring
@@ -117,5 +122,11 @@ export declare class FeaturePipelineEngine extends BaseEngine<FeaturePipelineEng
117
122
  private static deriveOutputAttribute;
118
123
  /** Collect field names from either an object map or an array of names. */
119
124
  private static fieldNamesFrom;
125
+ /**
126
+ * Best-effort pipeline type derived from the Record Process `Configuration` JSON
127
+ * (which serializes `DataFeatureSpec`). Returns 'LLM' when unset, empty, or unparseable, and a built-in
128
+ * name ('decision') in its canonical case ('Decision').
129
+ */
130
+ private static derivePipelineType;
120
131
  }
121
132
  //# sourceMappingURL=feature-pipeline.engine.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"feature-pipeline.engine.d.ts","sourceRoot":"","sources":["../../../../src/KnowledgeHub/components/feature-pipelines/feature-pipeline.engine.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,UAAU,EAEV,iBAAiB,EAEjB,QAAQ,EACT,MAAM,sBAAsB,CAAC;AAE9B,OAAO,EAAE,qBAAqB,EAAE,kBAAkB,EAAE,MAAM,+BAA+B,CAAC;AAC1F,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,MAAM,CAAC;AAEvC;;;;;;;;GAQG;AACH,eAAO,MAAM,8BAA8B,qBAAqB,CAAC;AAEjE,mGAAmG;AACnG,MAAM,MAAM,wBAAwB,GAAG,OAAO,GAAG,SAAS,GAAG,WAAW,GAAG,QAAQ,GAAG,WAAW,CAAC;AAElG;;;;GAIG;AACH,MAAM,WAAW,sBAAsB;IACrC,oGAAoG;IACpG,EAAE,EAAE,MAAM,CAAC;IACX,8CAA8C;IAC9C,IAAI,EAAE,MAAM,CAAC;IACb,4BAA4B;IAC5B,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,uCAAuC;IACvC,MAAM,EAAE,QAAQ,GAAG,UAAU,GAAG,OAAO,CAAC;IACxC;;;;OAIG;IACH,QAAQ,EAAE,qBAAqB,CAAC,UAAU,CAAC,CAAC;IAC5C,+EAA+E;IAC/E,cAAc,EAAE,MAAM,CAAC;IACvB,yCAAyC;IACzC,YAAY,EAAE,MAAM,CAAC;IACrB,0FAA0F;IAC1F,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,6EAA6E;IAC7E,eAAe,EAAE,OAAO,CAAC;IACzB,oDAAoD;IACpD,eAAe,EAAE,OAAO,CAAC;IACzB,wFAAwF;IACxF,SAAS,EAAE,IAAI,GAAG,IAAI,CAAC;IACvB,gDAAgD;IAChD,aAAa,EAAE,wBAAwB,CAAC;IACxC,6DAA6D;IAC7D,mBAAmB,EAAE,MAAM,GAAG,IAAI,CAAC;IACnC,yDAAyD;IACzD,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,2EAA2E;IAC3E,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,4DAA4D;IAC5D,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;CAC9B;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,qBAAa,qBAAsB,SAAQ,UAAU,CAAC,qBAAqB,CAAC;IAC1E,+EAA+E;IAC/E,WAAkB,QAAQ,IAAI,qBAAqB,CAElD;IAED,OAAO,CAAC,gBAAgB,CAA+B;IACvD,OAAO,CAAC,YAAY,CAA4B;IAEhD;;;;;OAKG;IACU,MAAM,CACjB,YAAY,CAAC,EAAE,OAAO,EACtB,WAAW,CAAC,EAAE,QAAQ,EACtB,QAAQ,CAAC,EAAE,iBAAiB,GAC3B,OAAO,CAAC,IAAI,CAAC;IAUhB,qEAAqE;IACrE,IAAW,SAAS,IAAI,qBAAqB,EAAE,CAE9C;IAED,oFAAoF;IACpF,IAAW,IAAI,IAAI,kBAAkB,EAAE,CAEtC;IAED,mGAAmG;IACnG,IAAW,UAAU,IAAI,UAAU,CAAC,qBAAqB,EAAE,CAAC,CAE3D;IAED,kDAAkD;IAClD,IAAW,KAAK,IAAI,UAAU,CAAC,kBAAkB,EAAE,CAAC,CAEnD;IAID,gFAAgF;IACzE,YAAY,IAAI,sBAAsB,EAAE;IAI/C;;;OAGG;WACW,cAAc,CAC1B,SAAS,EAAE,aAAa,CAAC,qBAAqB,CAAC,EAC/C,IAAI,EAAE,aAAa,CAAC,kBAAkB,CAAC,GACtC,sBAAsB,EAAE;IAM3B,gFAAgF;IAChF,OAAO,CAAC,MAAM,CAAC,iBAAiB;IAIhC,gFAAgF;IAChF,OAAO,CAAC,MAAM,CAAC,YAAY;IAyB3B,uGAAuG;IACvG,OAAO,CAAC,MAAM,CAAC,SAAS;IAkBxB,mGAAmG;IACnG,OAAO,CAAC,MAAM,CAAC,YAAY;IAkB3B;;;;;OAKG;IACH,OAAO,CAAC,MAAM,CAAC,qBAAqB;IAkBpC,0EAA0E;IAC1E,OAAO,CAAC,MAAM,CAAC,cAAc;CAS9B"}
1
+ {"version":3,"file":"feature-pipeline.engine.d.ts","sourceRoot":"","sources":["../../../../src/KnowledgeHub/components/feature-pipelines/feature-pipeline.engine.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,UAAU,EAEV,iBAAiB,EAEjB,QAAQ,EACT,MAAM,sBAAsB,CAAC;AAE9B,OAAO,EAAE,qBAAqB,EAAE,kBAAkB,EAAE,MAAM,+BAA+B,CAAC;AAC1F,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,MAAM,CAAC;AAEvC;;;;;;;;GAQG;AACH,eAAO,MAAM,8BAA8B,qBAAqB,CAAC;AAEjE,mGAAmG;AACnG,MAAM,MAAM,wBAAwB,GAAG,OAAO,GAAG,SAAS,GAAG,WAAW,GAAG,QAAQ,GAAG,WAAW,CAAC;AAElG;;;;GAIG;AACH,MAAM,WAAW,sBAAsB;IACrC,oGAAoG;IACpG,EAAE,EAAE,MAAM,CAAC;IACX,8CAA8C;IAC9C,IAAI,EAAE,MAAM,CAAC;IACb,4BAA4B;IAC5B,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,uCAAuC;IACvC,MAAM,EAAE,QAAQ,GAAG,UAAU,GAAG,OAAO,CAAC;IACxC;;;;OAIG;IACH,QAAQ,EAAE,qBAAqB,CAAC,UAAU,CAAC,CAAC;IAC5C,+EAA+E;IAC/E,cAAc,EAAE,MAAM,CAAC;IACvB,yCAAyC;IACzC,YAAY,EAAE,MAAM,CAAC;IACrB,0FAA0F;IAC1F,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,6EAA6E;IAC7E,eAAe,EAAE,OAAO,CAAC;IACzB,oDAAoD;IACpD,eAAe,EAAE,OAAO,CAAC;IACzB,wFAAwF;IACxF,SAAS,EAAE,IAAI,GAAG,IAAI,CAAC;IACvB,gDAAgD;IAChD,aAAa,EAAE,wBAAwB,CAAC;IACxC,6DAA6D;IAC7D,mBAAmB,EAAE,MAAM,GAAG,IAAI,CAAC;IACnC,yDAAyD;IACzD,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,2EAA2E;IAC3E,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,4DAA4D;IAC5D,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B;;;OAGG;IACH,YAAY,EAAE,MAAM,CAAC;CACtB;AAUD;;;;;;;;;;;;;;;;GAgBG;AACH,qBAAa,qBAAsB,SAAQ,UAAU,CAAC,qBAAqB,CAAC;IAC1E,+EAA+E;IAC/E,WAAkB,QAAQ,IAAI,qBAAqB,CAElD;IAED,OAAO,CAAC,gBAAgB,CAA+B;IACvD,OAAO,CAAC,YAAY,CAA4B;IAEhD;;;;;OAKG;IACU,MAAM,CACjB,YAAY,CAAC,EAAE,OAAO,EACtB,WAAW,CAAC,EAAE,QAAQ,EACtB,QAAQ,CAAC,EAAE,iBAAiB,GAC3B,OAAO,CAAC,IAAI,CAAC;IAUhB,qEAAqE;IACrE,IAAW,SAAS,IAAI,qBAAqB,EAAE,CAE9C;IAED,oFAAoF;IACpF,IAAW,IAAI,IAAI,kBAAkB,EAAE,CAEtC;IAED,mGAAmG;IACnG,IAAW,UAAU,IAAI,UAAU,CAAC,qBAAqB,EAAE,CAAC,CAE3D;IAED,kDAAkD;IAClD,IAAW,KAAK,IAAI,UAAU,CAAC,kBAAkB,EAAE,CAAC,CAEnD;IAID,gFAAgF;IACzE,YAAY,IAAI,sBAAsB,EAAE;IAI/C;;;OAGG;WACW,cAAc,CAC1B,SAAS,EAAE,aAAa,CAAC,qBAAqB,CAAC,EAC/C,IAAI,EAAE,aAAa,CAAC,kBAAkB,CAAC,GACtC,sBAAsB,EAAE;IAM3B,gFAAgF;IAChF,OAAO,CAAC,MAAM,CAAC,iBAAiB;IAIhC,gFAAgF;IAChF,OAAO,CAAC,MAAM,CAAC,YAAY;IA0B3B,uGAAuG;IACvG,OAAO,CAAC,MAAM,CAAC,SAAS;IAkBxB,mGAAmG;IACnG,OAAO,CAAC,MAAM,CAAC,YAAY;IAkB3B;;;;;OAKG;IACH,OAAO,CAAC,MAAM,CAAC,qBAAqB;IAkBpC,0EAA0E;IAC1E,OAAO,CAAC,MAAM,CAAC,cAAc;IAU7B;;;;OAIG;IACH,OAAO,CAAC,MAAM,CAAC,kBAAkB;CAQlC"}
@@ -1,5 +1,5 @@
1
1
  import { BaseEngine, Metadata, } from '@memberjunction/core';
2
- import { UUIDsEqual } from '@memberjunction/global';
2
+ import { SafeJSONParse, UUIDsEqual } from '@memberjunction/global';
3
3
  /**
4
4
  * Name of the seeded `MJ: Record Process Categories` row that marks a Record
5
5
  * Process as a Feature Pipeline. Authored in
@@ -10,6 +10,8 @@ import { UUIDsEqual } from '@memberjunction/global';
10
10
  * rather than re-exported across the package boundary).
11
11
  */
12
12
  export const FEATURE_PIPELINE_CATEGORY_NAME = 'Feature Pipeline';
13
+ /** The built-in pipeline type names, by their lower-case form. */
14
+ const BUILT_IN_PIPELINE_TYPES = { llm: 'LLM', decision: 'Decision' };
13
15
  /**
14
16
  * **FeaturePipelineEngine** (Knowledge Hub, Angular) — the discovery + monitoring
15
17
  * cache for Feature Pipelines (Predictive Studio SP6).
@@ -101,6 +103,7 @@ export class FeaturePipelineEngine extends BaseEngine {
101
103
  LastRunProcessed: latest?.ProcessedItems ?? null,
102
104
  LastRunSuccess: latest?.SuccessCount ?? null,
103
105
  LastRunErrors: latest?.ErrorCount ?? null,
106
+ PipelineType: FeaturePipelineEngine.derivePipelineType(p.Configuration),
104
107
  };
105
108
  }
106
109
  /** Newest run for a pipeline id, scanning by StartTime/EndTime/createdAt regardless of input order. */
@@ -173,5 +176,18 @@ export class FeaturePipelineEngine extends BaseEngine {
173
176
  }
174
177
  return [];
175
178
  }
179
+ /**
180
+ * Best-effort pipeline type derived from the Record Process `Configuration` JSON
181
+ * (which serializes `DataFeatureSpec`). Returns 'LLM' when unset, empty, or unparseable, and a built-in
182
+ * name ('decision') in its canonical case ('Decision').
183
+ */
184
+ static derivePipelineType(config) {
185
+ const parsed = config ? SafeJSONParse(config) : null;
186
+ const typeName = parsed && typeof parsed === 'object' && typeof parsed.PipelineType === 'string' ? parsed.PipelineType.trim() : '';
187
+ if (typeName.length === 0) {
188
+ return 'LLM';
189
+ }
190
+ return BUILT_IN_PIPELINE_TYPES[typeName.toLowerCase()] ?? typeName;
191
+ }
176
192
  }
177
193
  //# sourceMappingURL=feature-pipeline.engine.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"feature-pipeline.engine.js","sourceRoot":"","sources":["../../../../src/KnowledgeHub/components/feature-pipelines/feature-pipeline.engine.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n BaseEngine,\n BaseEnginePropertyConfig,\n IMetadataProvider,\n Metadata,\n UserInfo,\n} from '@memberjunction/core';\nimport { UUIDsEqual } from '@memberjunction/global';\nimport { MJRecordProcessEntity, MJProcessRunEntity } from '@memberjunction/core-entities';\nimport type { Observable } from 'rxjs';\n\n/**\n * Name of the seeded `MJ: Record Process Categories` row that marks a Record\n * Process as a Feature Pipeline. Authored in\n * `metadata/record-process-categories/.record-process-categories.json` and pushed\n * with `mj sync`. Mirrors `FEATURE_PIPELINE_CATEGORY_NAME` in the server-side\n * `@memberjunction/predictive-studio` engine — kept in sync by value (the two\n * packages don't share a dependency, so the constant is intentionally duplicated\n * rather than re-exported across the package boundary).\n */\nexport const FEATURE_PIPELINE_CATEGORY_NAME = 'Feature Pipeline';\n\n/** Normalized last-run status for a Feature Pipeline, derived from the most recent Process Run. */\nexport type FeaturePipelineRunStatus = 'Never' | 'Running' | 'Completed' | 'Failed' | 'Cancelled';\n\n/**\n * UI-facing summary of one Feature Pipeline: what it is, the entity it writes to,\n * the attribute it produces, and last-run / freshness. Built purely from the\n * cached Record Process + Process Run entities by {@link FeaturePipelineEngine.GetSummaries}.\n */\nexport interface FeaturePipelineSummary {\n /** The underlying `MJ: Record Processes` id (also the `RunFeaturePipeline` `featurePipelineID`). */\n ID: string;\n /** Display name (the Record Process name). */\n Name: string;\n /** Optional description. */\n Description: string | null;\n /** Record Process lifecycle status. */\n Status: 'Active' | 'Disabled' | 'Draft';\n /**\n * The per-record work the pipeline performs. Derived from the entity's `WorkType` so it tracks\n * the CodeGen-generated union (sourced from the `CK_RecordProcess_WorkType` CHECK constraint) and\n * never drifts as work types are added (e.g. `'ML Model'`).\n */\n WorkType: MJRecordProcessEntity['WorkType'];\n /** Id of the target entity whose records the pipeline derives features for. */\n TargetEntityID: string;\n /** Display name of the target entity. */\n TargetEntity: string;\n /** Best-effort attribute written back, parsed from the Record Process `OutputMapping`. */\n OutputAttribute: string | null;\n /** Whether the pipeline can be run on demand (the Run button is enabled). */\n OnDemandEnabled: boolean;\n /** Whether the pipeline runs on a cron schedule. */\n ScheduleEnabled: boolean;\n /** Timestamp of the most recent run (start time, falling back to end time), or null. */\n LastRunAt: Date | null;\n /** Normalized status of the most recent run. */\n LastRunStatus: FeaturePipelineRunStatus;\n /** Id of the most recent `MJ: Process Runs` row, or null. */\n LastRunProcessRunID: string | null;\n /** Records processed in the most recent run, or null. */\n LastRunProcessed: number | null;\n /** Records whose features were written in the most recent run, or null. */\n LastRunSuccess: number | null;\n /** Records that errored in the most recent run, or null. */\n LastRunErrors: number | null;\n}\n\n/**\n * **FeaturePipelineEngine** (Knowledge Hub, Angular) — the discovery + monitoring\n * cache for Feature Pipelines (Predictive Studio SP6).\n *\n * A Feature Pipeline has no dedicated entity: it IS an `MJ: Record Processes` row\n * categorized into the seeded {@link FEATURE_PIPELINE_CATEGORY_NAME} category. This\n * engine caches those rows (plus the recent `MJ: Process Runs` headers) so the KH\n * UI can list \"what feature pipelines exist, what entity each writes to, and when\n * each last ran\" — reactively, via the canonical MJ BaseEngine pattern (`Config()`\n * declaring `BaseEnginePropertyConfig[]`, lazy-loaded, `ObserveProperty`\n * reactivity for free).\n *\n * The freshness/projection logic is a pure static function ({@link BuildSummaries})\n * so it can be exercised without a provider, and it intentionally mirrors the\n * server-side `@memberjunction/predictive-studio` engine of the same name (the two\n * don't share a dependency).\n */\nexport class FeaturePipelineEngine extends BaseEngine<FeaturePipelineEngine> {\n /** Singleton accessor for the default provider — never `new` this directly. */\n public static get Instance(): FeaturePipelineEngine {\n return super.getInstance<FeaturePipelineEngine>();\n }\n\n private _recordProcesses: MJRecordProcessEntity[] = [];\n private _processRuns: MJProcessRunEntity[] = [];\n\n /**\n * Lazy-load the Record Process + recent Process Run caches. Safe to call from\n * every entry point — a no-op when already loaded (unless `forceRefresh`).\n * Process Runs are ordered newest-first so the most recent run per pipeline is\n * the first match.\n */\n public async Config(\n forceRefresh?: boolean,\n contextUser?: UserInfo,\n provider?: IMetadataProvider,\n ): Promise<void> {\n const c: Partial<BaseEnginePropertyConfig>[] = [\n { Type: 'entity', EntityName: 'MJ: Record Processes', PropertyName: '_recordProcesses', OrderBy: 'Name' },\n { Type: 'entity', EntityName: 'MJ: Process Runs', PropertyName: '_processRuns', OrderBy: '__mj_CreatedAt DESC' },\n ];\n await super.Load(c, provider ?? Metadata.Provider, forceRefresh, contextUser);\n }\n\n // ---- Cached arrays (sync getters; observe via the streams for reactive UI) ----\n\n /** Every cached Record Process categorized as a Feature Pipeline. */\n public get Pipelines(): MJRecordProcessEntity[] {\n return (this._recordProcesses ?? []).filter((rp) => FeaturePipelineEngine.isFeaturePipeline(rp));\n }\n\n /** All cached Process Run headers (newest first; all entities — callers filter). */\n public get Runs(): MJProcessRunEntity[] {\n return this._processRuns ?? [];\n }\n\n /** Reactive stream of the raw Record Processes — re-emits on any save/delete/remote-invalidate. */\n public get Pipelines$(): Observable<MJRecordProcessEntity[]> {\n return this.ObserveProperty<MJRecordProcessEntity>('_recordProcesses');\n }\n\n /** Reactive stream of the Process Run headers. */\n public get Runs$(): Observable<MJProcessRunEntity[]> {\n return this.ObserveProperty<MJProcessRunEntity>('_processRuns');\n }\n\n // ---- Projection ----\n\n /** Build the UI summaries for every feature pipeline from the loaded caches. */\n public GetSummaries(): FeaturePipelineSummary[] {\n return FeaturePipelineEngine.BuildSummaries(this.Pipelines, this.Runs);\n }\n\n /**\n * Pure, side-effect-free summary builder — used by {@link GetSummaries} and by\n * unit tests (which pass in-memory arrays, no provider).\n */\n public static BuildSummaries(\n pipelines: ReadonlyArray<MJRecordProcessEntity>,\n runs: ReadonlyArray<MJProcessRunEntity>,\n ): FeaturePipelineSummary[] {\n return pipelines.map((p) => FeaturePipelineEngine.buildSummary(p, runs));\n }\n\n // ---- Internal helpers ----\n\n /** Whether a Record Process belongs to the seeded Feature Pipeline category. */\n private static isFeaturePipeline(rp: MJRecordProcessEntity): boolean {\n return (rp.Category ?? '').trim().toLowerCase() === FEATURE_PIPELINE_CATEGORY_NAME.toLowerCase();\n }\n\n /** Project one pipeline + the run set into a {@link FeaturePipelineSummary}. */\n private static buildSummary(\n p: MJRecordProcessEntity,\n runs: ReadonlyArray<MJProcessRunEntity>,\n ): FeaturePipelineSummary {\n const latest = FeaturePipelineEngine.newestRun(runs, p.ID);\n return {\n ID: p.ID,\n Name: p.Name,\n Description: p.Description ?? null,\n Status: p.Status,\n WorkType: p.WorkType,\n TargetEntityID: p.EntityID,\n TargetEntity: p.Entity,\n OutputAttribute: FeaturePipelineEngine.deriveOutputAttribute(p.OutputMapping),\n OnDemandEnabled: p.OnDemandEnabled,\n ScheduleEnabled: p.ScheduleEnabled,\n LastRunAt: latest?.StartTime ?? latest?.EndTime ?? null,\n LastRunStatus: FeaturePipelineEngine.mapRunStatus(latest),\n LastRunProcessRunID: latest?.ID ?? null,\n LastRunProcessed: latest?.ProcessedItems ?? null,\n LastRunSuccess: latest?.SuccessCount ?? null,\n LastRunErrors: latest?.ErrorCount ?? null,\n };\n }\n\n /** Newest run for a pipeline id, scanning by StartTime/EndTime/createdAt regardless of input order. */\n private static newestRun(\n runs: ReadonlyArray<MJProcessRunEntity>,\n recordProcessID: string,\n ): MJProcessRunEntity | undefined {\n let best: MJProcessRunEntity | undefined;\n let bestTime = -Infinity;\n for (const r of runs) {\n if (r.RecordProcessID == null || !UUIDsEqual(r.RecordProcessID, recordProcessID)) continue;\n const candidate = r.StartTime ?? r.EndTime ?? r.__mj_CreatedAt;\n const t = candidate ? new Date(candidate).getTime() : -Infinity;\n if (t > bestTime) {\n bestTime = t;\n best = r;\n }\n }\n return best;\n }\n\n /** Normalize a Process Run status into the small union the UI renders. `undefined` → `'Never'`. */\n private static mapRunStatus(run: MJProcessRunEntity | undefined): FeaturePipelineRunStatus {\n if (!run) return 'Never';\n switch (run.Status) {\n case 'Completed':\n return 'Completed';\n case 'Failed':\n return 'Failed';\n case 'Cancelled':\n return 'Cancelled';\n case 'Running':\n case 'Pending':\n case 'Paused':\n return 'Running';\n default:\n return 'Never';\n }\n }\n\n /**\n * Best-effort attribute the pipeline writes, parsed from the Record Process\n * `OutputMapping` JSON. Probes the common shapes (`field` / `targetField` /\n * `attribute`, or a `fields` array / object) and returns null otherwise. Never\n * throws on malformed JSON.\n */\n private static deriveOutputAttribute(outputMapping: string | null): string | null {\n if (!outputMapping) return null;\n let parsed: unknown;\n try {\n parsed = JSON.parse(outputMapping);\n } catch {\n return null;\n }\n if (parsed == null || typeof parsed !== 'object') return null;\n const obj = parsed as Record<string, unknown>;\n\n const single = obj['field'] ?? obj['targetField'] ?? obj['attribute'];\n if (typeof single === 'string' && single.trim()) return single.trim();\n\n const names = FeaturePipelineEngine.fieldNamesFrom(obj['fields']);\n return names.length > 0 ? names.join(', ') : null;\n }\n\n /** Collect field names from either an object map or an array of names. */\n private static fieldNamesFrom(fields: unknown): string[] {\n if (Array.isArray(fields)) {\n return fields.filter((f): f is string => typeof f === 'string' && f.trim().length > 0);\n }\n if (fields != null && typeof fields === 'object') {\n return Object.keys(fields as Record<string, unknown>);\n }\n return [];\n }\n}\n"]}
1
+ {"version":3,"file":"feature-pipeline.engine.js","sourceRoot":"","sources":["../../../../src/KnowledgeHub/components/feature-pipelines/feature-pipeline.engine.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,UAAU,EAGV,QAAQ,GAET,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AAInE;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,8BAA8B,GAAG,kBAAkB,CAAC;AA2DjE,kEAAkE;AAClE,MAAM,uBAAuB,GAA2B,EAAE,GAAG,EAAE,KAAK,EAAE,QAAQ,EAAE,UAAU,EAAE,CAAC;AAE7F;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,OAAO,qBAAsB,SAAQ,UAAiC;IAC1E,+EAA+E;IACxE,MAAM,KAAK,QAAQ;QACxB,OAAO,KAAK,CAAC,WAAW,EAAyB,CAAC;IACpD,CAAC;IAEO,gBAAgB,GAA4B,EAAE,CAAC;IAC/C,YAAY,GAAyB,EAAE,CAAC;IAEhD;;;;;OAKG;IACI,KAAK,CAAC,MAAM,CACjB,YAAsB,EACtB,WAAsB,EACtB,QAA4B;QAE5B,MAAM,CAAC,GAAwC;YAC7C,EAAE,IAAI,EAAE,QAAQ,EAAE,UAAU,EAAE,sBAAsB,EAAE,YAAY,EAAE,kBAAkB,EAAE,OAAO,EAAE,MAAM,EAAE;YACzG,EAAE,IAAI,EAAE,QAAQ,EAAE,UAAU,EAAE,kBAAkB,EAAE,YAAY,EAAE,cAAc,EAAE,OAAO,EAAE,qBAAqB,EAAE;SACjH,CAAC;QACF,MAAM,KAAK,CAAC,IAAI,CAAC,CAAC,EAAE,QAAQ,IAAI,QAAQ,CAAC,QAAQ,EAAE,YAAY,EAAE,WAAW,CAAC,CAAC;IAChF,CAAC;IAED,kFAAkF;IAElF,qEAAqE;IACrE,IAAW,SAAS;QAClB,OAAO,CAAC,IAAI,CAAC,gBAAgB,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,qBAAqB,CAAC,iBAAiB,CAAC,EAAE,CAAC,CAAC,CAAC;IACnG,CAAC;IAED,oFAAoF;IACpF,IAAW,IAAI;QACb,OAAO,IAAI,CAAC,YAAY,IAAI,EAAE,CAAC;IACjC,CAAC;IAED,mGAAmG;IACnG,IAAW,UAAU;QACnB,OAAO,IAAI,CAAC,eAAe,CAAwB,kBAAkB,CAAC,CAAC;IACzE,CAAC;IAED,kDAAkD;IAClD,IAAW,KAAK;QACd,OAAO,IAAI,CAAC,eAAe,CAAqB,cAAc,CAAC,CAAC;IAClE,CAAC;IAED,uBAAuB;IAEvB,gFAAgF;IACzE,YAAY;QACjB,OAAO,qBAAqB,CAAC,cAAc,CAAC,IAAI,CAAC,SAAS,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC;IACzE,CAAC;IAED;;;OAGG;IACI,MAAM,CAAC,cAAc,CAC1B,SAA+C,EAC/C,IAAuC;QAEvC,OAAO,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,qBAAqB,CAAC,YAAY,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,CAAC;IAC3E,CAAC;IAED,6BAA6B;IAE7B,gFAAgF;IACxE,MAAM,CAAC,iBAAiB,CAAC,EAAyB;QACxD,OAAO,CAAC,EAAE,CAAC,QAAQ,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,KAAK,8BAA8B,CAAC,WAAW,EAAE,CAAC;IACnG,CAAC;IAED,gFAAgF;IACxE,MAAM,CAAC,YAAY,CACzB,CAAwB,EACxB,IAAuC;QAEvC,MAAM,MAAM,GAAG,qBAAqB,CAAC,SAAS,CAAC,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC;QAC3D,OAAO;YACL,EAAE,EAAE,CAAC,CAAC,EAAE;YACR,IAAI,EAAE,CAAC,CAAC,IAAI;YACZ,WAAW,EAAE,CAAC,CAAC,WAAW,IAAI,IAAI;YAClC,MAAM,EAAE,CAAC,CAAC,MAAM;YAChB,QAAQ,EAAE,CAAC,CAAC,QAAQ;YACpB,cAAc,EAAE,CAAC,CAAC,QAAQ;YAC1B,YAAY,EAAE,CAAC,CAAC,MAAM;YACtB,eAAe,EAAE,qBAAqB,CAAC,qBAAqB,CAAC,CAAC,CAAC,aAAa,CAAC;YAC7E,eAAe,EAAE,CAAC,CAAC,eAAe;YAClC,eAAe,EAAE,CAAC,CAAC,eAAe;YAClC,SAAS,EAAE,MAAM,EAAE,SAAS,IAAI,MAAM,EAAE,OAAO,IAAI,IAAI;YACvD,aAAa,EAAE,qBAAqB,CAAC,YAAY,CAAC,MAAM,CAAC;YACzD,mBAAmB,EAAE,MAAM,EAAE,EAAE,IAAI,IAAI;YACvC,gBAAgB,EAAE,MAAM,EAAE,cAAc,IAAI,IAAI;YAChD,cAAc,EAAE,MAAM,EAAE,YAAY,IAAI,IAAI;YAC5C,aAAa,EAAE,MAAM,EAAE,UAAU,IAAI,IAAI;YACzC,YAAY,EAAE,qBAAqB,CAAC,kBAAkB,CAAC,CAAC,CAAC,aAAa,CAAC;SACxE,CAAC;IACJ,CAAC;IAED,uGAAuG;IAC/F,MAAM,CAAC,SAAS,CACtB,IAAuC,EACvC,eAAuB;QAEvB,IAAI,IAAoC,CAAC;QACzC,IAAI,QAAQ,GAAG,CAAC,QAAQ,CAAC;QACzB,KAAK,MAAM,CAAC,IAAI,IAAI,EAAE,CAAC;YACrB,IAAI,CAAC,CAAC,eAAe,IAAI,IAAI,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,eAAe,EAAE,eAAe,CAAC;gBAAE,SAAS;YAC3F,MAAM,SAAS,GAAG,CAAC,CAAC,SAAS,IAAI,CAAC,CAAC,OAAO,IAAI,CAAC,CAAC,cAAc,CAAC;YAC/D,MAAM,CAAC,GAAG,SAAS,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,SAAS,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC;YAChE,IAAI,CAAC,GAAG,QAAQ,EAAE,CAAC;gBACjB,QAAQ,GAAG,CAAC,CAAC;gBACb,IAAI,GAAG,CAAC,CAAC;YACX,CAAC;QACH,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC;IAED,mGAAmG;IAC3F,MAAM,CAAC,YAAY,CAAC,GAAmC;QAC7D,IAAI,CAAC,GAAG;YAAE,OAAO,OAAO,CAAC;QACzB,QAAQ,GAAG,CAAC,MAAM,EAAE,CAAC;YACnB,KAAK,WAAW;gBACd,OAAO,WAAW,CAAC;YACrB,KAAK,QAAQ;gBACX,OAAO,QAAQ,CAAC;YAClB,KAAK,WAAW;gBACd,OAAO,WAAW,CAAC;YACrB,KAAK,SAAS,CAAC;YACf,KAAK,SAAS,CAAC;YACf,KAAK,QAAQ;gBACX,OAAO,SAAS,CAAC;YACnB;gBACE,OAAO,OAAO,CAAC;QACnB,CAAC;IACH,CAAC;IAED;;;;;OAKG;IACK,MAAM,CAAC,qBAAqB,CAAC,aAA4B;QAC/D,IAAI,CAAC,aAAa;YAAE,OAAO,IAAI,CAAC;QAChC,IAAI,MAAe,CAAC;QACpB,IAAI,CAAC;YACH,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,aAAa,CAAC,CAAC;QACrC,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,IAAI,CAAC;QACd,CAAC;QACD,IAAI,MAAM,IAAI,IAAI,IAAI,OAAO,MAAM,KAAK,QAAQ;YAAE,OAAO,IAAI,CAAC;QAC9D,MAAM,GAAG,GAAG,MAAiC,CAAC;QAE9C,MAAM,MAAM,GAAG,GAAG,CAAC,OAAO,CAAC,IAAI,GAAG,CAAC,aAAa,CAAC,IAAI,GAAG,CAAC,WAAW,CAAC,CAAC;QACtE,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,MAAM,CAAC,IAAI,EAAE;YAAE,OAAO,MAAM,CAAC,IAAI,EAAE,CAAC;QAEtE,MAAM,KAAK,GAAG,qBAAqB,CAAC,cAAc,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC;QAClE,OAAO,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IACpD,CAAC;IAED,0EAA0E;IAClE,MAAM,CAAC,cAAc,CAAC,MAAe;QAC3C,IAAI,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;YAC1B,OAAO,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAe,EAAE,CAAC,OAAO,CAAC,KAAK,QAAQ,IAAI,CAAC,CAAC,IAAI,EAAE,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;QACzF,CAAC;QACD,IAAI,MAAM,IAAI,IAAI,IAAI,OAAO,MAAM,KAAK,QAAQ,EAAE,CAAC;YACjD,OAAO,MAAM,CAAC,IAAI,CAAC,MAAiC,CAAC,CAAC;QACxD,CAAC;QACD,OAAO,EAAE,CAAC;IACZ,CAAC;IAED;;;;OAIG;IACK,MAAM,CAAC,kBAAkB,CAAC,MAAqB;QACrD,MAAM,MAAM,GAAG,MAAM,CAAC,CAAC,CAAC,aAAa,CAA4B,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;QAChF,MAAM,QAAQ,GAAG,MAAM,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,OAAO,MAAM,CAAC,YAAY,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,YAAY,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACnI,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC1B,OAAO,KAAK,CAAC;QACf,CAAC;QACD,OAAO,uBAAuB,CAAC,QAAQ,CAAC,WAAW,EAAE,CAAC,IAAI,QAAQ,CAAC;IACrE,CAAC;CACF","sourcesContent":["import {\n BaseEngine,\n BaseEnginePropertyConfig,\n IMetadataProvider,\n Metadata,\n UserInfo,\n} from '@memberjunction/core';\nimport { SafeJSONParse, UUIDsEqual } from '@memberjunction/global';\nimport { MJRecordProcessEntity, MJProcessRunEntity } from '@memberjunction/core-entities';\nimport type { Observable } from 'rxjs';\n\n/**\n * Name of the seeded `MJ: Record Process Categories` row that marks a Record\n * Process as a Feature Pipeline. Authored in\n * `metadata/record-process-categories/.record-process-categories.json` and pushed\n * with `mj sync`. Mirrors `FEATURE_PIPELINE_CATEGORY_NAME` in the server-side\n * `@memberjunction/predictive-studio` engine — kept in sync by value (the two\n * packages don't share a dependency, so the constant is intentionally duplicated\n * rather than re-exported across the package boundary).\n */\nexport const FEATURE_PIPELINE_CATEGORY_NAME = 'Feature Pipeline';\n\n/** Normalized last-run status for a Feature Pipeline, derived from the most recent Process Run. */\nexport type FeaturePipelineRunStatus = 'Never' | 'Running' | 'Completed' | 'Failed' | 'Cancelled';\n\n/**\n * UI-facing summary of one Feature Pipeline: what it is, the entity it writes to,\n * the attribute it produces, and last-run / freshness. Built purely from the\n * cached Record Process + Process Run entities by {@link FeaturePipelineEngine.GetSummaries}.\n */\nexport interface FeaturePipelineSummary {\n /** The underlying `MJ: Record Processes` id (also the `RunFeaturePipeline` `featurePipelineID`). */\n ID: string;\n /** Display name (the Record Process name). */\n Name: string;\n /** Optional description. */\n Description: string | null;\n /** Record Process lifecycle status. */\n Status: 'Active' | 'Disabled' | 'Draft';\n /**\n * The per-record work the pipeline performs. Derived from the entity's `WorkType` so it tracks\n * the CodeGen-generated union (sourced from the `CK_RecordProcess_WorkType` CHECK constraint) and\n * never drifts as work types are added (e.g. `'ML Model'`).\n */\n WorkType: MJRecordProcessEntity['WorkType'];\n /** Id of the target entity whose records the pipeline derives features for. */\n TargetEntityID: string;\n /** Display name of the target entity. */\n TargetEntity: string;\n /** Best-effort attribute written back, parsed from the Record Process `OutputMapping`. */\n OutputAttribute: string | null;\n /** Whether the pipeline can be run on demand (the Run button is enabled). */\n OnDemandEnabled: boolean;\n /** Whether the pipeline runs on a cron schedule. */\n ScheduleEnabled: boolean;\n /** Timestamp of the most recent run (start time, falling back to end time), or null. */\n LastRunAt: Date | null;\n /** Normalized status of the most recent run. */\n LastRunStatus: FeaturePipelineRunStatus;\n /** Id of the most recent `MJ: Process Runs` row, or null. */\n LastRunProcessRunID: string | null;\n /** Records processed in the most recent run, or null. */\n LastRunProcessed: number | null;\n /** Records whose features were written in the most recent run, or null. */\n LastRunSuccess: number | null;\n /** Records that errored in the most recent run, or null. */\n LastRunErrors: number | null;\n /**\n * The pipeline type ('LLM' or 'Decision' or custom). Defaults to 'LLM' when unset. The two built-in\n * names are given in their canonical case whatever case the spec uses, as the processor matches them.\n */\n PipelineType: string;\n}\n\n/** The one DataFeatureSpec property the summary reads from a Record Process `Configuration`. */\ninterface ConfigurationPipelineType {\n PipelineType?: string | null;\n}\n\n/** The built-in pipeline type names, by their lower-case form. */\nconst BUILT_IN_PIPELINE_TYPES: Record<string, string> = { llm: 'LLM', decision: 'Decision' };\n\n/**\n * **FeaturePipelineEngine** (Knowledge Hub, Angular) — the discovery + monitoring\n * cache for Feature Pipelines (Predictive Studio SP6).\n *\n * A Feature Pipeline has no dedicated entity: it IS an `MJ: Record Processes` row\n * categorized into the seeded {@link FEATURE_PIPELINE_CATEGORY_NAME} category. This\n * engine caches those rows (plus the recent `MJ: Process Runs` headers) so the KH\n * UI can list \"what feature pipelines exist, what entity each writes to, and when\n * each last ran\" — reactively, via the canonical MJ BaseEngine pattern (`Config()`\n * declaring `BaseEnginePropertyConfig[]`, lazy-loaded, `ObserveProperty`\n * reactivity for free).\n *\n * The freshness/projection logic is a pure static function ({@link BuildSummaries})\n * so it can be exercised without a provider, and it intentionally mirrors the\n * server-side `@memberjunction/predictive-studio` engine of the same name (the two\n * don't share a dependency).\n */\nexport class FeaturePipelineEngine extends BaseEngine<FeaturePipelineEngine> {\n /** Singleton accessor for the default provider — never `new` this directly. */\n public static get Instance(): FeaturePipelineEngine {\n return super.getInstance<FeaturePipelineEngine>();\n }\n\n private _recordProcesses: MJRecordProcessEntity[] = [];\n private _processRuns: MJProcessRunEntity[] = [];\n\n /**\n * Lazy-load the Record Process + recent Process Run caches. Safe to call from\n * every entry point — a no-op when already loaded (unless `forceRefresh`).\n * Process Runs are ordered newest-first so the most recent run per pipeline is\n * the first match.\n */\n public async Config(\n forceRefresh?: boolean,\n contextUser?: UserInfo,\n provider?: IMetadataProvider,\n ): Promise<void> {\n const c: Partial<BaseEnginePropertyConfig>[] = [\n { Type: 'entity', EntityName: 'MJ: Record Processes', PropertyName: '_recordProcesses', OrderBy: 'Name' },\n { Type: 'entity', EntityName: 'MJ: Process Runs', PropertyName: '_processRuns', OrderBy: '__mj_CreatedAt DESC' },\n ];\n await super.Load(c, provider ?? Metadata.Provider, forceRefresh, contextUser);\n }\n\n // ---- Cached arrays (sync getters; observe via the streams for reactive UI) ----\n\n /** Every cached Record Process categorized as a Feature Pipeline. */\n public get Pipelines(): MJRecordProcessEntity[] {\n return (this._recordProcesses ?? []).filter((rp) => FeaturePipelineEngine.isFeaturePipeline(rp));\n }\n\n /** All cached Process Run headers (newest first; all entities — callers filter). */\n public get Runs(): MJProcessRunEntity[] {\n return this._processRuns ?? [];\n }\n\n /** Reactive stream of the raw Record Processes — re-emits on any save/delete/remote-invalidate. */\n public get Pipelines$(): Observable<MJRecordProcessEntity[]> {\n return this.ObserveProperty<MJRecordProcessEntity>('_recordProcesses');\n }\n\n /** Reactive stream of the Process Run headers. */\n public get Runs$(): Observable<MJProcessRunEntity[]> {\n return this.ObserveProperty<MJProcessRunEntity>('_processRuns');\n }\n\n // ---- Projection ----\n\n /** Build the UI summaries for every feature pipeline from the loaded caches. */\n public GetSummaries(): FeaturePipelineSummary[] {\n return FeaturePipelineEngine.BuildSummaries(this.Pipelines, this.Runs);\n }\n\n /**\n * Pure, side-effect-free summary builder — used by {@link GetSummaries} and by\n * unit tests (which pass in-memory arrays, no provider).\n */\n public static BuildSummaries(\n pipelines: ReadonlyArray<MJRecordProcessEntity>,\n runs: ReadonlyArray<MJProcessRunEntity>,\n ): FeaturePipelineSummary[] {\n return pipelines.map((p) => FeaturePipelineEngine.buildSummary(p, runs));\n }\n\n // ---- Internal helpers ----\n\n /** Whether a Record Process belongs to the seeded Feature Pipeline category. */\n private static isFeaturePipeline(rp: MJRecordProcessEntity): boolean {\n return (rp.Category ?? '').trim().toLowerCase() === FEATURE_PIPELINE_CATEGORY_NAME.toLowerCase();\n }\n\n /** Project one pipeline + the run set into a {@link FeaturePipelineSummary}. */\n private static buildSummary(\n p: MJRecordProcessEntity,\n runs: ReadonlyArray<MJProcessRunEntity>,\n ): FeaturePipelineSummary {\n const latest = FeaturePipelineEngine.newestRun(runs, p.ID);\n return {\n ID: p.ID,\n Name: p.Name,\n Description: p.Description ?? null,\n Status: p.Status,\n WorkType: p.WorkType,\n TargetEntityID: p.EntityID,\n TargetEntity: p.Entity,\n OutputAttribute: FeaturePipelineEngine.deriveOutputAttribute(p.OutputMapping),\n OnDemandEnabled: p.OnDemandEnabled,\n ScheduleEnabled: p.ScheduleEnabled,\n LastRunAt: latest?.StartTime ?? latest?.EndTime ?? null,\n LastRunStatus: FeaturePipelineEngine.mapRunStatus(latest),\n LastRunProcessRunID: latest?.ID ?? null,\n LastRunProcessed: latest?.ProcessedItems ?? null,\n LastRunSuccess: latest?.SuccessCount ?? null,\n LastRunErrors: latest?.ErrorCount ?? null,\n PipelineType: FeaturePipelineEngine.derivePipelineType(p.Configuration),\n };\n }\n\n /** Newest run for a pipeline id, scanning by StartTime/EndTime/createdAt regardless of input order. */\n private static newestRun(\n runs: ReadonlyArray<MJProcessRunEntity>,\n recordProcessID: string,\n ): MJProcessRunEntity | undefined {\n let best: MJProcessRunEntity | undefined;\n let bestTime = -Infinity;\n for (const r of runs) {\n if (r.RecordProcessID == null || !UUIDsEqual(r.RecordProcessID, recordProcessID)) continue;\n const candidate = r.StartTime ?? r.EndTime ?? r.__mj_CreatedAt;\n const t = candidate ? new Date(candidate).getTime() : -Infinity;\n if (t > bestTime) {\n bestTime = t;\n best = r;\n }\n }\n return best;\n }\n\n /** Normalize a Process Run status into the small union the UI renders. `undefined` → `'Never'`. */\n private static mapRunStatus(run: MJProcessRunEntity | undefined): FeaturePipelineRunStatus {\n if (!run) return 'Never';\n switch (run.Status) {\n case 'Completed':\n return 'Completed';\n case 'Failed':\n return 'Failed';\n case 'Cancelled':\n return 'Cancelled';\n case 'Running':\n case 'Pending':\n case 'Paused':\n return 'Running';\n default:\n return 'Never';\n }\n }\n\n /**\n * Best-effort attribute the pipeline writes, parsed from the Record Process\n * `OutputMapping` JSON. Probes the common shapes (`field` / `targetField` /\n * `attribute`, or a `fields` array / object) and returns null otherwise. Never\n * throws on malformed JSON.\n */\n private static deriveOutputAttribute(outputMapping: string | null): string | null {\n if (!outputMapping) return null;\n let parsed: unknown;\n try {\n parsed = JSON.parse(outputMapping);\n } catch {\n return null;\n }\n if (parsed == null || typeof parsed !== 'object') return null;\n const obj = parsed as Record<string, unknown>;\n\n const single = obj['field'] ?? obj['targetField'] ?? obj['attribute'];\n if (typeof single === 'string' && single.trim()) return single.trim();\n\n const names = FeaturePipelineEngine.fieldNamesFrom(obj['fields']);\n return names.length > 0 ? names.join(', ') : null;\n }\n\n /** Collect field names from either an object map or an array of names. */\n private static fieldNamesFrom(fields: unknown): string[] {\n if (Array.isArray(fields)) {\n return fields.filter((f): f is string => typeof f === 'string' && f.trim().length > 0);\n }\n if (fields != null && typeof fields === 'object') {\n return Object.keys(fields as Record<string, unknown>);\n }\n return [];\n }\n\n /**\n * Best-effort pipeline type derived from the Record Process `Configuration` JSON\n * (which serializes `DataFeatureSpec`). Returns 'LLM' when unset, empty, or unparseable, and a built-in\n * name ('decision') in its canonical case ('Decision').\n */\n private static derivePipelineType(config: string | null): string {\n const parsed = config ? SafeJSONParse<ConfigurationPipelineType>(config) : null;\n const typeName = parsed && typeof parsed === 'object' && typeof parsed.PipelineType === 'string' ? parsed.PipelineType.trim() : '';\n if (typeName.length === 0) {\n return 'LLM';\n }\n return BUILT_IN_PIPELINE_TYPES[typeName.toLowerCase()] ?? typeName;\n }\n}\n\n"]}
@@ -65,8 +65,8 @@ function FeaturePipelinesResourceComponent_Conditional_14_Template(rf, ctx) { if
65
65
  function FeaturePipelinesResourceComponent_Conditional_15_Template(rf, ctx) { if (rf & 1) {
66
66
  i0.ɵɵelement(0, "mj-empty-state", 13);
67
67
  } }
68
- function FeaturePipelinesResourceComponent_Conditional_16_For_2_Conditional_10_Template(rf, ctx) { if (rf & 1) {
69
- i0.ɵɵelementStart(0, "p", 25);
68
+ function FeaturePipelinesResourceComponent_Conditional_16_For_2_Conditional_13_Template(rf, ctx) { if (rf & 1) {
69
+ i0.ɵɵelementStart(0, "p", 27);
70
70
  i0.ɵɵtext(1);
71
71
  i0.ɵɵelementEnd();
72
72
  } if (rf & 2) {
@@ -75,8 +75,8 @@ function FeaturePipelinesResourceComponent_Conditional_16_For_2_Conditional_10_T
75
75
  i0.ɵɵadvance();
76
76
  i0.ɵɵtextInterpolate(pipeline_r4.Description);
77
77
  } }
78
- function FeaturePipelinesResourceComponent_Conditional_16_For_2_Conditional_30_Template(rf, ctx) { if (rf & 1) {
79
- i0.ɵɵelementStart(0, "span", 33);
78
+ function FeaturePipelinesResourceComponent_Conditional_16_For_2_Conditional_33_Template(rf, ctx) { if (rf & 1) {
79
+ i0.ɵɵelementStart(0, "span", 35);
80
80
  i0.ɵɵtext(1);
81
81
  i0.ɵɵelementEnd();
82
82
  } if (rf & 2) {
@@ -84,12 +84,12 @@ function FeaturePipelinesResourceComponent_Conditional_16_For_2_Conditional_30_T
84
84
  i0.ɵɵadvance();
85
85
  i0.ɵɵtextInterpolate2("\u00B7 ", pipeline_r4.LastRunSuccess, "/", pipeline_r4.LastRunProcessed, " written");
86
86
  } }
87
- function FeaturePipelinesResourceComponent_Conditional_16_For_2_Conditional_33_Template(rf, ctx) { if (rf & 1) {
88
- i0.ɵɵelement(0, "i", 38);
87
+ function FeaturePipelinesResourceComponent_Conditional_16_For_2_Conditional_36_Template(rf, ctx) { if (rf & 1) {
88
+ i0.ɵɵelement(0, "i", 40);
89
89
  i0.ɵɵtext(1, " Running\u2026 ");
90
90
  } }
91
- function FeaturePipelinesResourceComponent_Conditional_16_For_2_Conditional_34_Template(rf, ctx) { if (rf & 1) {
92
- i0.ɵɵelement(0, "i", 39);
91
+ function FeaturePipelinesResourceComponent_Conditional_16_For_2_Conditional_37_Template(rf, ctx) { if (rf & 1) {
92
+ i0.ɵɵelement(0, "i", 41);
93
93
  i0.ɵɵtext(1, " Run ");
94
94
  } }
95
95
  function FeaturePipelinesResourceComponent_Conditional_16_For_2_Template(rf, ctx) { if (rf & 1) {
@@ -99,43 +99,46 @@ function FeaturePipelinesResourceComponent_Conditional_16_For_2_Template(rf, ctx
99
99
  i0.ɵɵelementStart(3, "div", 21)(4, "h3", 22);
100
100
  i0.ɵɵtext(5);
101
101
  i0.ɵɵelementEnd();
102
- i0.ɵɵelementStart(6, "span", 23);
103
- i0.ɵɵtext(7);
104
- i0.ɵɵelementEnd()();
105
- i0.ɵɵelementStart(8, "span", 24);
106
- i0.ɵɵtext(9);
102
+ i0.ɵɵelementStart(6, "div", 23)(7, "span", 24);
103
+ i0.ɵɵtext(8);
104
+ i0.ɵɵelementEnd();
105
+ i0.ɵɵelementStart(9, "span", 25);
106
+ i0.ɵɵtext(10);
107
+ i0.ɵɵelementEnd()()();
108
+ i0.ɵɵelementStart(11, "span", 26);
109
+ i0.ɵɵtext(12);
107
110
  i0.ɵɵelementEnd()();
108
- i0.ɵɵconditionalCreate(10, FeaturePipelinesResourceComponent_Conditional_16_For_2_Conditional_10_Template, 2, 2, "p", 25);
109
- i0.ɵɵelementStart(11, "div", 26)(12, "div", 27)(13, "span", 28);
110
- i0.ɵɵelement(14, "i", 29);
111
- i0.ɵɵtext(15, " Target");
111
+ i0.ɵɵconditionalCreate(13, FeaturePipelinesResourceComponent_Conditional_16_For_2_Conditional_13_Template, 2, 2, "p", 27);
112
+ i0.ɵɵelementStart(14, "div", 28)(15, "div", 29)(16, "span", 30);
113
+ i0.ɵɵelement(17, "i", 31);
114
+ i0.ɵɵtext(18, " Target");
112
115
  i0.ɵɵelementEnd();
113
- i0.ɵɵelementStart(16, "span", 30);
114
- i0.ɵɵtext(17);
116
+ i0.ɵɵelementStart(19, "span", 32);
117
+ i0.ɵɵtext(20);
115
118
  i0.ɵɵelementEnd()();
116
- i0.ɵɵelementStart(18, "div", 27)(19, "span", 28);
117
- i0.ɵɵelement(20, "i", 31);
118
- i0.ɵɵtext(21, " Writes");
119
+ i0.ɵɵelementStart(21, "div", 29)(22, "span", 30);
120
+ i0.ɵɵelement(23, "i", 33);
121
+ i0.ɵɵtext(24, " Writes");
119
122
  i0.ɵɵelementEnd();
120
- i0.ɵɵelementStart(22, "span", 30);
121
- i0.ɵɵtext(23);
123
+ i0.ɵɵelementStart(25, "span", 32);
124
+ i0.ɵɵtext(26);
122
125
  i0.ɵɵelementEnd()();
123
- i0.ɵɵelementStart(24, "div", 27)(25, "span", 28);
124
- i0.ɵɵelement(26, "i", 32);
125
- i0.ɵɵtext(27, " Last run");
126
+ i0.ɵɵelementStart(27, "div", 29)(28, "span", 30);
127
+ i0.ɵɵelement(29, "i", 34);
128
+ i0.ɵɵtext(30, " Last run");
126
129
  i0.ɵɵelementEnd();
127
- i0.ɵɵelementStart(28, "span", 30);
128
- i0.ɵɵtext(29);
129
- i0.ɵɵconditionalCreate(30, FeaturePipelinesResourceComponent_Conditional_16_For_2_Conditional_30_Template, 2, 2, "span", 33);
130
+ i0.ɵɵelementStart(31, "span", 32);
131
+ i0.ɵɵtext(32);
132
+ i0.ɵɵconditionalCreate(33, FeaturePipelinesResourceComponent_Conditional_16_For_2_Conditional_33_Template, 2, 2, "span", 35);
130
133
  i0.ɵɵelementEnd()()();
131
- i0.ɵɵelementStart(31, "div", 34)(32, "button", 35);
132
- i0.ɵɵlistener("click", function FeaturePipelinesResourceComponent_Conditional_16_For_2_Template_button_click_32_listener() { const pipeline_r4 = i0.ɵɵrestoreView(_r3).$implicit; const ctx_r0 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r0.OnRunPipeline(pipeline_r4)); });
133
- i0.ɵɵconditionalCreate(33, FeaturePipelinesResourceComponent_Conditional_16_For_2_Conditional_33_Template, 2, 0)(34, FeaturePipelinesResourceComponent_Conditional_16_For_2_Conditional_34_Template, 2, 0);
134
+ i0.ɵɵelementStart(34, "div", 36)(35, "button", 37);
135
+ i0.ɵɵlistener("click", function FeaturePipelinesResourceComponent_Conditional_16_For_2_Template_button_click_35_listener() { const pipeline_r4 = i0.ɵɵrestoreView(_r3).$implicit; const ctx_r0 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r0.OnRunPipeline(pipeline_r4)); });
136
+ i0.ɵɵconditionalCreate(36, FeaturePipelinesResourceComponent_Conditional_16_For_2_Conditional_36_Template, 2, 0)(37, FeaturePipelinesResourceComponent_Conditional_16_For_2_Conditional_37_Template, 2, 0);
134
137
  i0.ɵɵelementEnd();
135
- i0.ɵɵelementStart(35, "button", 36);
136
- i0.ɵɵlistener("click", function FeaturePipelinesResourceComponent_Conditional_16_For_2_Template_button_click_35_listener() { const pipeline_r4 = i0.ɵɵrestoreView(_r3).$implicit; const ctx_r0 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r0.OnViewPipeline(pipeline_r4)); });
137
- i0.ɵɵelement(36, "i", 37);
138
- i0.ɵɵtext(37, " View ");
138
+ i0.ɵɵelementStart(38, "button", 38);
139
+ i0.ɵɵlistener("click", function FeaturePipelinesResourceComponent_Conditional_16_For_2_Template_button_click_38_listener() { const pipeline_r4 = i0.ɵɵrestoreView(_r3).$implicit; const ctx_r0 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r0.OnViewPipeline(pipeline_r4)); });
140
+ i0.ɵɵelement(39, "i", 39);
141
+ i0.ɵɵtext(40, " View ");
139
142
  i0.ɵɵelementEnd()()();
140
143
  } if (rf & 2) {
141
144
  const pipeline_r4 = ctx.$implicit;
@@ -147,14 +150,18 @@ function FeaturePipelinesResourceComponent_Conditional_16_For_2_Template(rf, ctx
147
150
  i0.ɵɵproperty("title", pipeline_r4.Name);
148
151
  i0.ɵɵadvance();
149
152
  i0.ɵɵtextInterpolate(pipeline_r4.Name);
150
- i0.ɵɵadvance(2);
153
+ i0.ɵɵadvance(3);
151
154
  i0.ɵɵtextInterpolate(pipeline_r4.WorkType);
152
155
  i0.ɵɵadvance();
156
+ i0.ɵɵclassProp("fp-type-badge--decision", pipeline_r4.PipelineType === "Decision");
157
+ i0.ɵɵadvance();
158
+ i0.ɵɵtextInterpolate(pipeline_r4.PipelineType);
159
+ i0.ɵɵadvance();
153
160
  i0.ɵɵclassMap(ctx_r0.RunStatusClass(pipeline_r4.LastRunStatus));
154
161
  i0.ɵɵadvance();
155
162
  i0.ɵɵtextInterpolate1(" ", ctx_r0.RunStatusLabel(pipeline_r4.LastRunStatus), " ");
156
163
  i0.ɵɵadvance();
157
- i0.ɵɵconditional(pipeline_r4.Description ? 10 : -1);
164
+ i0.ɵɵconditional(pipeline_r4.Description ? 13 : -1);
158
165
  i0.ɵɵadvance(7);
159
166
  i0.ɵɵtextInterpolate(pipeline_r4.TargetEntity);
160
167
  i0.ɵɵadvance(6);
@@ -162,15 +169,15 @@ function FeaturePipelinesResourceComponent_Conditional_16_For_2_Template(rf, ctx
162
169
  i0.ɵɵadvance(6);
163
170
  i0.ɵɵtextInterpolate1(" ", ctx_r0.TimeAgo(pipeline_r4.LastRunAt), " ");
164
171
  i0.ɵɵadvance();
165
- i0.ɵɵconditional(pipeline_r4.LastRunProcessed != null ? 30 : -1);
172
+ i0.ɵɵconditional(pipeline_r4.LastRunProcessed != null ? 33 : -1);
166
173
  i0.ɵɵadvance(2);
167
174
  i0.ɵɵproperty("disabled", !pipeline_r4.OnDemandEnabled || pipeline_r4.Status !== "Active" || ctx_r0.IsRunning(pipeline_r4.ID));
168
175
  i0.ɵɵadvance();
169
- i0.ɵɵconditional(ctx_r0.IsRunning(pipeline_r4.ID) ? 33 : 34);
176
+ i0.ɵɵconditional(ctx_r0.IsRunning(pipeline_r4.ID) ? 36 : 37);
170
177
  } }
171
178
  function FeaturePipelinesResourceComponent_Conditional_16_Template(rf, ctx) { if (rf & 1) {
172
179
  i0.ɵɵelementStart(0, "div", 14);
173
- i0.ɵɵrepeaterCreate(1, FeaturePipelinesResourceComponent_Conditional_16_For_2_Template, 38, 17, "div", 17, _forTrack0);
180
+ i0.ɵɵrepeaterCreate(1, FeaturePipelinesResourceComponent_Conditional_16_For_2_Template, 41, 20, "div", 17, _forTrack0);
174
181
  i0.ɵɵelementEnd();
175
182
  } if (rf & 2) {
176
183
  const ctx_r0 = i0.ɵɵnextContext();
@@ -502,7 +509,7 @@ let FeaturePipelinesResourceComponent = class FeaturePipelinesResourceComponent
502
509
  ]);
503
510
  }
504
511
  static ɵfac = /*@__PURE__*/ (() => { let ɵFeaturePipelinesResourceComponent_BaseFactory; return function FeaturePipelinesResourceComponent_Factory(__ngFactoryType__) { return (ɵFeaturePipelinesResourceComponent_BaseFactory || (ɵFeaturePipelinesResourceComponent_BaseFactory = i0.ɵɵgetInheritedFactory(FeaturePipelinesResourceComponent)))(__ngFactoryType__ || FeaturePipelinesResourceComponent); }; })();
505
- static ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: FeaturePipelinesResourceComponent, selectors: [["app-feature-pipelines-resource"]], standalone: false, features: [i0.ɵɵInheritDefinitionFeature], decls: 17, vars: 5, consts: [["Title", "Feature Pipelines", "Icon", "fa-solid fa-diagram-project", "Subtitle", "Derive feature attributes from your data and persist them back to entities"], ["meta", ""], ["Label", "last run failed", "Variant", "error", 3, "Count"], ["Label", "pipelines", 3, "Count", "Total"], ["actions", ""], [3, "Clicked", "Loading"], ["mjButton", "", "variant", "primary", "size", "sm", 3, "click"], [1, "fa-solid", "fa-plus"], ["toolbar", ""], ["Placeholder", "Search by name, entity, or attribute\u2026", 3, "ValueChange", "Value"], [3, "Flex"], [1, "fp-loading"], ["Icon", "fa-solid fa-diagram-project", "Title", "No feature pipelines yet", "Message", "A Feature Pipeline derives an attribute per record (e.g. an LLM summary or score) and writes it back to your data \u2014 reusable analytics, independent of any model.", "ActionText", "New Pipeline", "ActionIcon", "fa-solid fa-plus"], ["Variant", "no-results", "Icon", "fa-solid fa-magnifying-glass", "Title", "No matching pipelines", "Message", "No feature pipelines match your search."], [1, "fp-grid"], ["text", "Loading feature pipelines\u2026", "size", "medium"], ["Icon", "fa-solid fa-diagram-project", "Title", "No feature pipelines yet", "Message", "A Feature Pipeline derives an attribute per record (e.g. an LLM summary or score) and writes it back to your data \u2014 reusable analytics, independent of any model.", "ActionText", "New Pipeline", "ActionIcon", "fa-solid fa-plus", 3, "Action"], [1, "fp-card", 3, "fp-card--disabled"], [1, "fp-card"], [1, "fp-card__head"], [1, "fp-card__icon"], [1, "fp-card__title"], [3, "title"], [1, "fp-card__worktype"], [1, "fp-status"], [1, "fp-card__desc", 3, "title"], [1, "fp-card__meta"], [1, "fp-meta-row"], [1, "fp-meta-row__k"], [1, "fa-solid", "fa-table"], [1, "fp-meta-row__v"], [1, "fa-solid", "fa-pen-to-square"], [1, "fa-solid", "fa-clock-rotate-left"], [1, "fp-meta-row__sub"], [1, "fp-card__actions"], ["mjButton", "", "variant", "primary", "size", "sm", 3, "click", "disabled"], ["mjButton", "", "variant", "secondary", "size", "sm", 3, "click"], [1, "fa-solid", "fa-eye"], [1, "fa-solid", "fa-spinner", "fa-spin"], [1, "fa-solid", "fa-play"]], template: function FeaturePipelinesResourceComponent_Template(rf, ctx) { if (rf & 1) {
512
+ static ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: FeaturePipelinesResourceComponent, selectors: [["app-feature-pipelines-resource"]], standalone: false, features: [i0.ɵɵInheritDefinitionFeature], decls: 17, vars: 5, consts: [["Title", "Feature Pipelines", "Icon", "fa-solid fa-diagram-project", "Subtitle", "Derive feature attributes from your data and persist them back to entities"], ["meta", ""], ["Label", "last run failed", "Variant", "error", 3, "Count"], ["Label", "pipelines", 3, "Count", "Total"], ["actions", ""], [3, "Clicked", "Loading"], ["mjButton", "", "variant", "primary", "size", "sm", 3, "click"], [1, "fa-solid", "fa-plus"], ["toolbar", ""], ["Placeholder", "Search by name, entity, or attribute\u2026", 3, "ValueChange", "Value"], [3, "Flex"], [1, "fp-loading"], ["Icon", "fa-solid fa-diagram-project", "Title", "No feature pipelines yet", "Message", "A Feature Pipeline derives an attribute per record (e.g. an LLM summary or score) and writes it back to your data \u2014 reusable analytics, independent of any model.", "ActionText", "New Pipeline", "ActionIcon", "fa-solid fa-plus"], ["Variant", "no-results", "Icon", "fa-solid fa-magnifying-glass", "Title", "No matching pipelines", "Message", "No feature pipelines match your search."], [1, "fp-grid"], ["text", "Loading feature pipelines\u2026", "size", "medium"], ["Icon", "fa-solid fa-diagram-project", "Title", "No feature pipelines yet", "Message", "A Feature Pipeline derives an attribute per record (e.g. an LLM summary or score) and writes it back to your data \u2014 reusable analytics, independent of any model.", "ActionText", "New Pipeline", "ActionIcon", "fa-solid fa-plus", 3, "Action"], [1, "fp-card", 3, "fp-card--disabled"], [1, "fp-card"], [1, "fp-card__head"], [1, "fp-card__icon"], [1, "fp-card__title"], [3, "title"], [1, "fp-card__subhead"], [1, "fp-card__worktype"], [1, "fp-type-badge"], [1, "fp-status"], [1, "fp-card__desc", 3, "title"], [1, "fp-card__meta"], [1, "fp-meta-row"], [1, "fp-meta-row__k"], [1, "fa-solid", "fa-table"], [1, "fp-meta-row__v"], [1, "fa-solid", "fa-pen-to-square"], [1, "fa-solid", "fa-clock-rotate-left"], [1, "fp-meta-row__sub"], [1, "fp-card__actions"], ["mjButton", "", "variant", "primary", "size", "sm", 3, "click", "disabled"], ["mjButton", "", "variant", "secondary", "size", "sm", 3, "click"], [1, "fa-solid", "fa-eye"], [1, "fa-solid", "fa-spinner", "fa-spin"], [1, "fa-solid", "fa-play"]], template: function FeaturePipelinesResourceComponent_Template(rf, ctx) { if (rf & 1) {
506
513
  i0.ɵɵelementStart(0, "mj-page-layout")(1, "mj-page-header", 0)(2, "div", 1);
507
514
  i0.ɵɵconditionalCreate(3, FeaturePipelinesResourceComponent_Conditional_3_Template, 1, 1, "mj-stat-badge", 2)(4, FeaturePipelinesResourceComponent_Conditional_4_Template, 1, 2, "mj-stat-badge", 3);
508
515
  i0.ɵɵelementEnd();
@@ -531,7 +538,7 @@ let FeaturePipelinesResourceComponent = class FeaturePipelinesResourceComponent
531
538
  i0.ɵɵproperty("Flex", true);
532
539
  i0.ɵɵadvance();
533
540
  i0.ɵɵconditional(ctx.IsLoading ? 13 : ctx.AllPipelines.length === 0 ? 14 : ctx.FilteredPipelines.length === 0 ? 15 : 16);
534
- } }, dependencies: [i1.LoadingComponent, i2.MJButtonDirective, i2.MJPageHeaderComponent, i2.MJPageLayoutComponent, i2.MJPageBodyComponent, i2.MJPageSearchComponent, i2.MJStatBadgeComponent, i2.MJRefreshButtonComponent, i2.MJEmptyStateComponent], styles: ["\n\n\n\n\n\n\n\n\n\n\n.fp-loading[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n justify-content: center;\n min-height: 240px;\n width: 100%;\n}\n\n.fp-grid[_ngcontent-%COMP%] {\n display: grid;\n grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));\n gap: 16px;\n padding: 4px;\n align-content: start;\n}\n\n.fp-card[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n gap: 12px;\n padding: 16px;\n border: 1px solid var(--mj-border-default);\n border-radius: var(--mj-radius-lg, 10px);\n background: var(--mj-bg-surface-card);\n transition: border-color 0.15s ease, box-shadow 0.15s ease;\n}\n\n.fp-card[_ngcontent-%COMP%]:hover {\n border-color: var(--mj-border-strong);\n box-shadow: 0 2px 8px color-mix(in srgb, var(--mj-brand-primary) 8%, transparent);\n}\n\n.fp-card--disabled[_ngcontent-%COMP%] {\n opacity: 0.65;\n}\n\n.fp-card__head[_ngcontent-%COMP%] {\n display: flex;\n align-items: flex-start;\n gap: 12px;\n}\n\n.fp-card__icon[_ngcontent-%COMP%] {\n flex: 0 0 auto;\n width: 36px;\n height: 36px;\n display: flex;\n align-items: center;\n justify-content: center;\n border-radius: var(--mj-radius-md, 8px);\n background: color-mix(in srgb, var(--mj-brand-primary) 12%, var(--mj-bg-surface));\n color: var(--mj-brand-primary);\n font-size: 16px;\n}\n\n.fp-card__title[_ngcontent-%COMP%] {\n flex: 1 1 auto;\n min-width: 0;\n}\n\n.fp-card__title[_ngcontent-%COMP%] h3[_ngcontent-%COMP%] {\n margin: 0;\n font-size: 15px;\n font-weight: 600;\n color: var(--mj-text-primary);\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.fp-card__worktype[_ngcontent-%COMP%] {\n font-size: 11px;\n font-weight: 600;\n text-transform: uppercase;\n letter-spacing: 0.04em;\n color: var(--mj-text-muted);\n}\n\n.fp-card__desc[_ngcontent-%COMP%] {\n margin: 0;\n font-size: 13px;\n line-height: 1.4;\n color: var(--mj-text-secondary);\n display: -webkit-box;\n -webkit-line-clamp: 2;\n line-clamp: 2;\n -webkit-box-orient: vertical;\n overflow: hidden;\n}\n\n.fp-card__meta[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n gap: 6px;\n padding: 10px 0;\n border-top: 1px solid var(--mj-border-subtle);\n border-bottom: 1px solid var(--mj-border-subtle);\n}\n\n.fp-meta-row[_ngcontent-%COMP%] {\n display: flex;\n align-items: baseline;\n justify-content: space-between;\n gap: 12px;\n font-size: 13px;\n}\n\n.fp-meta-row__k[_ngcontent-%COMP%] {\n flex: 0 0 auto;\n color: var(--mj-text-muted);\n}\n\n.fp-meta-row__k[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n width: 14px;\n margin-right: 4px;\n}\n\n.fp-meta-row__v[_ngcontent-%COMP%] {\n flex: 1 1 auto;\n text-align: right;\n color: var(--mj-text-primary);\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.fp-meta-row__sub[_ngcontent-%COMP%] {\n color: var(--mj-text-muted);\n font-size: 12px;\n}\n\n.fp-card__actions[_ngcontent-%COMP%] {\n display: flex;\n gap: 8px;\n margin-top: auto;\n}\n\n\n.fp-status[_ngcontent-%COMP%] {\n flex: 0 0 auto;\n align-self: flex-start;\n padding: 2px 10px;\n border-radius: var(--mj-radius-full, 999px);\n font-size: 11px;\n font-weight: 600;\n white-space: nowrap;\n}\n\n.fp-status.status--never[_ngcontent-%COMP%] {\n background: var(--mj-bg-surface-sunken);\n color: var(--mj-text-muted);\n}\n\n.fp-status.status--running[_ngcontent-%COMP%] {\n background: var(--mj-status-info-bg);\n color: var(--mj-status-info-text);\n}\n\n.fp-status.status--completed[_ngcontent-%COMP%] {\n background: var(--mj-status-success-bg);\n color: var(--mj-status-success-text);\n}\n\n.fp-status.status--failed[_ngcontent-%COMP%] {\n background: var(--mj-status-error-bg);\n color: var(--mj-status-error-text);\n}\n\n.fp-status.status--cancelled[_ngcontent-%COMP%] {\n background: var(--mj-status-warning-bg);\n color: var(--mj-status-warning-text);\n}"] });
541
+ } }, dependencies: [i1.LoadingComponent, i2.MJButtonDirective, i2.MJPageHeaderComponent, i2.MJPageLayoutComponent, i2.MJPageBodyComponent, i2.MJPageSearchComponent, i2.MJStatBadgeComponent, i2.MJRefreshButtonComponent, i2.MJEmptyStateComponent], styles: ["\n\n\n\n\n\n\n\n\n\n\n.fp-loading[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n justify-content: center;\n min-height: 240px;\n width: 100%;\n}\n\n.fp-grid[_ngcontent-%COMP%] {\n display: grid;\n grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));\n gap: 16px;\n padding: 4px;\n align-content: start;\n}\n\n.fp-card[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n gap: 12px;\n padding: 16px;\n border: 1px solid var(--mj-border-default);\n border-radius: var(--mj-radius-lg, 10px);\n background: var(--mj-bg-surface-card);\n transition: border-color 0.15s ease, box-shadow 0.15s ease;\n}\n\n.fp-card[_ngcontent-%COMP%]:hover {\n border-color: var(--mj-border-strong);\n box-shadow: 0 2px 8px color-mix(in srgb, var(--mj-brand-primary) 8%, transparent);\n}\n\n.fp-card--disabled[_ngcontent-%COMP%] {\n opacity: 0.65;\n}\n\n.fp-card__head[_ngcontent-%COMP%] {\n display: flex;\n align-items: flex-start;\n gap: 12px;\n}\n\n.fp-card__icon[_ngcontent-%COMP%] {\n flex: 0 0 auto;\n width: 36px;\n height: 36px;\n display: flex;\n align-items: center;\n justify-content: center;\n border-radius: var(--mj-radius-md, 8px);\n background: color-mix(in srgb, var(--mj-brand-primary) 12%, var(--mj-bg-surface));\n color: var(--mj-brand-primary);\n font-size: 16px;\n}\n\n.fp-card__title[_ngcontent-%COMP%] {\n flex: 1 1 auto;\n min-width: 0;\n}\n\n.fp-card__title[_ngcontent-%COMP%] h3[_ngcontent-%COMP%] {\n margin: 0;\n font-size: 15px;\n font-weight: 600;\n color: var(--mj-text-primary);\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.fp-card__worktype[_ngcontent-%COMP%] {\n font-size: 11px;\n font-weight: 600;\n text-transform: uppercase;\n letter-spacing: 0.04em;\n color: var(--mj-text-muted);\n}\n\n.fp-card__subhead[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 6px;\n margin-top: 2px;\n}\n\n.fp-type-badge[_ngcontent-%COMP%] {\n display: inline-flex;\n align-items: center;\n padding: 1px 6px;\n border-radius: var(--mj-radius-sm, 4px);\n font-size: 10px;\n font-weight: 600;\n letter-spacing: 0.02em;\n background: var(--mj-bg-surface-sunken);\n color: var(--mj-text-secondary);\n border: 1px solid var(--mj-border-default);\n}\n\n.fp-type-badge--decision[_ngcontent-%COMP%] {\n background: var(--mj-status-info-bg);\n color: var(--mj-status-info-text);\n border-color: var(--mj-status-info-border);\n}\n\n.fp-card__desc[_ngcontent-%COMP%] {\n margin: 0;\n font-size: 13px;\n line-height: 1.4;\n color: var(--mj-text-secondary);\n display: -webkit-box;\n -webkit-line-clamp: 2;\n line-clamp: 2;\n -webkit-box-orient: vertical;\n overflow: hidden;\n}\n\n.fp-card__meta[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n gap: 6px;\n padding: 10px 0;\n border-top: 1px solid var(--mj-border-subtle);\n border-bottom: 1px solid var(--mj-border-subtle);\n}\n\n.fp-meta-row[_ngcontent-%COMP%] {\n display: flex;\n align-items: baseline;\n justify-content: space-between;\n gap: 12px;\n font-size: 13px;\n}\n\n.fp-meta-row__k[_ngcontent-%COMP%] {\n flex: 0 0 auto;\n color: var(--mj-text-muted);\n}\n\n.fp-meta-row__k[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n width: 14px;\n margin-right: 4px;\n}\n\n.fp-meta-row__v[_ngcontent-%COMP%] {\n flex: 1 1 auto;\n text-align: right;\n color: var(--mj-text-primary);\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.fp-meta-row__sub[_ngcontent-%COMP%] {\n color: var(--mj-text-muted);\n font-size: 12px;\n}\n\n.fp-card__actions[_ngcontent-%COMP%] {\n display: flex;\n gap: 8px;\n margin-top: auto;\n}\n\n\n.fp-status[_ngcontent-%COMP%] {\n flex: 0 0 auto;\n align-self: flex-start;\n padding: 2px 10px;\n border-radius: var(--mj-radius-full, 999px);\n font-size: 11px;\n font-weight: 600;\n white-space: nowrap;\n}\n\n.fp-status.status--never[_ngcontent-%COMP%] {\n background: var(--mj-bg-surface-sunken);\n color: var(--mj-text-muted);\n}\n\n.fp-status.status--running[_ngcontent-%COMP%] {\n background: var(--mj-status-info-bg);\n color: var(--mj-status-info-text);\n}\n\n.fp-status.status--completed[_ngcontent-%COMP%] {\n background: var(--mj-status-success-bg);\n color: var(--mj-status-success-text);\n}\n\n.fp-status.status--failed[_ngcontent-%COMP%] {\n background: var(--mj-status-error-bg);\n color: var(--mj-status-error-text);\n}\n\n.fp-status.status--cancelled[_ngcontent-%COMP%] {\n background: var(--mj-status-warning-bg);\n color: var(--mj-status-warning-text);\n}"] });
535
542
  };
536
543
  FeaturePipelinesResourceComponent = __decorate([
537
544
  RegisterClass(BaseResourceComponent, 'FeaturePipelinesResource')
@@ -539,7 +546,7 @@ FeaturePipelinesResourceComponent = __decorate([
539
546
  export { FeaturePipelinesResourceComponent };
540
547
  (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(FeaturePipelinesResourceComponent, [{
541
548
  type: Component,
542
- args: [{ standalone: false, selector: 'app-feature-pipelines-resource', template: "<mj-page-layout>\n <mj-page-header\n Title=\"Feature Pipelines\"\n Icon=\"fa-solid fa-diagram-project\"\n Subtitle=\"Derive feature attributes from your data and persist them back to entities\">\n <div meta>\n @if (FailedCount > 0) {\n <mj-stat-badge [Count]=\"FailedCount\" Label=\"last run failed\" Variant=\"error\"></mj-stat-badge>\n } @else {\n <mj-stat-badge\n [Count]=\"FilteredPipelines.length\"\n [Total]=\"AllPipelines.length\"\n Label=\"pipelines\">\n </mj-stat-badge>\n }\n </div>\n <div actions>\n <mj-refresh-button [Loading]=\"IsLoading\" (Clicked)=\"Refresh()\"></mj-refresh-button>\n <button mjButton variant=\"primary\" size=\"sm\" (click)=\"OnNewPipeline()\">\n <i class=\"fa-solid fa-plus\"></i> New Pipeline\n </button>\n </div>\n <div toolbar>\n <mj-page-search\n [Value]=\"SearchQuery\"\n Placeholder=\"Search by name, entity, or attribute\u2026\"\n (ValueChange)=\"SearchQuery = $event; OnSearchChanged()\">\n </mj-page-search>\n </div>\n </mj-page-header>\n\n <mj-page-body [Flex]=\"true\">\n @if (IsLoading) {\n <div class=\"fp-loading\">\n <mj-loading text=\"Loading feature pipelines\u2026\" size=\"medium\"></mj-loading>\n </div>\n } @else if (AllPipelines.length === 0) {\n <mj-empty-state\n Icon=\"fa-solid fa-diagram-project\"\n Title=\"No feature pipelines yet\"\n Message=\"A Feature Pipeline derives an attribute per record (e.g. an LLM summary or score) and writes it back to your data \u2014 reusable analytics, independent of any model.\"\n ActionText=\"New Pipeline\"\n ActionIcon=\"fa-solid fa-plus\"\n (Action)=\"OnNewPipeline()\">\n </mj-empty-state>\n } @else if (FilteredPipelines.length === 0) {\n <mj-empty-state\n Variant=\"no-results\"\n Icon=\"fa-solid fa-magnifying-glass\"\n Title=\"No matching pipelines\"\n Message=\"No feature pipelines match your search.\">\n </mj-empty-state>\n } @else {\n <div class=\"fp-grid\">\n @for (pipeline of FilteredPipelines; track pipeline.ID) {\n <div class=\"fp-card\" [class.fp-card--disabled]=\"pipeline.Status !== 'Active'\">\n <div class=\"fp-card__head\">\n <i class=\"fp-card__icon\" [class]=\"WorkTypeIcon(pipeline.WorkType)\"></i>\n <div class=\"fp-card__title\">\n <h3 [title]=\"pipeline.Name\">{{ pipeline.Name }}</h3>\n <span class=\"fp-card__worktype\">{{ pipeline.WorkType }}</span>\n </div>\n <span class=\"fp-status\" [class]=\"RunStatusClass(pipeline.LastRunStatus)\">\n {{ RunStatusLabel(pipeline.LastRunStatus) }}\n </span>\n </div>\n\n @if (pipeline.Description) {\n <p class=\"fp-card__desc\" [title]=\"pipeline.Description\">{{ pipeline.Description }}</p>\n }\n\n <div class=\"fp-card__meta\">\n <div class=\"fp-meta-row\">\n <span class=\"fp-meta-row__k\"><i class=\"fa-solid fa-table\"></i> Target</span>\n <span class=\"fp-meta-row__v\">{{ pipeline.TargetEntity }}</span>\n </div>\n <div class=\"fp-meta-row\">\n <span class=\"fp-meta-row__k\"><i class=\"fa-solid fa-pen-to-square\"></i> Writes</span>\n <span class=\"fp-meta-row__v\">{{ pipeline.OutputAttribute || '\u2014' }}</span>\n </div>\n <div class=\"fp-meta-row\">\n <span class=\"fp-meta-row__k\"><i class=\"fa-solid fa-clock-rotate-left\"></i> Last run</span>\n <span class=\"fp-meta-row__v\">\n {{ TimeAgo(pipeline.LastRunAt) }}\n @if (pipeline.LastRunProcessed != null) {\n <span class=\"fp-meta-row__sub\">\u00B7 {{ pipeline.LastRunSuccess }}/{{ pipeline.LastRunProcessed }} written</span>\n }\n </span>\n </div>\n </div>\n\n <div class=\"fp-card__actions\">\n <button\n mjButton\n variant=\"primary\"\n size=\"sm\"\n [disabled]=\"!pipeline.OnDemandEnabled || pipeline.Status !== 'Active' || IsRunning(pipeline.ID)\"\n (click)=\"OnRunPipeline(pipeline)\">\n @if (IsRunning(pipeline.ID)) {\n <i class=\"fa-solid fa-spinner fa-spin\"></i> Running\u2026\n } @else {\n <i class=\"fa-solid fa-play\"></i> Run\n }\n </button>\n <button mjButton variant=\"secondary\" size=\"sm\" (click)=\"OnViewPipeline(pipeline)\">\n <i class=\"fa-solid fa-eye\"></i> View\n </button>\n </div>\n </div>\n }\n </div>\n }\n </mj-page-body>\n</mj-page-layout>\n", styles: ["/**\n * Feature Pipelines (Knowledge Hub) \u2014 card-grid layout.\n * All colors use semantic design tokens (dark-mode + white-label safe).\n *\n * NOTE: this package builds with bare `ngc` (no Sass step) \u2014 component styles\n * are embedded RAW into the compiled JS. Keep this file FLAT, plain CSS.\n * Sass nesting / BEM selector concatenation would be silently dropped by the\n * browser and the rules would be DEAD. See .github/scripts/check-ngc-scss.sh\n * (CI gate) and MemberJunction/MJ#3105.\n */\n\n.fp-loading {\n display: flex;\n align-items: center;\n justify-content: center;\n min-height: 240px;\n width: 100%;\n}\n\n.fp-grid {\n display: grid;\n grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));\n gap: 16px;\n padding: 4px;\n align-content: start;\n}\n\n.fp-card {\n display: flex;\n flex-direction: column;\n gap: 12px;\n padding: 16px;\n border: 1px solid var(--mj-border-default);\n border-radius: var(--mj-radius-lg, 10px);\n background: var(--mj-bg-surface-card);\n transition: border-color 0.15s ease, box-shadow 0.15s ease;\n}\n\n.fp-card:hover {\n border-color: var(--mj-border-strong);\n box-shadow: 0 2px 8px color-mix(in srgb, var(--mj-brand-primary) 8%, transparent);\n}\n\n.fp-card--disabled {\n opacity: 0.65;\n}\n\n.fp-card__head {\n display: flex;\n align-items: flex-start;\n gap: 12px;\n}\n\n.fp-card__icon {\n flex: 0 0 auto;\n width: 36px;\n height: 36px;\n display: flex;\n align-items: center;\n justify-content: center;\n border-radius: var(--mj-radius-md, 8px);\n background: color-mix(in srgb, var(--mj-brand-primary) 12%, var(--mj-bg-surface));\n color: var(--mj-brand-primary);\n font-size: 16px;\n}\n\n.fp-card__title {\n flex: 1 1 auto;\n min-width: 0;\n}\n\n.fp-card__title h3 {\n margin: 0;\n font-size: 15px;\n font-weight: 600;\n color: var(--mj-text-primary);\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.fp-card__worktype {\n font-size: 11px;\n font-weight: 600;\n text-transform: uppercase;\n letter-spacing: 0.04em;\n color: var(--mj-text-muted);\n}\n\n.fp-card__desc {\n margin: 0;\n font-size: 13px;\n line-height: 1.4;\n color: var(--mj-text-secondary);\n display: -webkit-box;\n -webkit-line-clamp: 2;\n line-clamp: 2;\n -webkit-box-orient: vertical;\n overflow: hidden;\n}\n\n.fp-card__meta {\n display: flex;\n flex-direction: column;\n gap: 6px;\n padding: 10px 0;\n border-top: 1px solid var(--mj-border-subtle);\n border-bottom: 1px solid var(--mj-border-subtle);\n}\n\n.fp-meta-row {\n display: flex;\n align-items: baseline;\n justify-content: space-between;\n gap: 12px;\n font-size: 13px;\n}\n\n.fp-meta-row__k {\n flex: 0 0 auto;\n color: var(--mj-text-muted);\n}\n\n.fp-meta-row__k i {\n width: 14px;\n margin-right: 4px;\n}\n\n.fp-meta-row__v {\n flex: 1 1 auto;\n text-align: right;\n color: var(--mj-text-primary);\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.fp-meta-row__sub {\n color: var(--mj-text-muted);\n font-size: 12px;\n}\n\n.fp-card__actions {\n display: flex;\n gap: 8px;\n margin-top: auto;\n}\n\n/* Run-status pill \u2014 semantic status tokens */\n.fp-status {\n flex: 0 0 auto;\n align-self: flex-start;\n padding: 2px 10px;\n border-radius: var(--mj-radius-full, 999px);\n font-size: 11px;\n font-weight: 600;\n white-space: nowrap;\n}\n\n.fp-status.status--never {\n background: var(--mj-bg-surface-sunken);\n color: var(--mj-text-muted);\n}\n\n.fp-status.status--running {\n background: var(--mj-status-info-bg);\n color: var(--mj-status-info-text);\n}\n\n.fp-status.status--completed {\n background: var(--mj-status-success-bg);\n color: var(--mj-status-success-text);\n}\n\n.fp-status.status--failed {\n background: var(--mj-status-error-bg);\n color: var(--mj-status-error-text);\n}\n\n.fp-status.status--cancelled {\n background: var(--mj-status-warning-bg);\n color: var(--mj-status-warning-text);\n}\n"] }]
549
+ args: [{ standalone: false, selector: 'app-feature-pipelines-resource', template: "<mj-page-layout>\n <mj-page-header\n Title=\"Feature Pipelines\"\n Icon=\"fa-solid fa-diagram-project\"\n Subtitle=\"Derive feature attributes from your data and persist them back to entities\">\n <div meta>\n @if (FailedCount > 0) {\n <mj-stat-badge [Count]=\"FailedCount\" Label=\"last run failed\" Variant=\"error\"></mj-stat-badge>\n } @else {\n <mj-stat-badge\n [Count]=\"FilteredPipelines.length\"\n [Total]=\"AllPipelines.length\"\n Label=\"pipelines\">\n </mj-stat-badge>\n }\n </div>\n <div actions>\n <mj-refresh-button [Loading]=\"IsLoading\" (Clicked)=\"Refresh()\"></mj-refresh-button>\n <button mjButton variant=\"primary\" size=\"sm\" (click)=\"OnNewPipeline()\">\n <i class=\"fa-solid fa-plus\"></i> New Pipeline\n </button>\n </div>\n <div toolbar>\n <mj-page-search\n [Value]=\"SearchQuery\"\n Placeholder=\"Search by name, entity, or attribute\u2026\"\n (ValueChange)=\"SearchQuery = $event; OnSearchChanged()\">\n </mj-page-search>\n </div>\n </mj-page-header>\n\n <mj-page-body [Flex]=\"true\">\n @if (IsLoading) {\n <div class=\"fp-loading\">\n <mj-loading text=\"Loading feature pipelines\u2026\" size=\"medium\"></mj-loading>\n </div>\n } @else if (AllPipelines.length === 0) {\n <mj-empty-state\n Icon=\"fa-solid fa-diagram-project\"\n Title=\"No feature pipelines yet\"\n Message=\"A Feature Pipeline derives an attribute per record (e.g. an LLM summary or score) and writes it back to your data \u2014 reusable analytics, independent of any model.\"\n ActionText=\"New Pipeline\"\n ActionIcon=\"fa-solid fa-plus\"\n (Action)=\"OnNewPipeline()\">\n </mj-empty-state>\n } @else if (FilteredPipelines.length === 0) {\n <mj-empty-state\n Variant=\"no-results\"\n Icon=\"fa-solid fa-magnifying-glass\"\n Title=\"No matching pipelines\"\n Message=\"No feature pipelines match your search.\">\n </mj-empty-state>\n } @else {\n <div class=\"fp-grid\">\n @for (pipeline of FilteredPipelines; track pipeline.ID) {\n <div class=\"fp-card\" [class.fp-card--disabled]=\"pipeline.Status !== 'Active'\">\n <div class=\"fp-card__head\">\n <i class=\"fp-card__icon\" [class]=\"WorkTypeIcon(pipeline.WorkType)\"></i>\n <div class=\"fp-card__title\">\n <h3 [title]=\"pipeline.Name\">{{ pipeline.Name }}</h3>\n <div class=\"fp-card__subhead\">\n <span class=\"fp-card__worktype\">{{ pipeline.WorkType }}</span>\n <span class=\"fp-type-badge\" [class.fp-type-badge--decision]=\"pipeline.PipelineType === 'Decision'\">{{ pipeline.PipelineType }}</span>\n </div>\n </div>\n <span class=\"fp-status\" [class]=\"RunStatusClass(pipeline.LastRunStatus)\">\n {{ RunStatusLabel(pipeline.LastRunStatus) }}\n </span>\n </div>\n\n @if (pipeline.Description) {\n <p class=\"fp-card__desc\" [title]=\"pipeline.Description\">{{ pipeline.Description }}</p>\n }\n\n <div class=\"fp-card__meta\">\n <div class=\"fp-meta-row\">\n <span class=\"fp-meta-row__k\"><i class=\"fa-solid fa-table\"></i> Target</span>\n <span class=\"fp-meta-row__v\">{{ pipeline.TargetEntity }}</span>\n </div>\n <div class=\"fp-meta-row\">\n <span class=\"fp-meta-row__k\"><i class=\"fa-solid fa-pen-to-square\"></i> Writes</span>\n <span class=\"fp-meta-row__v\">{{ pipeline.OutputAttribute || '\u2014' }}</span>\n </div>\n <div class=\"fp-meta-row\">\n <span class=\"fp-meta-row__k\"><i class=\"fa-solid fa-clock-rotate-left\"></i> Last run</span>\n <span class=\"fp-meta-row__v\">\n {{ TimeAgo(pipeline.LastRunAt) }}\n @if (pipeline.LastRunProcessed != null) {\n <span class=\"fp-meta-row__sub\">\u00B7 {{ pipeline.LastRunSuccess }}/{{ pipeline.LastRunProcessed }} written</span>\n }\n </span>\n </div>\n </div>\n\n <div class=\"fp-card__actions\">\n <button\n mjButton\n variant=\"primary\"\n size=\"sm\"\n [disabled]=\"!pipeline.OnDemandEnabled || pipeline.Status !== 'Active' || IsRunning(pipeline.ID)\"\n (click)=\"OnRunPipeline(pipeline)\">\n @if (IsRunning(pipeline.ID)) {\n <i class=\"fa-solid fa-spinner fa-spin\"></i> Running\u2026\n } @else {\n <i class=\"fa-solid fa-play\"></i> Run\n }\n </button>\n <button mjButton variant=\"secondary\" size=\"sm\" (click)=\"OnViewPipeline(pipeline)\">\n <i class=\"fa-solid fa-eye\"></i> View\n </button>\n </div>\n </div>\n }\n </div>\n }\n </mj-page-body>\n</mj-page-layout>\n", styles: ["/**\n * Feature Pipelines (Knowledge Hub) \u2014 card-grid layout.\n * All colors use semantic design tokens (dark-mode + white-label safe).\n *\n * NOTE: this package builds with bare `ngc` (no Sass step) \u2014 component styles\n * are embedded RAW into the compiled JS. Keep this file FLAT, plain CSS.\n * Sass nesting / BEM selector concatenation would be silently dropped by the\n * browser and the rules would be DEAD. See .github/scripts/check-ngc-scss.sh\n * (CI gate) and MemberJunction/MJ#3105.\n */\n\n.fp-loading {\n display: flex;\n align-items: center;\n justify-content: center;\n min-height: 240px;\n width: 100%;\n}\n\n.fp-grid {\n display: grid;\n grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));\n gap: 16px;\n padding: 4px;\n align-content: start;\n}\n\n.fp-card {\n display: flex;\n flex-direction: column;\n gap: 12px;\n padding: 16px;\n border: 1px solid var(--mj-border-default);\n border-radius: var(--mj-radius-lg, 10px);\n background: var(--mj-bg-surface-card);\n transition: border-color 0.15s ease, box-shadow 0.15s ease;\n}\n\n.fp-card:hover {\n border-color: var(--mj-border-strong);\n box-shadow: 0 2px 8px color-mix(in srgb, var(--mj-brand-primary) 8%, transparent);\n}\n\n.fp-card--disabled {\n opacity: 0.65;\n}\n\n.fp-card__head {\n display: flex;\n align-items: flex-start;\n gap: 12px;\n}\n\n.fp-card__icon {\n flex: 0 0 auto;\n width: 36px;\n height: 36px;\n display: flex;\n align-items: center;\n justify-content: center;\n border-radius: var(--mj-radius-md, 8px);\n background: color-mix(in srgb, var(--mj-brand-primary) 12%, var(--mj-bg-surface));\n color: var(--mj-brand-primary);\n font-size: 16px;\n}\n\n.fp-card__title {\n flex: 1 1 auto;\n min-width: 0;\n}\n\n.fp-card__title h3 {\n margin: 0;\n font-size: 15px;\n font-weight: 600;\n color: var(--mj-text-primary);\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.fp-card__worktype {\n font-size: 11px;\n font-weight: 600;\n text-transform: uppercase;\n letter-spacing: 0.04em;\n color: var(--mj-text-muted);\n}\n\n.fp-card__subhead {\n display: flex;\n align-items: center;\n gap: 6px;\n margin-top: 2px;\n}\n\n.fp-type-badge {\n display: inline-flex;\n align-items: center;\n padding: 1px 6px;\n border-radius: var(--mj-radius-sm, 4px);\n font-size: 10px;\n font-weight: 600;\n letter-spacing: 0.02em;\n background: var(--mj-bg-surface-sunken);\n color: var(--mj-text-secondary);\n border: 1px solid var(--mj-border-default);\n}\n\n.fp-type-badge--decision {\n background: var(--mj-status-info-bg);\n color: var(--mj-status-info-text);\n border-color: var(--mj-status-info-border);\n}\n\n.fp-card__desc {\n margin: 0;\n font-size: 13px;\n line-height: 1.4;\n color: var(--mj-text-secondary);\n display: -webkit-box;\n -webkit-line-clamp: 2;\n line-clamp: 2;\n -webkit-box-orient: vertical;\n overflow: hidden;\n}\n\n.fp-card__meta {\n display: flex;\n flex-direction: column;\n gap: 6px;\n padding: 10px 0;\n border-top: 1px solid var(--mj-border-subtle);\n border-bottom: 1px solid var(--mj-border-subtle);\n}\n\n.fp-meta-row {\n display: flex;\n align-items: baseline;\n justify-content: space-between;\n gap: 12px;\n font-size: 13px;\n}\n\n.fp-meta-row__k {\n flex: 0 0 auto;\n color: var(--mj-text-muted);\n}\n\n.fp-meta-row__k i {\n width: 14px;\n margin-right: 4px;\n}\n\n.fp-meta-row__v {\n flex: 1 1 auto;\n text-align: right;\n color: var(--mj-text-primary);\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.fp-meta-row__sub {\n color: var(--mj-text-muted);\n font-size: 12px;\n}\n\n.fp-card__actions {\n display: flex;\n gap: 8px;\n margin-top: auto;\n}\n\n/* Run-status pill \u2014 semantic status tokens */\n.fp-status {\n flex: 0 0 auto;\n align-self: flex-start;\n padding: 2px 10px;\n border-radius: var(--mj-radius-full, 999px);\n font-size: 11px;\n font-weight: 600;\n white-space: nowrap;\n}\n\n.fp-status.status--never {\n background: var(--mj-bg-surface-sunken);\n color: var(--mj-text-muted);\n}\n\n.fp-status.status--running {\n background: var(--mj-status-info-bg);\n color: var(--mj-status-info-text);\n}\n\n.fp-status.status--completed {\n background: var(--mj-status-success-bg);\n color: var(--mj-status-success-text);\n}\n\n.fp-status.status--failed {\n background: var(--mj-status-error-bg);\n color: var(--mj-status-error-text);\n}\n\n.fp-status.status--cancelled {\n background: var(--mj-status-warning-bg);\n color: var(--mj-status-warning-text);\n}\n"] }]
543
550
  }], null, null); })();
544
551
  (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(FeaturePipelinesResourceComponent, { className: "FeaturePipelinesResourceComponent", filePath: "src/KnowledgeHub/components/feature-pipelines/feature-pipelines-resource.component.ts", lineNumber: 65 }); })();
545
552
  /** Tree-shaking prevention — keeps the component in the bundle. */