@memberjunction/ng-core-entity-forms 6.1.2 → 6.2.0-edge.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +22 -0
- package/dist/lib/custom/MLModels/ml-model-form.component.d.ts +27 -0
- package/dist/lib/custom/MLModels/ml-model-form.component.d.ts.map +1 -0
- package/dist/lib/custom/MLModels/ml-model-form.component.js +193 -0
- package/dist/lib/custom/MLModels/ml-model-form.component.js.map +1 -0
- package/dist/lib/custom/MLModels/ml-model-view-models.d.ts +44 -0
- package/dist/lib/custom/MLModels/ml-model-view-models.d.ts.map +1 -0
- package/dist/lib/custom/MLModels/ml-model-view-models.js +208 -0
- package/dist/lib/custom/MLModels/ml-model-view-models.js.map +1 -0
- package/dist/lib/custom/MLModels/ps-model-detail.component.d.ts +76 -0
- package/dist/lib/custom/MLModels/ps-model-detail.component.d.ts.map +1 -0
- package/dist/lib/custom/MLModels/ps-model-detail.component.js +693 -0
- package/dist/lib/custom/MLModels/ps-model-detail.component.js.map +1 -0
- package/dist/lib/custom/RecordProcesses/record-process-form.component.d.ts +27 -7
- package/dist/lib/custom/RecordProcesses/record-process-form.component.d.ts.map +1 -1
- package/dist/lib/custom/RecordProcesses/record-process-form.component.js +496 -19
- package/dist/lib/custom/RecordProcesses/record-process-form.component.js.map +1 -1
- package/dist/lib/custom/custom-forms.module.d.ts +32 -30
- package/dist/lib/custom/custom-forms.module.d.ts.map +1 -1
- package/dist/lib/custom/custom-forms.module.js +21 -4
- package/dist/lib/custom/custom-forms.module.js.map +1 -1
- package/dist/lib/generated/Entities/MJAIPrompt/mjaiprompt.form.component.d.ts.map +1 -1
- package/dist/lib/generated/Entities/MJAIPrompt/mjaiprompt.form.component.js +62 -26
- package/dist/lib/generated/Entities/MJAIPrompt/mjaiprompt.form.component.js.map +1 -1
- package/dist/lib/generated/Entities/MJCredential/mjcredential.form.component.d.ts.map +1 -1
- package/dist/lib/generated/Entities/MJCredential/mjcredential.form.component.js +33 -15
- package/dist/lib/generated/Entities/MJCredential/mjcredential.form.component.js.map +1 -1
- package/dist/lib/generated/Entities/MJEntity/mjentity.form.component.d.ts.map +1 -1
- package/dist/lib/generated/Entities/MJEntity/mjentity.form.component.js +100 -82
- package/dist/lib/generated/Entities/MJEntity/mjentity.form.component.js.map +1 -1
- package/dist/lib/generated/Entities/MJEntityField/mjentityfield.form.component.js +24 -20
- package/dist/lib/generated/Entities/MJEntityField/mjentityfield.form.component.js.map +1 -1
- package/dist/lib/generated/Entities/MJFeatureValue/mjfeaturevalue.form.component.d.ts +10 -0
- package/dist/lib/generated/Entities/MJFeatureValue/mjfeaturevalue.form.component.d.ts.map +1 -0
- package/dist/lib/generated/Entities/MJFeatureValue/mjfeaturevalue.form.component.js +122 -0
- package/dist/lib/generated/Entities/MJFeatureValue/mjfeaturevalue.form.component.js.map +1 -0
- package/dist/lib/generated/Entities/MJFeatureValueCache/mjfeaturevaluecache.form.component.d.ts +10 -0
- package/dist/lib/generated/Entities/MJFeatureValueCache/mjfeaturevaluecache.form.component.d.ts.map +1 -0
- package/dist/lib/generated/Entities/MJFeatureValueCache/mjfeaturevaluecache.form.component.js +125 -0
- package/dist/lib/generated/Entities/MJFeatureValueCache/mjfeaturevaluecache.form.component.js.map +1 -0
- package/dist/lib/generated/Entities/MJProcessRun/mjprocessrun.form.component.d.ts.map +1 -1
- package/dist/lib/generated/Entities/MJProcessRun/mjprocessrun.form.component.js +23 -5
- package/dist/lib/generated/Entities/MJProcessRun/mjprocessrun.form.component.js.map +1 -1
- package/dist/lib/generated/Entities/MJProcessRunDetail/mjprocessrundetail.form.component.d.ts.map +1 -1
- package/dist/lib/generated/Entities/MJProcessRunDetail/mjprocessrundetail.form.component.js +25 -6
- package/dist/lib/generated/Entities/MJProcessRunDetail/mjprocessrundetail.form.component.js.map +1 -1
- package/dist/lib/generated/Entities/MJQuery/mjquery.form.component.d.ts.map +1 -1
- package/dist/lib/generated/Entities/MJQuery/mjquery.form.component.js +11 -15
- package/dist/lib/generated/Entities/MJQuery/mjquery.form.component.js.map +1 -1
- package/dist/lib/generated/Entities/MJRecordProcess/mjrecordprocess.form.component.d.ts.map +1 -1
- package/dist/lib/generated/Entities/MJRecordProcess/mjrecordprocess.form.component.js +43 -7
- package/dist/lib/generated/Entities/MJRecordProcess/mjrecordprocess.form.component.js.map +1 -1
- package/dist/lib/generated/Entities/MJWebSearchProvider/mjwebsearchprovider.form.component.d.ts +10 -0
- package/dist/lib/generated/Entities/MJWebSearchProvider/mjwebsearchprovider.form.component.d.ts.map +1 -0
- package/dist/lib/generated/Entities/MJWebSearchProvider/mjwebsearchprovider.form.component.js +78 -0
- package/dist/lib/generated/Entities/MJWebSearchProvider/mjwebsearchprovider.form.component.js.map +1 -0
- package/dist/lib/generated/generated-forms.module.d.ts +240 -237
- package/dist/lib/generated/generated-forms.module.d.ts.map +1 -1
- package/dist/lib/generated/generated-forms.module.js +11 -2
- package/dist/lib/generated/generated-forms.module.js.map +1 -1
- package/dist/lib/panels/model-predictions/model-prediction.logic.d.ts +95 -0
- package/dist/lib/panels/model-predictions/model-prediction.logic.d.ts.map +1 -1
- package/dist/lib/panels/model-predictions/model-prediction.logic.js +173 -0
- package/dist/lib/panels/model-predictions/model-prediction.logic.js.map +1 -1
- package/dist/lib/panels/model-predictions/model-prediction.panel.d.ts +57 -2
- package/dist/lib/panels/model-predictions/model-prediction.panel.d.ts.map +1 -1
- package/dist/lib/panels/model-predictions/model-prediction.panel.js +555 -64
- package/dist/lib/panels/model-predictions/model-prediction.panel.js.map +1 -1
- package/dist/public-api.d.ts +4 -0
- package/dist/public-api.d.ts.map +1 -1
- package/dist/public-api.js +4 -0
- package/dist/public-api.js.map +1 -1
- package/package.json +42 -41
|
@@ -0,0 +1,693 @@
|
|
|
1
|
+
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, EventEmitter, Input, Output, inject, } from '@angular/core';
|
|
2
|
+
import { CommonModule } from '@angular/common';
|
|
3
|
+
import { FormsModule } from '@angular/forms';
|
|
4
|
+
import { MJButtonDirective } from '@memberjunction/ng-ui-components';
|
|
5
|
+
import { MJNotificationService } from '@memberjunction/ng-notifications';
|
|
6
|
+
import { PredictiveStudioPromoteModelOperation, } from '@memberjunction/core-entities';
|
|
7
|
+
import { PS_FEATURE_DOMINANCE_THRESHOLD, formatMetricValue, humanizeFeatureName, maxFeatureImportance, metricsToDisplay, overfitGap, parseFeatureImportance, parseMetrics, primaryModelScore, } from './ml-model-view-models';
|
|
8
|
+
import * as i0 from "@angular/core";
|
|
9
|
+
import * as i1 from "@angular/forms";
|
|
10
|
+
const _forTrack0 = ($index, $item) => $item.key;
|
|
11
|
+
const _forTrack1 = ($index, $item) => $item.name;
|
|
12
|
+
function PSModelDetailComponent_Conditional_1_For_19_Conditional_2_Template(rf, ctx) { if (rf & 1) {
|
|
13
|
+
i0.ɵɵelement(0, "i", 28);
|
|
14
|
+
} }
|
|
15
|
+
function PSModelDetailComponent_Conditional_1_For_19_Conditional_3_Template(rf, ctx) { if (rf & 1) {
|
|
16
|
+
i0.ɵɵtext(0);
|
|
17
|
+
} if (rf & 2) {
|
|
18
|
+
const $index_r1 = i0.ɵɵnextContext().$index;
|
|
19
|
+
i0.ɵɵtextInterpolate1(" ", $index_r1 + 1, " ");
|
|
20
|
+
} }
|
|
21
|
+
function PSModelDetailComponent_Conditional_1_For_19_Conditional_5_Template(rf, ctx) { if (rf & 1) {
|
|
22
|
+
i0.ɵɵelement(0, "span", 29);
|
|
23
|
+
} }
|
|
24
|
+
function PSModelDetailComponent_Conditional_1_For_19_Template(rf, ctx) { if (rf & 1) {
|
|
25
|
+
i0.ɵɵelementStart(0, "div", 26)(1, "span", 27);
|
|
26
|
+
i0.ɵɵconditionalCreate(2, PSModelDetailComponent_Conditional_1_For_19_Conditional_2_Template, 1, 0, "i", 28)(3, PSModelDetailComponent_Conditional_1_For_19_Conditional_3_Template, 1, 1);
|
|
27
|
+
i0.ɵɵelementEnd();
|
|
28
|
+
i0.ɵɵtext(4);
|
|
29
|
+
i0.ɵɵelementEnd();
|
|
30
|
+
i0.ɵɵconditionalCreate(5, PSModelDetailComponent_Conditional_1_For_19_Conditional_5_Template, 1, 0, "span", 29);
|
|
31
|
+
} if (rf & 2) {
|
|
32
|
+
const step_r2 = ctx.$implicit;
|
|
33
|
+
const ɵ$index_35_r3 = ctx.$index;
|
|
34
|
+
const ɵ$count_35_r4 = ctx.$count;
|
|
35
|
+
const ctx_r4 = i0.ɵɵnextContext(2);
|
|
36
|
+
i0.ɵɵclassProp("done", ctx_r4.stepState(step_r2) === "done")("curr", ctx_r4.stepState(step_r2) === "curr");
|
|
37
|
+
i0.ɵɵadvance(2);
|
|
38
|
+
i0.ɵɵconditional(ctx_r4.stepState(step_r2) === "done" ? 2 : 3);
|
|
39
|
+
i0.ɵɵadvance(2);
|
|
40
|
+
i0.ɵɵtextInterpolate1(" ", step_r2, " ");
|
|
41
|
+
i0.ɵɵadvance();
|
|
42
|
+
i0.ɵɵconditional(!(ɵ$index_35_r3 === ɵ$count_35_r4 - 1) ? 5 : -1);
|
|
43
|
+
} }
|
|
44
|
+
function PSModelDetailComponent_Conditional_1_Conditional_44_For_2_Template(rf, ctx) { if (rf & 1) {
|
|
45
|
+
i0.ɵɵelementStart(0, "div", 30)(1, "span", 16);
|
|
46
|
+
i0.ɵɵtext(2);
|
|
47
|
+
i0.ɵɵelementEnd();
|
|
48
|
+
i0.ɵɵelementStart(3, "strong");
|
|
49
|
+
i0.ɵɵtext(4);
|
|
50
|
+
i0.ɵɵelementEnd()();
|
|
51
|
+
} if (rf & 2) {
|
|
52
|
+
const s_r6 = ctx.$implicit;
|
|
53
|
+
i0.ɵɵadvance(2);
|
|
54
|
+
i0.ɵɵtextInterpolate(s_r6.label);
|
|
55
|
+
i0.ɵɵadvance(2);
|
|
56
|
+
i0.ɵɵtextInterpolate(s_r6.value);
|
|
57
|
+
} }
|
|
58
|
+
function PSModelDetailComponent_Conditional_1_Conditional_44_Template(rf, ctx) { if (rf & 1) {
|
|
59
|
+
i0.ɵɵelementStart(0, "div", 21);
|
|
60
|
+
i0.ɵɵrepeaterCreate(1, PSModelDetailComponent_Conditional_1_Conditional_44_For_2_Template, 5, 2, "div", 30, _forTrack0);
|
|
61
|
+
i0.ɵɵelementEnd();
|
|
62
|
+
} if (rf & 2) {
|
|
63
|
+
const ctx_r4 = i0.ɵɵnextContext(2);
|
|
64
|
+
i0.ɵɵadvance();
|
|
65
|
+
i0.ɵɵrepeater(ctx_r4.secondaryMetrics);
|
|
66
|
+
} }
|
|
67
|
+
function PSModelDetailComponent_Conditional_1_Conditional_45_Template(rf, ctx) { if (rf & 1) {
|
|
68
|
+
i0.ɵɵelementStart(0, "div", 22);
|
|
69
|
+
i0.ɵɵtext(1, "No secondary metrics recorded for this model.");
|
|
70
|
+
i0.ɵɵelementEnd();
|
|
71
|
+
} }
|
|
72
|
+
function PSModelDetailComponent_Conditional_1_Conditional_46_Template(rf, ctx) { if (rf & 1) {
|
|
73
|
+
i0.ɵɵelementStart(0, "div", 14);
|
|
74
|
+
i0.ɵɵtext(1, " Train\u2013holdout gap of ");
|
|
75
|
+
i0.ɵɵelementStart(2, "strong");
|
|
76
|
+
i0.ɵɵtext(3);
|
|
77
|
+
i0.ɵɵelementEnd();
|
|
78
|
+
i0.ɵɵtext(4);
|
|
79
|
+
i0.ɵɵelementEnd();
|
|
80
|
+
} if (rf & 2) {
|
|
81
|
+
const ctx_r4 = i0.ɵɵnextContext(2);
|
|
82
|
+
i0.ɵɵadvance(3);
|
|
83
|
+
i0.ɵɵtextInterpolate(ctx_r4.gapText);
|
|
84
|
+
i0.ɵɵadvance();
|
|
85
|
+
i0.ɵɵtextInterpolate1(" ", ctx_r4.gapVerdict, ". ");
|
|
86
|
+
} }
|
|
87
|
+
function PSModelDetailComponent_Conditional_1_Conditional_54_For_1_Template(rf, ctx) { if (rf & 1) {
|
|
88
|
+
i0.ɵɵelementStart(0, "div", 31)(1, "span", 32);
|
|
89
|
+
i0.ɵɵtext(2);
|
|
90
|
+
i0.ɵɵelementEnd();
|
|
91
|
+
i0.ɵɵelementStart(3, "div", 33);
|
|
92
|
+
i0.ɵɵelement(4, "span");
|
|
93
|
+
i0.ɵɵelementEnd();
|
|
94
|
+
i0.ɵɵelementStart(5, "span", 34);
|
|
95
|
+
i0.ɵɵtext(6);
|
|
96
|
+
i0.ɵɵelementEnd()();
|
|
97
|
+
} if (rf & 2) {
|
|
98
|
+
const f_r7 = ctx.$implicit;
|
|
99
|
+
i0.ɵɵadvance();
|
|
100
|
+
i0.ɵɵproperty("title", f_r7.name);
|
|
101
|
+
i0.ɵɵadvance();
|
|
102
|
+
i0.ɵɵtextInterpolate(f_r7.name);
|
|
103
|
+
i0.ɵɵadvance(2);
|
|
104
|
+
i0.ɵɵstyleProp("width", f_r7.pct, "%");
|
|
105
|
+
i0.ɵɵclassProp("warn", f_r7.warning);
|
|
106
|
+
i0.ɵɵadvance(2);
|
|
107
|
+
i0.ɵɵtextInterpolate(f_r7.value);
|
|
108
|
+
} }
|
|
109
|
+
function PSModelDetailComponent_Conditional_1_Conditional_54_Template(rf, ctx) { if (rf & 1) {
|
|
110
|
+
i0.ɵɵrepeaterCreate(0, PSModelDetailComponent_Conditional_1_Conditional_54_For_1_Template, 7, 7, "div", 31, _forTrack1);
|
|
111
|
+
} if (rf & 2) {
|
|
112
|
+
const ctx_r4 = i0.ɵɵnextContext(2);
|
|
113
|
+
i0.ɵɵrepeater(ctx_r4.importance);
|
|
114
|
+
} }
|
|
115
|
+
function PSModelDetailComponent_Conditional_1_Conditional_55_Template(rf, ctx) { if (rf & 1) {
|
|
116
|
+
i0.ɵɵelementStart(0, "div", 23);
|
|
117
|
+
i0.ɵɵtext(1, "No feature-importance data was recorded for this model.");
|
|
118
|
+
i0.ɵɵelementEnd();
|
|
119
|
+
} }
|
|
120
|
+
function PSModelDetailComponent_Conditional_1_Conditional_56_Conditional_3_Template(rf, ctx) { if (rf & 1) {
|
|
121
|
+
i0.ɵɵelementStart(0, "strong");
|
|
122
|
+
i0.ɵɵtext(1, "Leakage flag raised.");
|
|
123
|
+
i0.ɵɵelementEnd();
|
|
124
|
+
i0.ɵɵtext(2, " A single feature dominates \u2014 top importance is ");
|
|
125
|
+
i0.ɵɵelementStart(3, "strong");
|
|
126
|
+
i0.ɵɵtext(4);
|
|
127
|
+
i0.ɵɵelementEnd();
|
|
128
|
+
i0.ɵɵtext(5, ", at/above the ");
|
|
129
|
+
i0.ɵɵelementStart(6, "strong");
|
|
130
|
+
i0.ɵɵtext(7);
|
|
131
|
+
i0.ɵɵelementEnd();
|
|
132
|
+
i0.ɵɵtext(8, " dominance threshold. ");
|
|
133
|
+
i0.ɵɵelementStart(9, "div", 36);
|
|
134
|
+
i0.ɵɵelement(10, "i", 37);
|
|
135
|
+
i0.ɵɵtext(11, " Sign-off with a reason is required to publish this model. ");
|
|
136
|
+
i0.ɵɵelementEnd();
|
|
137
|
+
} if (rf & 2) {
|
|
138
|
+
const ctx_r4 = i0.ɵɵnextContext(3);
|
|
139
|
+
i0.ɵɵadvance(4);
|
|
140
|
+
i0.ɵɵtextInterpolate2("", ctx_r4.topFeatureName, " at ", ctx_r4.topFeatureValue);
|
|
141
|
+
i0.ɵɵadvance(3);
|
|
142
|
+
i0.ɵɵtextInterpolate(ctx_r4.dominanceThreshold);
|
|
143
|
+
} }
|
|
144
|
+
function PSModelDetailComponent_Conditional_1_Conditional_56_Conditional_4_Template(rf, ctx) { if (rf & 1) {
|
|
145
|
+
i0.ɵɵelementStart(0, "strong");
|
|
146
|
+
i0.ɵɵtext(1, "Leakage gate clear.");
|
|
147
|
+
i0.ɵɵelementEnd();
|
|
148
|
+
i0.ɵɵtext(2, " No single feature dominates \u2014 top importance is ");
|
|
149
|
+
i0.ɵɵelementStart(3, "strong");
|
|
150
|
+
i0.ɵɵtext(4);
|
|
151
|
+
i0.ɵɵelementEnd();
|
|
152
|
+
i0.ɵɵtext(5, ", below the ");
|
|
153
|
+
i0.ɵɵelementStart(6, "strong");
|
|
154
|
+
i0.ɵɵtext(7);
|
|
155
|
+
i0.ɵɵelementEnd();
|
|
156
|
+
i0.ɵɵtext(8, " dominance threshold. ");
|
|
157
|
+
i0.ɵɵelementStart(9, "div", 36);
|
|
158
|
+
i0.ɵɵelement(10, "i", 28);
|
|
159
|
+
i0.ɵɵtext(11, " Sign-off required before any Validated \u2192 Published promotion. ");
|
|
160
|
+
i0.ɵɵelementEnd();
|
|
161
|
+
} if (rf & 2) {
|
|
162
|
+
const ctx_r4 = i0.ɵɵnextContext(3);
|
|
163
|
+
i0.ɵɵadvance(4);
|
|
164
|
+
i0.ɵɵtextInterpolate2("", ctx_r4.topFeatureName, " at ", ctx_r4.topFeatureValue);
|
|
165
|
+
i0.ɵɵadvance(3);
|
|
166
|
+
i0.ɵɵtextInterpolate(ctx_r4.dominanceThreshold);
|
|
167
|
+
} }
|
|
168
|
+
function PSModelDetailComponent_Conditional_1_Conditional_56_Template(rf, ctx) { if (rf & 1) {
|
|
169
|
+
i0.ɵɵelementStart(0, "div", 35);
|
|
170
|
+
i0.ɵɵelement(1, "i");
|
|
171
|
+
i0.ɵɵelementStart(2, "div");
|
|
172
|
+
i0.ɵɵconditionalCreate(3, PSModelDetailComponent_Conditional_1_Conditional_56_Conditional_3_Template, 12, 3)(4, PSModelDetailComponent_Conditional_1_Conditional_56_Conditional_4_Template, 12, 3);
|
|
173
|
+
i0.ɵɵelementEnd()();
|
|
174
|
+
} if (rf & 2) {
|
|
175
|
+
const ctx_r4 = i0.ɵɵnextContext(2);
|
|
176
|
+
i0.ɵɵclassProp("success", !ctx_r4.leakageFlagged)("warn", ctx_r4.leakageFlagged);
|
|
177
|
+
i0.ɵɵadvance();
|
|
178
|
+
i0.ɵɵclassMap(ctx_r4.leakageFlagged ? "fa-solid fa-triangle-exclamation" : "fa-solid fa-shield-halved");
|
|
179
|
+
i0.ɵɵadvance(2);
|
|
180
|
+
i0.ɵɵconditional(ctx_r4.leakageFlagged ? 3 : 4);
|
|
181
|
+
} }
|
|
182
|
+
function PSModelDetailComponent_Conditional_1_Conditional_57_Conditional_4_Template(rf, ctx) { if (rf & 1) {
|
|
183
|
+
const _r8 = i0.ɵɵgetCurrentView();
|
|
184
|
+
i0.ɵɵelementStart(0, "button", 43);
|
|
185
|
+
i0.ɵɵlistener("click", function PSModelDetailComponent_Conditional_1_Conditional_57_Conditional_4_Template_button_click_0_listener() { i0.ɵɵrestoreView(_r8); const ctx_r4 = i0.ɵɵnextContext(3); return i0.ɵɵresetView(ctx_r4.requestPromote("Validated")); });
|
|
186
|
+
i0.ɵɵelement(1, "i", 44);
|
|
187
|
+
i0.ɵɵtext(2, " Mark Validated ");
|
|
188
|
+
i0.ɵɵelementEnd();
|
|
189
|
+
} }
|
|
190
|
+
function PSModelDetailComponent_Conditional_1_Conditional_57_Conditional_5_Template(rf, ctx) { if (rf & 1) {
|
|
191
|
+
const _r9 = i0.ɵɵgetCurrentView();
|
|
192
|
+
i0.ɵɵelementStart(0, "button", 45);
|
|
193
|
+
i0.ɵɵlistener("click", function PSModelDetailComponent_Conditional_1_Conditional_57_Conditional_5_Template_button_click_0_listener() { i0.ɵɵrestoreView(_r9); const ctx_r4 = i0.ɵɵnextContext(3); return i0.ɵɵresetView(ctx_r4.requestPromote("Published")); });
|
|
194
|
+
i0.ɵɵelement(1, "i", 46);
|
|
195
|
+
i0.ɵɵtext(2, " Promote to Published ");
|
|
196
|
+
i0.ɵɵelementEnd();
|
|
197
|
+
} }
|
|
198
|
+
function PSModelDetailComponent_Conditional_1_Conditional_57_Conditional_6_Template(rf, ctx) { if (rf & 1) {
|
|
199
|
+
const _r10 = i0.ɵɵgetCurrentView();
|
|
200
|
+
i0.ɵɵelementStart(0, "button", 47);
|
|
201
|
+
i0.ɵɵlistener("click", function PSModelDetailComponent_Conditional_1_Conditional_57_Conditional_6_Template_button_click_0_listener() { i0.ɵɵrestoreView(_r10); const ctx_r4 = i0.ɵɵnextContext(3); return i0.ɵɵresetView(ctx_r4.requestPromote("Archived")); });
|
|
202
|
+
i0.ɵɵelement(1, "i", 48);
|
|
203
|
+
i0.ɵɵtext(2, " Archive ");
|
|
204
|
+
i0.ɵɵelementEnd();
|
|
205
|
+
} }
|
|
206
|
+
function PSModelDetailComponent_Conditional_1_Conditional_57_Template(rf, ctx) { if (rf & 1) {
|
|
207
|
+
i0.ɵɵelementStart(0, "div", 2)(1, "div", 38)(2, "span", 39);
|
|
208
|
+
i0.ɵɵtext(3);
|
|
209
|
+
i0.ɵɵelementEnd();
|
|
210
|
+
i0.ɵɵconditionalCreate(4, PSModelDetailComponent_Conditional_1_Conditional_57_Conditional_4_Template, 3, 0, "button", 40);
|
|
211
|
+
i0.ɵɵconditionalCreate(5, PSModelDetailComponent_Conditional_1_Conditional_57_Conditional_5_Template, 3, 0, "button", 41);
|
|
212
|
+
i0.ɵɵconditionalCreate(6, PSModelDetailComponent_Conditional_1_Conditional_57_Conditional_6_Template, 3, 0, "button", 42);
|
|
213
|
+
i0.ɵɵelementEnd()();
|
|
214
|
+
} if (rf & 2) {
|
|
215
|
+
const ctx_r4 = i0.ɵɵnextContext(2);
|
|
216
|
+
i0.ɵɵadvance(3);
|
|
217
|
+
i0.ɵɵtextInterpolate(ctx_r4.actionHint);
|
|
218
|
+
i0.ɵɵadvance();
|
|
219
|
+
i0.ɵɵconditional(ctx_r4.canValidate ? 4 : -1);
|
|
220
|
+
i0.ɵɵadvance();
|
|
221
|
+
i0.ɵɵconditional(ctx_r4.canPublish ? 5 : -1);
|
|
222
|
+
i0.ɵɵadvance();
|
|
223
|
+
i0.ɵɵconditional(ctx_r4.canArchive ? 6 : -1);
|
|
224
|
+
} }
|
|
225
|
+
function PSModelDetailComponent_Conditional_1_Conditional_58_Conditional_9_Template(rf, ctx) { if (rf & 1) {
|
|
226
|
+
const _r12 = i0.ɵɵgetCurrentView();
|
|
227
|
+
i0.ɵɵelementStart(0, "div", 55)(1, "label");
|
|
228
|
+
i0.ɵɵtext(2);
|
|
229
|
+
i0.ɵɵelementEnd();
|
|
230
|
+
i0.ɵɵelementStart(3, "textarea", 62);
|
|
231
|
+
i0.ɵɵtwoWayListener("ngModelChange", function PSModelDetailComponent_Conditional_1_Conditional_58_Conditional_9_Template_textarea_ngModelChange_3_listener($event) { i0.ɵɵrestoreView(_r12); const ctx_r4 = i0.ɵɵnextContext(3); i0.ɵɵtwoWayBindingSet(ctx_r4.pendingReason, $event) || (ctx_r4.pendingReason = $event); return i0.ɵɵresetView($event); });
|
|
232
|
+
i0.ɵɵelementEnd()();
|
|
233
|
+
} if (rf & 2) {
|
|
234
|
+
const ctx_r4 = i0.ɵɵnextContext(3);
|
|
235
|
+
i0.ɵɵadvance(2);
|
|
236
|
+
i0.ɵɵtextInterpolate2(" ", ctx_r4.pending.leakageFlagged ? "Leakage sign-off reason" : "Reason (optional)", " ", ctx_r4.pending.leakageFlagged ? " *" : "", " ");
|
|
237
|
+
i0.ɵɵadvance();
|
|
238
|
+
i0.ɵɵtwoWayProperty("ngModel", ctx_r4.pendingReason);
|
|
239
|
+
i0.ɵɵproperty("placeholder", ctx_r4.pending.leakageFlagged ? "Explain why this model is safe to publish despite the leakage flag\u2026" : "Add an optional note for the record\u2026")("disabled", ctx_r4.busy);
|
|
240
|
+
} }
|
|
241
|
+
function PSModelDetailComponent_Conditional_1_Conditional_58_Conditional_12_Template(rf, ctx) { if (rf & 1) {
|
|
242
|
+
i0.ɵɵelement(0, "i", 58);
|
|
243
|
+
} }
|
|
244
|
+
function PSModelDetailComponent_Conditional_1_Conditional_58_Conditional_13_Template(rf, ctx) { if (rf & 1) {
|
|
245
|
+
i0.ɵɵelement(0, "i");
|
|
246
|
+
} if (rf & 2) {
|
|
247
|
+
const ctx_r4 = i0.ɵɵnextContext(3);
|
|
248
|
+
i0.ɵɵclassMap(ctx_r4.pendingIcon);
|
|
249
|
+
} }
|
|
250
|
+
function PSModelDetailComponent_Conditional_1_Conditional_58_Template(rf, ctx) { if (rf & 1) {
|
|
251
|
+
const _r11 = i0.ɵɵgetCurrentView();
|
|
252
|
+
i0.ɵɵelementStart(0, "div", 49);
|
|
253
|
+
i0.ɵɵlistener("click", function PSModelDetailComponent_Conditional_1_Conditional_58_Template_div_click_0_listener() { i0.ɵɵrestoreView(_r11); const ctx_r4 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r4.cancelPromote()); });
|
|
254
|
+
i0.ɵɵelementStart(1, "div", 50);
|
|
255
|
+
i0.ɵɵlistener("click", function PSModelDetailComponent_Conditional_1_Conditional_58_Template_div_click_1_listener($event) { return $event.stopPropagation(); });
|
|
256
|
+
i0.ɵɵelementStart(2, "div", 51)(3, "span", 52);
|
|
257
|
+
i0.ɵɵelement(4, "i");
|
|
258
|
+
i0.ɵɵelementEnd();
|
|
259
|
+
i0.ɵɵelementStart(5, "h3");
|
|
260
|
+
i0.ɵɵtext(6);
|
|
261
|
+
i0.ɵɵelementEnd()();
|
|
262
|
+
i0.ɵɵelementStart(7, "div", 53);
|
|
263
|
+
i0.ɵɵelement(8, "div", 54);
|
|
264
|
+
i0.ɵɵconditionalCreate(9, PSModelDetailComponent_Conditional_1_Conditional_58_Conditional_9_Template, 4, 5, "div", 55);
|
|
265
|
+
i0.ɵɵelementEnd();
|
|
266
|
+
i0.ɵɵelementStart(10, "div", 56)(11, "button", 57);
|
|
267
|
+
i0.ɵɵlistener("click", function PSModelDetailComponent_Conditional_1_Conditional_58_Template_button_click_11_listener() { i0.ɵɵrestoreView(_r11); const ctx_r4 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r4.confirmPromote(ctx_r4.pendingReason.trim())); });
|
|
268
|
+
i0.ɵɵconditionalCreate(12, PSModelDetailComponent_Conditional_1_Conditional_58_Conditional_12_Template, 1, 0, "i", 58)(13, PSModelDetailComponent_Conditional_1_Conditional_58_Conditional_13_Template, 1, 2, "i", 59);
|
|
269
|
+
i0.ɵɵtext(14);
|
|
270
|
+
i0.ɵɵelementEnd();
|
|
271
|
+
i0.ɵɵelement(15, "span", 60);
|
|
272
|
+
i0.ɵɵelementStart(16, "button", 61);
|
|
273
|
+
i0.ɵɵlistener("click", function PSModelDetailComponent_Conditional_1_Conditional_58_Template_button_click_16_listener() { i0.ɵɵrestoreView(_r11); const ctx_r4 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r4.cancelPromote()); });
|
|
274
|
+
i0.ɵɵtext(17, " Cancel ");
|
|
275
|
+
i0.ɵɵelementEnd()()()();
|
|
276
|
+
} if (rf & 2) {
|
|
277
|
+
const ctx_r4 = i0.ɵɵnextContext(2);
|
|
278
|
+
i0.ɵɵadvance(2);
|
|
279
|
+
i0.ɵɵclassProp("warn", ctx_r4.pendingVariant === "warn")("danger", ctx_r4.pendingVariant === "danger");
|
|
280
|
+
i0.ɵɵadvance(2);
|
|
281
|
+
i0.ɵɵclassMap(ctx_r4.pendingIcon);
|
|
282
|
+
i0.ɵɵadvance(2);
|
|
283
|
+
i0.ɵɵtextInterpolate(ctx_r4.pendingTitle);
|
|
284
|
+
i0.ɵɵadvance(2);
|
|
285
|
+
i0.ɵɵproperty("innerHTML", ctx_r4.pendingMessage, i0.ɵɵsanitizeHtml);
|
|
286
|
+
i0.ɵɵadvance();
|
|
287
|
+
i0.ɵɵconditional(ctx_r4.pending.leakageFlagged || ctx_r4.pending.targetStatus === "Archived" ? 9 : -1);
|
|
288
|
+
i0.ɵɵadvance(2);
|
|
289
|
+
i0.ɵɵproperty("variant", ctx_r4.pendingVariant === "danger" ? "danger" : "primary")("disabled", ctx_r4.busy || ctx_r4.pending.leakageFlagged && !ctx_r4.pendingReason.trim());
|
|
290
|
+
i0.ɵɵadvance();
|
|
291
|
+
i0.ɵɵconditional(ctx_r4.busy ? 12 : 13);
|
|
292
|
+
i0.ɵɵadvance(2);
|
|
293
|
+
i0.ɵɵtextInterpolate1(" ", ctx_r4.pendingConfirmLabel, " ");
|
|
294
|
+
i0.ɵɵadvance(2);
|
|
295
|
+
i0.ɵɵproperty("disabled", ctx_r4.busy);
|
|
296
|
+
} }
|
|
297
|
+
function PSModelDetailComponent_Conditional_1_Template(rf, ctx) { if (rf & 1) {
|
|
298
|
+
i0.ɵɵelementStart(0, "div", 2)(1, "div", 3)(2, "div", 4);
|
|
299
|
+
i0.ɵɵelement(3, "i", 5);
|
|
300
|
+
i0.ɵɵelementEnd();
|
|
301
|
+
i0.ɵɵelementStart(4, "div", 6)(5, "h2", 7);
|
|
302
|
+
i0.ɵɵtext(6);
|
|
303
|
+
i0.ɵɵelementStart(7, "span", 8);
|
|
304
|
+
i0.ɵɵtext(8);
|
|
305
|
+
i0.ɵɵelementEnd()();
|
|
306
|
+
i0.ɵɵelementStart(9, "div", 9);
|
|
307
|
+
i0.ɵɵtext(10);
|
|
308
|
+
i0.ɵɵelementEnd()();
|
|
309
|
+
i0.ɵɵelementStart(11, "span", 10);
|
|
310
|
+
i0.ɵɵtext(12);
|
|
311
|
+
i0.ɵɵelementEnd()()();
|
|
312
|
+
i0.ɵɵelementStart(13, "div", 2)(14, "div", 11)(15, "div", 12);
|
|
313
|
+
i0.ɵɵtext(16, "Lifecycle");
|
|
314
|
+
i0.ɵɵelementEnd();
|
|
315
|
+
i0.ɵɵelementStart(17, "div", 13);
|
|
316
|
+
i0.ɵɵrepeaterCreate(18, PSModelDetailComponent_Conditional_1_For_19_Template, 6, 7, null, null, i0.ɵɵrepeaterTrackByIdentity);
|
|
317
|
+
i0.ɵɵelementEnd();
|
|
318
|
+
i0.ɵɵelementStart(20, "div", 14);
|
|
319
|
+
i0.ɵɵtext(21, " Models are immutable once registered; promotion only changes lifecycle state, never weights. ");
|
|
320
|
+
i0.ɵɵelementEnd()()();
|
|
321
|
+
i0.ɵɵelementStart(22, "div", 2)(23, "div", 15)(24, "h3");
|
|
322
|
+
i0.ɵɵtext(25, "Performance");
|
|
323
|
+
i0.ɵɵelementEnd();
|
|
324
|
+
i0.ɵɵelementStart(26, "span", 16);
|
|
325
|
+
i0.ɵɵtext(27, "Holdout = held-out test fold, never seen in training");
|
|
326
|
+
i0.ɵɵelementEnd()();
|
|
327
|
+
i0.ɵɵelementStart(28, "div", 11)(29, "div", 17)(30, "div", 18)(31, "div", 12);
|
|
328
|
+
i0.ɵɵtext(32);
|
|
329
|
+
i0.ɵɵelementEnd();
|
|
330
|
+
i0.ɵɵelementStart(33, "div", 19);
|
|
331
|
+
i0.ɵɵtext(34);
|
|
332
|
+
i0.ɵɵelementEnd();
|
|
333
|
+
i0.ɵɵelementStart(35, "div", 16);
|
|
334
|
+
i0.ɵɵtext(36, "in-sample \u2022 optimistic");
|
|
335
|
+
i0.ɵɵelementEnd()();
|
|
336
|
+
i0.ɵɵelementStart(37, "div", 20)(38, "div", 12);
|
|
337
|
+
i0.ɵɵtext(39);
|
|
338
|
+
i0.ɵɵelementEnd();
|
|
339
|
+
i0.ɵɵelementStart(40, "div", 19);
|
|
340
|
+
i0.ɵɵtext(41);
|
|
341
|
+
i0.ɵɵelementEnd();
|
|
342
|
+
i0.ɵɵelementStart(42, "div", 16);
|
|
343
|
+
i0.ɵɵtext(43, "out-of-sample \u2022 the honest number");
|
|
344
|
+
i0.ɵɵelementEnd()()();
|
|
345
|
+
i0.ɵɵconditionalCreate(44, PSModelDetailComponent_Conditional_1_Conditional_44_Template, 3, 0, "div", 21)(45, PSModelDetailComponent_Conditional_1_Conditional_45_Template, 2, 0, "div", 22);
|
|
346
|
+
i0.ɵɵconditionalCreate(46, PSModelDetailComponent_Conditional_1_Conditional_46_Template, 5, 2, "div", 14);
|
|
347
|
+
i0.ɵɵelementEnd()();
|
|
348
|
+
i0.ɵɵelementStart(47, "div", 2)(48, "div", 15)(49, "h3");
|
|
349
|
+
i0.ɵɵtext(50, "Feature Importance");
|
|
350
|
+
i0.ɵɵelementEnd();
|
|
351
|
+
i0.ɵɵelementStart(51, "span", 16);
|
|
352
|
+
i0.ɵɵtext(52);
|
|
353
|
+
i0.ɵɵelementEnd()();
|
|
354
|
+
i0.ɵɵelementStart(53, "div", 11);
|
|
355
|
+
i0.ɵɵconditionalCreate(54, PSModelDetailComponent_Conditional_1_Conditional_54_Template, 2, 0)(55, PSModelDetailComponent_Conditional_1_Conditional_55_Template, 2, 0, "div", 23);
|
|
356
|
+
i0.ɵɵelementEnd()();
|
|
357
|
+
i0.ɵɵconditionalCreate(56, PSModelDetailComponent_Conditional_1_Conditional_56_Template, 5, 7, "div", 24);
|
|
358
|
+
i0.ɵɵconditionalCreate(57, PSModelDetailComponent_Conditional_1_Conditional_57_Template, 7, 4, "div", 2);
|
|
359
|
+
i0.ɵɵconditionalCreate(58, PSModelDetailComponent_Conditional_1_Conditional_58_Template, 18, 14, "div", 25);
|
|
360
|
+
} if (rf & 2) {
|
|
361
|
+
const ctx_r4 = i0.ɵɵnextContext();
|
|
362
|
+
i0.ɵɵadvance(6);
|
|
363
|
+
i0.ɵɵtextInterpolate1(" ", ctx_r4.modelDisplayName, " ");
|
|
364
|
+
i0.ɵɵadvance(2);
|
|
365
|
+
i0.ɵɵtextInterpolate1("v", ctx_r4.model.Version);
|
|
366
|
+
i0.ɵɵadvance(2);
|
|
367
|
+
i0.ɵɵtextInterpolate2(" ", ctx_r4.model.Algorithm || "Model", "", ctx_r4.targetSuffix, " \u2022 immutable snapshot ");
|
|
368
|
+
i0.ɵɵadvance();
|
|
369
|
+
i0.ɵɵclassMap(ctx_r4.statusBadgeClass(ctx_r4.model.Status));
|
|
370
|
+
i0.ɵɵadvance();
|
|
371
|
+
i0.ɵɵtextInterpolate(ctx_r4.model.Status);
|
|
372
|
+
i0.ɵɵadvance(6);
|
|
373
|
+
i0.ɵɵrepeater(ctx_r4.lifecycleSteps);
|
|
374
|
+
i0.ɵɵadvance(14);
|
|
375
|
+
i0.ɵɵtextInterpolate1("Train ", ctx_r4.primaryScoreLabel);
|
|
376
|
+
i0.ɵɵadvance(2);
|
|
377
|
+
i0.ɵɵtextInterpolate(ctx_r4.trainPrimaryScore);
|
|
378
|
+
i0.ɵɵadvance(5);
|
|
379
|
+
i0.ɵɵtextInterpolate1("Holdout ", ctx_r4.primaryScoreLabel);
|
|
380
|
+
i0.ɵɵadvance(2);
|
|
381
|
+
i0.ɵɵtextInterpolate(ctx_r4.holdoutPrimaryScore);
|
|
382
|
+
i0.ɵɵadvance(3);
|
|
383
|
+
i0.ɵɵconditional(ctx_r4.secondaryMetrics.length > 0 ? 44 : 45);
|
|
384
|
+
i0.ɵɵadvance(2);
|
|
385
|
+
i0.ɵɵconditional(ctx_r4.gapText ? 46 : -1);
|
|
386
|
+
i0.ɵɵadvance(6);
|
|
387
|
+
i0.ɵɵtextInterpolate(ctx_r4.importanceCaption);
|
|
388
|
+
i0.ɵɵadvance(2);
|
|
389
|
+
i0.ɵɵconditional(ctx_r4.importance.length > 0 ? 54 : 55);
|
|
390
|
+
i0.ɵɵadvance(2);
|
|
391
|
+
i0.ɵɵconditional(ctx_r4.importance.length > 0 ? 56 : -1);
|
|
392
|
+
i0.ɵɵadvance();
|
|
393
|
+
i0.ɵɵconditional(ctx_r4.showActions ? 57 : -1);
|
|
394
|
+
i0.ɵɵadvance();
|
|
395
|
+
i0.ɵɵconditional(ctx_r4.pending ? 58 : -1);
|
|
396
|
+
} }
|
|
397
|
+
function PSModelDetailComponent_Conditional_2_Template(rf, ctx) { if (rf & 1) {
|
|
398
|
+
i0.ɵɵelementStart(0, "div", 1)(1, "span", 63);
|
|
399
|
+
i0.ɵɵelement(2, "i", 5);
|
|
400
|
+
i0.ɵɵelementEnd();
|
|
401
|
+
i0.ɵɵelementStart(3, "h3");
|
|
402
|
+
i0.ɵɵtext(4, "No Model Selected");
|
|
403
|
+
i0.ɵɵelementEnd();
|
|
404
|
+
i0.ɵɵelementStart(5, "p", 64);
|
|
405
|
+
i0.ɵɵtext(6, "Select an ML Model from the list to inspect its configuration and metrics.");
|
|
406
|
+
i0.ɵɵelementEnd()();
|
|
407
|
+
} }
|
|
408
|
+
/**
|
|
409
|
+
* **PSModelDetailComponent** — Unified enterprise component for inspecting, evaluating,
|
|
410
|
+
* and promoting versioned ML Models. Shared between:
|
|
411
|
+
* 1. **Predictive Studio Model Registry** (`PSRegistryComponent`)
|
|
412
|
+
* 2. **Custom Entity Form for `MJ: ML Models`** (`MLModelFormComponentExtended`)
|
|
413
|
+
*
|
|
414
|
+
* Renders:
|
|
415
|
+
* - Identity & immutable snapshot badge
|
|
416
|
+
* - Progressive 4-stage lifecycle stepper (Draft → Validated → Published → Archived)
|
|
417
|
+
* - In-sample train vs out-of-sample holdout performance cards and overfit gap check
|
|
418
|
+
* - Key feature importance rankings with single-feature dominance warnings
|
|
419
|
+
* - Target leakage gate verification and required sign-off rationale capture
|
|
420
|
+
* - Remote Operation lifecycle promotion (Validate, Publish, Archive)
|
|
421
|
+
*/
|
|
422
|
+
export class PSModelDetailComponent {
|
|
423
|
+
constructor() {
|
|
424
|
+
this.model = null;
|
|
425
|
+
this.provider = null;
|
|
426
|
+
this.currentUser = null;
|
|
427
|
+
this.showActions = true;
|
|
428
|
+
this.statusChanged = new EventEmitter();
|
|
429
|
+
this.cdr = inject(ChangeDetectorRef);
|
|
430
|
+
this.notifications = inject(MJNotificationService);
|
|
431
|
+
this.lifecycleSteps = ['Draft', 'Validated', 'Published', 'Archived'];
|
|
432
|
+
this.dominanceThreshold = PS_FEATURE_DOMINANCE_THRESHOLD.toFixed(2);
|
|
433
|
+
this.pending = null;
|
|
434
|
+
this.pendingReason = '';
|
|
435
|
+
this.busy = false;
|
|
436
|
+
}
|
|
437
|
+
get modelDisplayName() {
|
|
438
|
+
if (this.displayName)
|
|
439
|
+
return this.displayName;
|
|
440
|
+
if (!this.model)
|
|
441
|
+
return '';
|
|
442
|
+
return this.model.Pipeline || (this.model.Algorithm ? `${this.model.Algorithm} Model` : 'ML Model');
|
|
443
|
+
}
|
|
444
|
+
// ── Badges & Lifecycle Stepper ──
|
|
445
|
+
statusBadgeClass(status) {
|
|
446
|
+
switch ((status ?? '').toLowerCase()) {
|
|
447
|
+
case 'published':
|
|
448
|
+
return 'green';
|
|
449
|
+
case 'validated':
|
|
450
|
+
return 'blue';
|
|
451
|
+
case 'draft':
|
|
452
|
+
return 'amber';
|
|
453
|
+
case 'archived':
|
|
454
|
+
return 'gray';
|
|
455
|
+
default:
|
|
456
|
+
return 'gray';
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
stepState(step) {
|
|
460
|
+
const order = ['Draft', 'Validated', 'Published', 'Archived'];
|
|
461
|
+
const currIdx = order.indexOf(this.model?.Status ?? 'Draft');
|
|
462
|
+
const stepIdx = order.indexOf(step);
|
|
463
|
+
if (stepIdx < currIdx)
|
|
464
|
+
return 'done';
|
|
465
|
+
if (stepIdx === currIdx)
|
|
466
|
+
return 'curr';
|
|
467
|
+
return 'todo';
|
|
468
|
+
}
|
|
469
|
+
get targetSuffix() {
|
|
470
|
+
const target = this.model?.TargetVariable;
|
|
471
|
+
return target ? ` · predicts ${target}` : '';
|
|
472
|
+
}
|
|
473
|
+
// ── Metrics & Performance Derivations ──
|
|
474
|
+
get primaryScoreLabel() {
|
|
475
|
+
if (!this.model)
|
|
476
|
+
return 'Score';
|
|
477
|
+
const score = primaryModelScore(this.model);
|
|
478
|
+
return score?.label ?? 'AUC';
|
|
479
|
+
}
|
|
480
|
+
get trainPrimaryScore() {
|
|
481
|
+
if (!this.model)
|
|
482
|
+
return '—';
|
|
483
|
+
const score = primaryModelScore(this.model);
|
|
484
|
+
const trainMetrics = parseMetrics(this.model.Metrics);
|
|
485
|
+
if (score && trainMetrics[score.key] != null) {
|
|
486
|
+
return formatMetricValue(score.key, trainMetrics[score.key]);
|
|
487
|
+
}
|
|
488
|
+
return trainMetrics.AUC != null ? trainMetrics.AUC.toFixed(3) : '—';
|
|
489
|
+
}
|
|
490
|
+
get holdoutPrimaryScore() {
|
|
491
|
+
if (!this.model)
|
|
492
|
+
return '—';
|
|
493
|
+
const score = primaryModelScore(this.model);
|
|
494
|
+
const holdoutMetrics = parseMetrics(this.model.HoldoutMetrics);
|
|
495
|
+
if (score && holdoutMetrics[score.key] != null) {
|
|
496
|
+
return formatMetricValue(score.key, holdoutMetrics[score.key]);
|
|
497
|
+
}
|
|
498
|
+
return holdoutMetrics.AUC != null ? holdoutMetrics.AUC.toFixed(3) : '—';
|
|
499
|
+
}
|
|
500
|
+
get secondaryMetrics() {
|
|
501
|
+
if (!this.model)
|
|
502
|
+
return [];
|
|
503
|
+
const score = primaryModelScore(this.model);
|
|
504
|
+
const primaryKey = score?.key;
|
|
505
|
+
const holdout = metricsToDisplay(parseMetrics(this.model.HoldoutMetrics), { excludeAuc: false });
|
|
506
|
+
const all = holdout.length > 0 ? holdout : metricsToDisplay(parseMetrics(this.model.Metrics), { excludeAuc: false });
|
|
507
|
+
return primaryKey ? all.filter((m) => m.key !== primaryKey) : all.filter((m) => m.key !== 'AUC');
|
|
508
|
+
}
|
|
509
|
+
get gapText() {
|
|
510
|
+
if (!this.model)
|
|
511
|
+
return '';
|
|
512
|
+
const gap = overfitGap(this.model);
|
|
513
|
+
return gap == null ? '' : Math.abs(gap).toFixed(3);
|
|
514
|
+
}
|
|
515
|
+
get gapVerdict() {
|
|
516
|
+
if (!this.model)
|
|
517
|
+
return '';
|
|
518
|
+
const gap = overfitGap(this.model);
|
|
519
|
+
if (gap == null)
|
|
520
|
+
return '';
|
|
521
|
+
return gap > 0.1 ? 'is wide — watch for overfitting' : 'is within tolerance — no overfitting flag';
|
|
522
|
+
}
|
|
523
|
+
// ── Feature Importance & Leakage Gate ──
|
|
524
|
+
get importance() {
|
|
525
|
+
if (!this.model)
|
|
526
|
+
return [];
|
|
527
|
+
return parseFeatureImportance(this.model.FeatureImportance, 6).map((b) => ({
|
|
528
|
+
...b,
|
|
529
|
+
name: humanizeFeatureName(b.name),
|
|
530
|
+
}));
|
|
531
|
+
}
|
|
532
|
+
get importanceCaption() {
|
|
533
|
+
const n = this.importance.length;
|
|
534
|
+
return n === 0 ? 'no data' : `normalized · top ${n}`;
|
|
535
|
+
}
|
|
536
|
+
get topFeatureName() {
|
|
537
|
+
return this.importance[0]?.name ?? '—';
|
|
538
|
+
}
|
|
539
|
+
get topFeatureValue() {
|
|
540
|
+
return this.importance[0]?.value ?? '—';
|
|
541
|
+
}
|
|
542
|
+
get leakageFlagged() {
|
|
543
|
+
if (!this.model)
|
|
544
|
+
return false;
|
|
545
|
+
const max = maxFeatureImportance(this.model.FeatureImportance);
|
|
546
|
+
return max != null && max >= PS_FEATURE_DOMINANCE_THRESHOLD;
|
|
547
|
+
}
|
|
548
|
+
// ── Actions & Promotion Flow ──
|
|
549
|
+
get canValidate() {
|
|
550
|
+
return this.model?.Status === 'Draft';
|
|
551
|
+
}
|
|
552
|
+
get canPublish() {
|
|
553
|
+
return this.model?.Status === 'Validated';
|
|
554
|
+
}
|
|
555
|
+
get canArchive() {
|
|
556
|
+
return this.model?.Status === 'Published' || this.model?.Status === 'Validated';
|
|
557
|
+
}
|
|
558
|
+
get actionHint() {
|
|
559
|
+
switch (this.model?.Status) {
|
|
560
|
+
case 'Draft':
|
|
561
|
+
return 'Verify metrics and feature importance before validating or publishing.';
|
|
562
|
+
case 'Validated':
|
|
563
|
+
return 'Validated and sign-off clear. Ready to promote to production serving.';
|
|
564
|
+
case 'Published':
|
|
565
|
+
return 'Active in production serving. Archiving will detach live predictions.';
|
|
566
|
+
case 'Archived':
|
|
567
|
+
return 'Archived snapshot. Preserved for governance and historical auditing.';
|
|
568
|
+
default:
|
|
569
|
+
return '';
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
requestPromote(targetStatus) {
|
|
573
|
+
if (!this.model)
|
|
574
|
+
return;
|
|
575
|
+
this.pendingReason = '';
|
|
576
|
+
this.pending = {
|
|
577
|
+
modelId: this.model.ID,
|
|
578
|
+
modelName: `${this.modelDisplayName} v${this.model.Version}`,
|
|
579
|
+
targetStatus,
|
|
580
|
+
leakageFlagged: this.leakageFlagged && targetStatus !== 'Archived',
|
|
581
|
+
};
|
|
582
|
+
this.cdr.markForCheck();
|
|
583
|
+
}
|
|
584
|
+
cancelPromote() {
|
|
585
|
+
if (this.busy)
|
|
586
|
+
return;
|
|
587
|
+
this.pending = null;
|
|
588
|
+
this.pendingReason = '';
|
|
589
|
+
this.cdr.markForCheck();
|
|
590
|
+
}
|
|
591
|
+
get pendingTitle() {
|
|
592
|
+
if (!this.pending)
|
|
593
|
+
return '';
|
|
594
|
+
return this.pending.targetStatus === 'Archived' ? 'Archive model' : `Promote to ${this.pending.targetStatus}`;
|
|
595
|
+
}
|
|
596
|
+
get pendingIcon() {
|
|
597
|
+
if (!this.pending)
|
|
598
|
+
return 'fa-solid fa-check';
|
|
599
|
+
return this.pending.targetStatus === 'Archived' ? 'fa-solid fa-box-archive' : 'fa-solid fa-arrow-up';
|
|
600
|
+
}
|
|
601
|
+
get pendingConfirmLabel() {
|
|
602
|
+
if (!this.pending)
|
|
603
|
+
return 'Confirm';
|
|
604
|
+
return this.pending.targetStatus === 'Archived' ? 'Archive' : `Promote to ${this.pending.targetStatus}`;
|
|
605
|
+
}
|
|
606
|
+
get pendingVariant() {
|
|
607
|
+
if (!this.pending)
|
|
608
|
+
return 'info';
|
|
609
|
+
if (this.pending.targetStatus === 'Archived')
|
|
610
|
+
return 'warn';
|
|
611
|
+
return this.pending.leakageFlagged ? 'warn' : 'info';
|
|
612
|
+
}
|
|
613
|
+
get pendingMessage() {
|
|
614
|
+
if (!this.pending)
|
|
615
|
+
return '';
|
|
616
|
+
const name = this.escapeHtml(this.pending.modelName);
|
|
617
|
+
if (this.pending.targetStatus === 'Archived') {
|
|
618
|
+
return `Archive <strong>${name}</strong>? This detaches any active scoring bindings and removes it from production scoring. The model artifact remains immutable and recoverable.`;
|
|
619
|
+
}
|
|
620
|
+
if (this.pending.leakageFlagged) {
|
|
621
|
+
return `<strong>${name}</strong> is flagged for possible target leakage. Publishing requires an explicit sign-off — confirm you understand the risk and provide a reason below.`;
|
|
622
|
+
}
|
|
623
|
+
return `Promote <strong>${name}</strong> to <strong>${this.pending.targetStatus}</strong>? This changes only the lifecycle state — the trained weights never change.`;
|
|
624
|
+
}
|
|
625
|
+
async confirmPromote(reason) {
|
|
626
|
+
if (!this.pending || this.busy || !this.model)
|
|
627
|
+
return;
|
|
628
|
+
this.busy = true;
|
|
629
|
+
this.cdr.markForCheck();
|
|
630
|
+
const { modelId, modelName, targetStatus, leakageFlagged } = this.pending;
|
|
631
|
+
try {
|
|
632
|
+
const op = new PredictiveStudioPromoteModelOperation();
|
|
633
|
+
const result = await op.Execute({
|
|
634
|
+
modelId,
|
|
635
|
+
targetStatus,
|
|
636
|
+
signOff: leakageFlagged ? true : undefined,
|
|
637
|
+
reason: reason || undefined,
|
|
638
|
+
}, { provider: this.provider ?? undefined, user: this.currentUser ?? undefined });
|
|
639
|
+
if (result.Success && result.Output?.promoted) {
|
|
640
|
+
const newStatus = result.Output.status;
|
|
641
|
+
this.model.Status = newStatus;
|
|
642
|
+
this.notifications.CreateSimpleNotification(`${modelName} → ${newStatus}`, 'success', 3500);
|
|
643
|
+
this.statusChanged.emit({ modelId, newStatus });
|
|
644
|
+
this.pending = null;
|
|
645
|
+
this.pendingReason = '';
|
|
646
|
+
}
|
|
647
|
+
else {
|
|
648
|
+
this.notifications.CreateSimpleNotification(result.ErrorMessage || `Could not promote ${modelName} (status: ${result.Output?.status ?? 'unchanged'}).`, 'error', 5000);
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
catch (e) {
|
|
652
|
+
this.notifications.CreateSimpleNotification(`Promotion failed: ${e instanceof Error ? e.message : String(e)}`, 'error', 5000);
|
|
653
|
+
}
|
|
654
|
+
finally {
|
|
655
|
+
this.busy = false;
|
|
656
|
+
this.cdr.markForCheck();
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
escapeHtml(value) {
|
|
660
|
+
return value
|
|
661
|
+
.replace(/&/g, '&')
|
|
662
|
+
.replace(/</g, '<')
|
|
663
|
+
.replace(/>/g, '>')
|
|
664
|
+
.replace(/"/g, '"');
|
|
665
|
+
}
|
|
666
|
+
static { this.ɵfac = function PSModelDetailComponent_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || PSModelDetailComponent)(); }; }
|
|
667
|
+
static { this.ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: PSModelDetailComponent, selectors: [["ps-model-detail"]], inputs: { model: "model", provider: "provider", currentUser: "currentUser", showActions: "showActions", displayName: "displayName" }, outputs: { statusChanged: "statusChanged" }, decls: 3, vars: 1, consts: [["data-testid", "ps-model-detail", 1, "ps-model-detail"], [1, "ps-empty", 2, "padding", "32px", "text-align", "center"], [1, "ps-card"], [1, "ps-card-body", "dh"], [1, "big-ico"], [1, "fa-solid", "fa-cube"], [2, "flex", "1", "min-width", "0"], ["data-testid", "ps-registry-detail-name"], [1, "ps-tag", "ps-mono"], [1, "ps-muted", "ps-small", "sub"], [1, "ps-badge"], [1, "ps-card-body"], [1, "ps-section-title"], [1, "ps-stepper"], [1, "ps-small", "ps-muted", 2, "margin-top", "10px"], [1, "ps-card-head"], [1, "ps-muted", "ps-small"], [1, "metric-pair"], [1, "mtile"], [1, "v"], [1, "mtile", "honest"], [1, "stat-bar"], [1, "ps-small", "ps-muted", 2, "margin-top", "6px"], [1, "ps-small", "ps-muted"], [1, "ps-callout", "gate", 3, "success", "warn"], [1, "ps-modal-backdrop"], [1, "ps-step"], [1, "pip"], [1, "fa-solid", "fa-check"], [1, "ln"], [1, "b"], [1, "ps-fbar"], [1, "name", "ps-small", 3, "title"], [1, "track"], [1, "ps-mono"], [1, "ps-callout", "gate"], [1, "ps-small", 2, "margin-top", "6px"], [1, "fa-solid", "fa-pen"], [1, "ps-card-body", "ps-row", 2, "align-items", "center", "flex-wrap", "wrap", "gap", "8px"], [1, "ps-muted", "ps-small", 2, "flex", "1"], ["mjButton", "", "variant", "secondary", "size", "sm", "data-testid", "ps-registry-validate"], ["mjButton", "", "variant", "primary", "size", "sm", "data-testid", "ps-registry-promote"], ["mjButton", "", "variant", "secondary", "size", "sm", "data-testid", "ps-registry-archive"], ["mjButton", "", "variant", "secondary", "size", "sm", "data-testid", "ps-registry-validate", 3, "click"], [1, "fa-solid", "fa-circle-check"], ["mjButton", "", "variant", "primary", "size", "sm", "data-testid", "ps-registry-promote", 3, "click"], [1, "fa-solid", "fa-arrow-up"], ["mjButton", "", "variant", "secondary", "size", "sm", "data-testid", "ps-registry-archive", 3, "click"], [1, "fa-solid", "fa-box-archive"], [1, "ps-modal-backdrop", 3, "click"], ["data-testid", "ps-confirm-modal", 1, "ps-modal", 3, "click"], [1, "ps-modal-head"], [1, "ico"], [1, "ps-modal-body"], [3, "innerHTML"], [1, "ps-field", 2, "margin-top", "12px"], [1, "ps-modal-foot"], ["mjButton", "", "size", "sm", 3, "click", "variant", "disabled"], [1, "fa-solid", "fa-circle-notch", "fa-spin"], [3, "class"], [1, "ps-spacer", 2, "flex", "1"], ["mjButton", "", "variant", "secondary", "size", "sm", 3, "click", "disabled"], ["rows", "3", 1, "ps-input", 3, "ngModelChange", "ngModel", "placeholder", "disabled"], [1, "ps-empty-ico"], [1, "ps-muted"]], template: function PSModelDetailComponent_Template(rf, ctx) { if (rf & 1) {
|
|
668
|
+
i0.ɵɵelementStart(0, "div", 0);
|
|
669
|
+
i0.ɵɵconditionalCreate(1, PSModelDetailComponent_Conditional_1_Template, 59, 18)(2, PSModelDetailComponent_Conditional_2_Template, 7, 0, "div", 1);
|
|
670
|
+
i0.ɵɵelementEnd();
|
|
671
|
+
} if (rf & 2) {
|
|
672
|
+
i0.ɵɵadvance();
|
|
673
|
+
i0.ɵɵconditional(ctx.model ? 1 : 2);
|
|
674
|
+
} }, dependencies: [CommonModule, FormsModule, i1.DefaultValueAccessor, i1.NgControlStatus, i1.NgModel, MJButtonDirective], styles: ["\n\n.ps-model-detail[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n gap: 16px;\n width: 100%;\n color: var(--mj-text-primary);\n background: transparent;\n font-family: inherit;\n}\n\n\n.ps-model-detail[_ngcontent-%COMP%] .ps-row[_ngcontent-%COMP%] { display: flex; gap: 16px; }\n.ps-model-detail[_ngcontent-%COMP%] .ps-col[_ngcontent-%COMP%] { display: flex; flex-direction: column; gap: 16px; }\n.ps-model-detail[_ngcontent-%COMP%] .ps-spacer[_ngcontent-%COMP%] { flex: 1; }\n.ps-model-detail[_ngcontent-%COMP%] .ps-muted[_ngcontent-%COMP%] { color: var(--mj-text-muted); }\n.ps-model-detail[_ngcontent-%COMP%] .ps-small[_ngcontent-%COMP%] { font-size: 12.5px; }\n.ps-model-detail[_ngcontent-%COMP%] .ps-mono[_ngcontent-%COMP%] { font-family: ui-monospace, 'SF Mono', Menlo, Consolas, monospace; font-size: 12.5px; }\n.ps-model-detail[_ngcontent-%COMP%] .ps-section-title[_ngcontent-%COMP%] {\n font-size: 12px;\n text-transform: uppercase;\n letter-spacing: .06em;\n color: var(--mj-text-muted);\n margin: 4px 0 2px;\n font-weight: 700;\n}\n\n\n.ps-model-detail[_ngcontent-%COMP%] .ps-card[_ngcontent-%COMP%] {\n background: var(--mj-bg-surface);\n border: 1px solid var(--mj-border-default);\n border-radius: 8px;\n overflow: hidden;\n}\n.ps-model-detail[_ngcontent-%COMP%] .ps-card-head[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 14px 16px;\n border-bottom: 1px solid var(--mj-border-subtle, var(--mj-border-default));\n}\n.ps-model-detail[_ngcontent-%COMP%] .ps-card-head[_ngcontent-%COMP%] h3[_ngcontent-%COMP%] {\n font-size: 14.5px;\n margin: 0;\n font-weight: 650;\n color: var(--mj-text-primary);\n}\n.ps-model-detail[_ngcontent-%COMP%] .ps-card-body[_ngcontent-%COMP%] {\n padding: 16px;\n}\n\n\n.ps-model-detail[_ngcontent-%COMP%] .ps-badge[_ngcontent-%COMP%] {\n display: inline-flex;\n align-items: center;\n gap: 5px;\n padding: 3px 10px;\n border-radius: 999px;\n font-size: 11.5px;\n font-weight: 650;\n line-height: 1.5;\n}\n.ps-model-detail[_ngcontent-%COMP%] .ps-badge.gray[_ngcontent-%COMP%] { background: var(--mj-bg-surface-sunken, #f3f4f6); color: var(--mj-text-secondary); }\n.ps-model-detail[_ngcontent-%COMP%] .ps-badge.blue[_ngcontent-%COMP%] { background: var(--mj-status-info-bg, #eff6ff); color: var(--mj-status-info-text, #1d4ed8); }\n.ps-model-detail[_ngcontent-%COMP%] .ps-badge.green[_ngcontent-%COMP%] { background: var(--mj-status-success-bg, #f0fdf4); color: var(--mj-status-success-text, #15803d); }\n.ps-model-detail[_ngcontent-%COMP%] .ps-badge.amber[_ngcontent-%COMP%] { background: var(--mj-status-warning-bg, #fffbeb); color: var(--mj-status-warning-text, #b45309); }\n.ps-model-detail[_ngcontent-%COMP%] .ps-badge.red[_ngcontent-%COMP%] { background: var(--mj-status-error-bg, #fef2f2); color: var(--mj-status-error-text, #b91c1c); }\n\n.ps-model-detail[_ngcontent-%COMP%] .ps-tag[_ngcontent-%COMP%] {\n display: inline-flex;\n align-items: center;\n padding: 2px 7px;\n border-radius: 4px;\n font-size: 11px;\n font-weight: 600;\n background: var(--mj-bg-surface-sunken, #f3f4f6);\n color: var(--mj-text-secondary);\n}\n\n\n.ps-model-detail[_ngcontent-%COMP%] .dh[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 14px;\n}\n.ps-model-detail[_ngcontent-%COMP%] .big-ico[_ngcontent-%COMP%] {\n width: 48px;\n height: 48px;\n border-radius: 12px;\n display: grid;\n place-items: center;\n background: var(--mj-brand-primary);\n color: var(--mj-text-inverse);\n font-size: 20px;\n flex: 0 0 auto;\n}\n.ps-model-detail[_ngcontent-%COMP%] .dh[_ngcontent-%COMP%] h2[_ngcontent-%COMP%] {\n font-size: 18px;\n color: var(--mj-text-primary);\n display: flex;\n align-items: center;\n gap: 8px;\n margin: 0;\n font-weight: 700;\n}\n.ps-model-detail[_ngcontent-%COMP%] .dh[_ngcontent-%COMP%] .sub[_ngcontent-%COMP%] {\n margin-top: 2px;\n}\n\n\n.ps-model-detail[_ngcontent-%COMP%] .ps-stepper[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 8px;\n margin-top: 8px;\n flex-wrap: wrap;\n}\n.ps-model-detail[_ngcontent-%COMP%] .ps-step[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 8px;\n font-size: 13px;\n font-weight: 600;\n color: var(--mj-text-muted);\n}\n.ps-model-detail[_ngcontent-%COMP%] .ps-step.curr[_ngcontent-%COMP%] {\n color: var(--mj-text-primary);\n}\n.ps-model-detail[_ngcontent-%COMP%] .ps-step.done[_ngcontent-%COMP%] {\n color: var(--mj-status-success, #15803d);\n}\n.ps-model-detail[_ngcontent-%COMP%] .ps-step[_ngcontent-%COMP%] .pip[_ngcontent-%COMP%] {\n width: 22px;\n height: 22px;\n border-radius: 50%;\n display: grid;\n place-items: center;\n font-size: 11px;\n border: 1.5px solid var(--mj-border-default);\n background: var(--mj-bg-surface);\n}\n.ps-model-detail[_ngcontent-%COMP%] .ps-step.curr[_ngcontent-%COMP%] .pip[_ngcontent-%COMP%] {\n border-color: var(--mj-brand-primary);\n color: var(--mj-brand-primary);\n font-weight: 700;\n}\n.ps-model-detail[_ngcontent-%COMP%] .ps-step.done[_ngcontent-%COMP%] .pip[_ngcontent-%COMP%] {\n background: var(--mj-status-success, #15803d);\n border-color: var(--mj-status-success, #15803d);\n color: var(--mj-text-inverse);\n}\n.ps-model-detail[_ngcontent-%COMP%] .ps-stepper[_ngcontent-%COMP%] .ln[_ngcontent-%COMP%] {\n flex: 1;\n min-width: 16px;\n height: 2px;\n background: var(--mj-border-default);\n}\n\n\n.ps-model-detail[_ngcontent-%COMP%] .metric-pair[_ngcontent-%COMP%] {\n display: grid;\n grid-template-columns: 1fr 1fr;\n gap: 12px;\n}\n@media (max-width: 640px) {\n .ps-model-detail[_ngcontent-%COMP%] .metric-pair[_ngcontent-%COMP%] {\n grid-template-columns: 1fr;\n }\n}\n.ps-model-detail[_ngcontent-%COMP%] .mtile[_ngcontent-%COMP%] {\n border: 1px solid var(--mj-border-default);\n border-radius: 9px;\n padding: 14px;\n background: var(--mj-bg-surface);\n}\n.ps-model-detail[_ngcontent-%COMP%] .mtile[_ngcontent-%COMP%] .v[_ngcontent-%COMP%] {\n font-size: 28px;\n font-weight: 720;\n color: var(--mj-text-primary);\n letter-spacing: -.02em;\n}\n.ps-model-detail[_ngcontent-%COMP%] .mtile.honest[_ngcontent-%COMP%] {\n border-color: var(--mj-brand-primary);\n background: color-mix(in srgb, var(--mj-brand-primary) 6%, var(--mj-bg-surface));\n}\n.ps-model-detail[_ngcontent-%COMP%] .mtile.honest[_ngcontent-%COMP%] .v[_ngcontent-%COMP%] {\n color: var(--mj-brand-primary);\n}\n\n.ps-model-detail[_ngcontent-%COMP%] .stat-bar[_ngcontent-%COMP%] {\n display: flex;\n gap: 10px;\n margin-top: 14px;\n flex-wrap: wrap;\n}\n.ps-model-detail[_ngcontent-%COMP%] .stat-bar[_ngcontent-%COMP%] .b[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n gap: 2px;\n padding: 8px 12px;\n border: 1px solid var(--mj-border-subtle, var(--mj-border-default));\n border-radius: 8px;\n flex: 1;\n min-width: 90px;\n}\n.ps-model-detail[_ngcontent-%COMP%] .stat-bar[_ngcontent-%COMP%] .b[_ngcontent-%COMP%] strong[_ngcontent-%COMP%] {\n color: var(--mj-text-primary);\n font-size: 15px;\n}\n\n\n.ps-model-detail[_ngcontent-%COMP%] .ps-fbar[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 10px;\n margin-bottom: 8px;\n}\n.ps-model-detail[_ngcontent-%COMP%] .ps-fbar[_ngcontent-%COMP%] .name[_ngcontent-%COMP%] {\n width: 170px;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n flex-shrink: 0;\n color: var(--mj-text-secondary);\n}\n.ps-model-detail[_ngcontent-%COMP%] .ps-fbar[_ngcontent-%COMP%] .track[_ngcontent-%COMP%] {\n flex: 1;\n height: 8px;\n background: var(--mj-bg-surface-sunken, #f3f4f6);\n border-radius: 999px;\n overflow: hidden;\n}\n.ps-model-detail[_ngcontent-%COMP%] .ps-fbar[_ngcontent-%COMP%] .track[_ngcontent-%COMP%] span[_ngcontent-%COMP%] {\n display: block;\n height: 100%;\n background: var(--mj-brand-primary);\n border-radius: 999px;\n}\n.ps-model-detail[_ngcontent-%COMP%] .ps-fbar[_ngcontent-%COMP%] .track[_ngcontent-%COMP%] span.warn[_ngcontent-%COMP%] {\n background: var(--mj-status-warning, #d98213);\n}\n\n\n.ps-model-detail[_ngcontent-%COMP%] .ps-callout[_ngcontent-%COMP%] {\n display: flex;\n gap: 12px;\n padding: 14px 16px;\n border-radius: 8px;\n border: 1px solid var(--mj-border-default);\n background: var(--mj-bg-surface);\n}\n.ps-model-detail[_ngcontent-%COMP%] .ps-callout.gate[_ngcontent-%COMP%] {\n align-items: flex-start;\n}\n.ps-model-detail[_ngcontent-%COMP%] .ps-callout.gate[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n font-size: 20px;\n margin-top: 2px;\n}\n.ps-model-detail[_ngcontent-%COMP%] .ps-callout.success[_ngcontent-%COMP%] {\n background: var(--mj-status-success-bg, #f0fdf4);\n border-color: color-mix(in srgb, var(--mj-status-success) 25%, transparent);\n color: var(--mj-status-success-text, #15803d);\n}\n.ps-model-detail[_ngcontent-%COMP%] .ps-callout.warn[_ngcontent-%COMP%] {\n background: var(--mj-status-warning-bg, #fffbeb);\n border-color: color-mix(in srgb, var(--mj-status-warning) 25%, transparent);\n color: var(--mj-status-warning-text, #b45309);\n}\n\n\n.ps-model-detail[_ngcontent-%COMP%] .ps-modal-backdrop[_ngcontent-%COMP%] {\n position: fixed;\n inset: 0;\n background: rgba(0, 0, 0, 0.45);\n z-index: 10000;\n display: flex;\n align-items: center;\n justify-content: center;\n padding: 16px;\n}\n.ps-model-detail[_ngcontent-%COMP%] .ps-modal[_ngcontent-%COMP%] {\n background: var(--mj-bg-surface);\n border: 1px solid var(--mj-border-default);\n border-radius: 12px;\n width: 100%;\n max-width: 480px;\n box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 8px 10px -6px rgba(0, 0, 0, 0.1);\n overflow: hidden;\n}\n.ps-model-detail[_ngcontent-%COMP%] .ps-modal-head[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 12px;\n padding: 16px 20px;\n border-bottom: 1px solid var(--mj-border-subtle, var(--mj-border-default));\n}\n.ps-model-detail[_ngcontent-%COMP%] .ps-modal-head[_ngcontent-%COMP%] .ico[_ngcontent-%COMP%] {\n width: 32px;\n height: 32px;\n border-radius: 8px;\n display: grid;\n place-items: center;\n background: var(--mj-brand-primary);\n color: var(--mj-text-inverse);\n font-size: 15px;\n}\n.ps-model-detail[_ngcontent-%COMP%] .ps-modal-head.warn[_ngcontent-%COMP%] .ico[_ngcontent-%COMP%] {\n background: var(--mj-status-warning, #d98213);\n}\n.ps-model-detail[_ngcontent-%COMP%] .ps-modal-head.danger[_ngcontent-%COMP%] .ico[_ngcontent-%COMP%] {\n background: var(--mj-status-error, #b91c1c);\n}\n.ps-model-detail[_ngcontent-%COMP%] .ps-modal-head[_ngcontent-%COMP%] h3[_ngcontent-%COMP%] {\n margin: 0;\n font-size: 16px;\n font-weight: 700;\n}\n.ps-model-detail[_ngcontent-%COMP%] .ps-modal-body[_ngcontent-%COMP%] {\n padding: 20px;\n font-size: 13.5px;\n line-height: 1.5;\n}\n.ps-model-detail[_ngcontent-%COMP%] .ps-modal-foot[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 10px;\n padding: 14px 20px;\n border-top: 1px solid var(--mj-border-subtle, var(--mj-border-default));\n background: var(--mj-bg-surface-sunken, #f9fafb);\n}\n.ps-model-detail[_ngcontent-%COMP%] .ps-field[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n gap: 6px;\n}\n.ps-model-detail[_ngcontent-%COMP%] .ps-field[_ngcontent-%COMP%] label[_ngcontent-%COMP%] {\n font-size: 12px;\n font-weight: 600;\n color: var(--mj-text-secondary);\n}\n.ps-model-detail[_ngcontent-%COMP%] .ps-input[_ngcontent-%COMP%] {\n width: 100%;\n padding: 8px 10px;\n border: 1px solid var(--mj-border-default);\n border-radius: 6px;\n background: var(--mj-bg-surface);\n color: var(--mj-text-primary);\n font-family: inherit;\n font-size: 13px;\n resize: vertical;\n}\n.ps-model-detail[_ngcontent-%COMP%] .ps-input[_ngcontent-%COMP%]:focus {\n outline: none;\n border-color: var(--mj-brand-primary);\n}"], changeDetection: 0 }); }
|
|
675
|
+
}
|
|
676
|
+
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(PSModelDetailComponent, [{
|
|
677
|
+
type: Component,
|
|
678
|
+
args: [{ standalone: true, selector: 'ps-model-detail', imports: [CommonModule, FormsModule, MJButtonDirective], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"ps-model-detail\" data-testid=\"ps-model-detail\">\n @if (model) {\n <!-- 1. Header Card -->\n <div class=\"ps-card\">\n <div class=\"ps-card-body dh\">\n <div class=\"big-ico\"><i class=\"fa-solid fa-cube\"></i></div>\n <div style=\"flex:1;min-width:0;\">\n <h2 data-testid=\"ps-registry-detail-name\">\n {{ modelDisplayName }}\n <span class=\"ps-tag ps-mono\">v{{ model.Version }}</span>\n </h2>\n <div class=\"ps-muted ps-small sub\">\n {{ model.Algorithm || 'Model' }}{{ targetSuffix }} • immutable snapshot\n </div>\n </div>\n <span class=\"ps-badge\" [class]=\"statusBadgeClass(model.Status)\">{{ model.Status }}</span>\n </div>\n </div>\n\n <!-- 2. Lifecycle Stepper -->\n <div class=\"ps-card\">\n <div class=\"ps-card-body\">\n <div class=\"ps-section-title\">Lifecycle</div>\n <div class=\"ps-stepper\">\n @for (step of lifecycleSteps; track step; let last = $last) {\n <div class=\"ps-step\" [class.done]=\"stepState(step) === 'done'\" [class.curr]=\"stepState(step) === 'curr'\">\n <span class=\"pip\">\n @if (stepState(step) === 'done') {\n <i class=\"fa-solid fa-check\"></i>\n } @else {\n {{ $index + 1 }}\n }\n </span>\n {{ step }}\n </div>\n @if (!last) {\n <span class=\"ln\"></span>\n }\n }\n </div>\n <div class=\"ps-small ps-muted\" style=\"margin-top:10px\">\n Models are immutable once registered; promotion only changes lifecycle state, never weights.\n </div>\n </div>\n </div>\n\n <!-- 3. Performance Metrics -->\n <div class=\"ps-card\">\n <div class=\"ps-card-head\">\n <h3>Performance</h3>\n <span class=\"ps-muted ps-small\">Holdout = held-out test fold, never seen in training</span>\n </div>\n <div class=\"ps-card-body\">\n <div class=\"metric-pair\">\n <div class=\"mtile\">\n <div class=\"ps-section-title\">Train {{ primaryScoreLabel }}</div>\n <div class=\"v\">{{ trainPrimaryScore }}</div>\n <div class=\"ps-muted ps-small\">in-sample • optimistic</div>\n </div>\n <div class=\"mtile honest\">\n <div class=\"ps-section-title\">Holdout {{ primaryScoreLabel }}</div>\n <div class=\"v\">{{ holdoutPrimaryScore }}</div>\n <div class=\"ps-muted ps-small\">out-of-sample • the honest number</div>\n </div>\n </div>\n\n @if (secondaryMetrics.length > 0) {\n <div class=\"stat-bar\">\n @for (s of secondaryMetrics; track s.key) {\n <div class=\"b\">\n <span class=\"ps-muted ps-small\">{{ s.label }}</span>\n <strong>{{ s.value }}</strong>\n </div>\n }\n </div>\n } @else {\n <div class=\"ps-small ps-muted\" style=\"margin-top:6px\">No secondary metrics recorded for this model.</div>\n }\n\n @if (gapText) {\n <div class=\"ps-small ps-muted\" style=\"margin-top:10px\">\n Train\u2013holdout gap of <strong>{{ gapText }}</strong> {{ gapVerdict }}.\n </div>\n }\n </div>\n </div>\n\n <!-- 4. Feature Importance -->\n <div class=\"ps-card\">\n <div class=\"ps-card-head\">\n <h3>Feature Importance</h3>\n <span class=\"ps-muted ps-small\">{{ importanceCaption }}</span>\n </div>\n <div class=\"ps-card-body\">\n @if (importance.length > 0) {\n @for (f of importance; track f.name) {\n <div class=\"ps-fbar\">\n <span class=\"name ps-small\" [title]=\"f.name\">{{ f.name }}</span>\n <div class=\"track\">\n <span [class.warn]=\"f.warning\" [style.width.%]=\"f.pct\"></span>\n </div>\n <span class=\"ps-mono\">{{ f.value }}</span>\n </div>\n }\n } @else {\n <div class=\"ps-small ps-muted\">No feature-importance data was recorded for this model.</div>\n }\n </div>\n </div>\n\n <!-- 5. Leakage Sign-Off Gate -->\n @if (importance.length > 0) {\n <div class=\"ps-callout gate\" [class.success]=\"!leakageFlagged\" [class.warn]=\"leakageFlagged\">\n <i [class]=\"leakageFlagged ? 'fa-solid fa-triangle-exclamation' : 'fa-solid fa-shield-halved'\"></i>\n <div>\n @if (leakageFlagged) {\n <strong>Leakage flag raised.</strong> A single feature dominates \u2014 top importance is\n <strong>{{ topFeatureName }} at {{ topFeatureValue }}</strong>, at/above the\n <strong>{{ dominanceThreshold }}</strong> dominance threshold.\n <div class=\"ps-small\" style=\"margin-top:6px\">\n <i class=\"fa-solid fa-pen\"></i> Sign-off with a reason is required to publish this model.\n </div>\n } @else {\n <strong>Leakage gate clear.</strong> No single feature dominates \u2014 top importance is\n <strong>{{ topFeatureName }} at {{ topFeatureValue }}</strong>, below the\n <strong>{{ dominanceThreshold }}</strong> dominance threshold.\n <div class=\"ps-small\" style=\"margin-top:6px\">\n <i class=\"fa-solid fa-check\"></i> Sign-off required before any Validated → Published promotion.\n </div>\n }\n </div>\n </div>\n }\n\n <!-- 6. Lifecycle Actions -->\n @if (showActions) {\n <div class=\"ps-card\">\n <div class=\"ps-card-body ps-row\" style=\"align-items:center;flex-wrap:wrap;gap:8px\">\n <span class=\"ps-muted ps-small\" style=\"flex:1\">{{ actionHint }}</span>\n @if (canValidate) {\n <button mjButton variant=\"secondary\" size=\"sm\" data-testid=\"ps-registry-validate\" (click)=\"requestPromote('Validated')\">\n <i class=\"fa-solid fa-circle-check\"></i> Mark Validated\n </button>\n }\n @if (canPublish) {\n <button mjButton variant=\"primary\" size=\"sm\" data-testid=\"ps-registry-promote\" (click)=\"requestPromote('Published')\">\n <i class=\"fa-solid fa-arrow-up\"></i> Promote to Published\n </button>\n }\n @if (canArchive) {\n <button mjButton variant=\"secondary\" size=\"sm\" data-testid=\"ps-registry-archive\" (click)=\"requestPromote('Archived')\">\n <i class=\"fa-solid fa-box-archive\"></i> Archive\n </button>\n }\n </div>\n </div>\n }\n\n <!-- 7. Confirm Promotion Modal -->\n @if (pending) {\n <div class=\"ps-modal-backdrop\" (click)=\"cancelPromote()\">\n <div class=\"ps-modal\" (click)=\"$event.stopPropagation()\" data-testid=\"ps-confirm-modal\">\n <div class=\"ps-modal-head\" [class.warn]=\"pendingVariant === 'warn'\" [class.danger]=\"pendingVariant === 'danger'\">\n <span class=\"ico\"><i [class]=\"pendingIcon\"></i></span>\n <h3>{{ pendingTitle }}</h3>\n </div>\n <div class=\"ps-modal-body\">\n <div [innerHTML]=\"pendingMessage\"></div>\n @if (pending.leakageFlagged || pending.targetStatus === 'Archived') {\n <div class=\"ps-field\" style=\"margin-top:12px\">\n <label>\n {{ pending.leakageFlagged ? 'Leakage sign-off reason' : 'Reason (optional)' }}\n {{ pending.leakageFlagged ? ' *' : '' }}\n </label>\n <textarea\n class=\"ps-input\"\n rows=\"3\"\n [(ngModel)]=\"pendingReason\"\n [placeholder]=\"pending.leakageFlagged ? 'Explain why this model is safe to publish despite the leakage flag\u2026' : 'Add an optional note for the record\u2026'\"\n [disabled]=\"busy\"></textarea>\n </div>\n }\n </div>\n <div class=\"ps-modal-foot\">\n <button\n mjButton\n [variant]=\"pendingVariant === 'danger' ? 'danger' : 'primary'\"\n size=\"sm\"\n [disabled]=\"busy || (pending.leakageFlagged && !pendingReason.trim())\"\n (click)=\"confirmPromote(pendingReason.trim())\">\n @if (busy) {\n <i class=\"fa-solid fa-circle-notch fa-spin\"></i>\n } @else {\n <i [class]=\"pendingIcon\"></i>\n }\n {{ pendingConfirmLabel }}\n </button>\n <span class=\"ps-spacer\" style=\"flex:1\"></span>\n <button mjButton variant=\"secondary\" size=\"sm\" [disabled]=\"busy\" (click)=\"cancelPromote()\">\n Cancel\n </button>\n </div>\n </div>\n </div>\n }\n } @else {\n <div class=\"ps-empty\" style=\"padding: 32px; text-align: center;\">\n <span class=\"ps-empty-ico\"><i class=\"fa-solid fa-cube\"></i></span>\n <h3>No Model Selected</h3>\n <p class=\"ps-muted\">Select an ML Model from the list to inspect its configuration and metrics.</p>\n </div>\n }\n</div>\n", styles: ["/* PSModelDetailComponent Styles \u2014 Design Token Compliant */\n\n.ps-model-detail {\n display: flex;\n flex-direction: column;\n gap: 16px;\n width: 100%;\n color: var(--mj-text-primary);\n background: transparent;\n font-family: inherit;\n}\n\n/* ---- generic helpers ---- */\n.ps-model-detail .ps-row { display: flex; gap: 16px; }\n.ps-model-detail .ps-col { display: flex; flex-direction: column; gap: 16px; }\n.ps-model-detail .ps-spacer { flex: 1; }\n.ps-model-detail .ps-muted { color: var(--mj-text-muted); }\n.ps-model-detail .ps-small { font-size: 12.5px; }\n.ps-model-detail .ps-mono { font-family: ui-monospace, 'SF Mono', Menlo, Consolas, monospace; font-size: 12.5px; }\n.ps-model-detail .ps-section-title {\n font-size: 12px;\n text-transform: uppercase;\n letter-spacing: .06em;\n color: var(--mj-text-muted);\n margin: 4px 0 2px;\n font-weight: 700;\n}\n\n/* ---- cards ---- */\n.ps-model-detail .ps-card {\n background: var(--mj-bg-surface);\n border: 1px solid var(--mj-border-default);\n border-radius: 8px;\n overflow: hidden;\n}\n.ps-model-detail .ps-card-head {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 14px 16px;\n border-bottom: 1px solid var(--mj-border-subtle, var(--mj-border-default));\n}\n.ps-model-detail .ps-card-head h3 {\n font-size: 14.5px;\n margin: 0;\n font-weight: 650;\n color: var(--mj-text-primary);\n}\n.ps-model-detail .ps-card-body {\n padding: 16px;\n}\n\n/* ---- badges & tags ---- */\n.ps-model-detail .ps-badge {\n display: inline-flex;\n align-items: center;\n gap: 5px;\n padding: 3px 10px;\n border-radius: 999px;\n font-size: 11.5px;\n font-weight: 650;\n line-height: 1.5;\n}\n.ps-model-detail .ps-badge.gray { background: var(--mj-bg-surface-sunken, #f3f4f6); color: var(--mj-text-secondary); }\n.ps-model-detail .ps-badge.blue { background: var(--mj-status-info-bg, #eff6ff); color: var(--mj-status-info-text, #1d4ed8); }\n.ps-model-detail .ps-badge.green { background: var(--mj-status-success-bg, #f0fdf4); color: var(--mj-status-success-text, #15803d); }\n.ps-model-detail .ps-badge.amber { background: var(--mj-status-warning-bg, #fffbeb); color: var(--mj-status-warning-text, #b45309); }\n.ps-model-detail .ps-badge.red { background: var(--mj-status-error-bg, #fef2f2); color: var(--mj-status-error-text, #b91c1c); }\n\n.ps-model-detail .ps-tag {\n display: inline-flex;\n align-items: center;\n padding: 2px 7px;\n border-radius: 4px;\n font-size: 11px;\n font-weight: 600;\n background: var(--mj-bg-surface-sunken, #f3f4f6);\n color: var(--mj-text-secondary);\n}\n\n/* ---- detail header ---- */\n.ps-model-detail .dh {\n display: flex;\n align-items: center;\n gap: 14px;\n}\n.ps-model-detail .big-ico {\n width: 48px;\n height: 48px;\n border-radius: 12px;\n display: grid;\n place-items: center;\n background: var(--mj-brand-primary);\n color: var(--mj-text-inverse);\n font-size: 20px;\n flex: 0 0 auto;\n}\n.ps-model-detail .dh h2 {\n font-size: 18px;\n color: var(--mj-text-primary);\n display: flex;\n align-items: center;\n gap: 8px;\n margin: 0;\n font-weight: 700;\n}\n.ps-model-detail .dh .sub {\n margin-top: 2px;\n}\n\n/* ---- stepper ---- */\n.ps-model-detail .ps-stepper {\n display: flex;\n align-items: center;\n gap: 8px;\n margin-top: 8px;\n flex-wrap: wrap;\n}\n.ps-model-detail .ps-step {\n display: flex;\n align-items: center;\n gap: 8px;\n font-size: 13px;\n font-weight: 600;\n color: var(--mj-text-muted);\n}\n.ps-model-detail .ps-step.curr {\n color: var(--mj-text-primary);\n}\n.ps-model-detail .ps-step.done {\n color: var(--mj-status-success, #15803d);\n}\n.ps-model-detail .ps-step .pip {\n width: 22px;\n height: 22px;\n border-radius: 50%;\n display: grid;\n place-items: center;\n font-size: 11px;\n border: 1.5px solid var(--mj-border-default);\n background: var(--mj-bg-surface);\n}\n.ps-model-detail .ps-step.curr .pip {\n border-color: var(--mj-brand-primary);\n color: var(--mj-brand-primary);\n font-weight: 700;\n}\n.ps-model-detail .ps-step.done .pip {\n background: var(--mj-status-success, #15803d);\n border-color: var(--mj-status-success, #15803d);\n color: var(--mj-text-inverse);\n}\n.ps-model-detail .ps-stepper .ln {\n flex: 1;\n min-width: 16px;\n height: 2px;\n background: var(--mj-border-default);\n}\n\n/* ---- performance metrics ---- */\n.ps-model-detail .metric-pair {\n display: grid;\n grid-template-columns: 1fr 1fr;\n gap: 12px;\n}\n@media (max-width: 640px) {\n .ps-model-detail .metric-pair {\n grid-template-columns: 1fr;\n }\n}\n.ps-model-detail .mtile {\n border: 1px solid var(--mj-border-default);\n border-radius: 9px;\n padding: 14px;\n background: var(--mj-bg-surface);\n}\n.ps-model-detail .mtile .v {\n font-size: 28px;\n font-weight: 720;\n color: var(--mj-text-primary);\n letter-spacing: -.02em;\n}\n.ps-model-detail .mtile.honest {\n border-color: var(--mj-brand-primary);\n background: color-mix(in srgb, var(--mj-brand-primary) 6%, var(--mj-bg-surface));\n}\n.ps-model-detail .mtile.honest .v {\n color: var(--mj-brand-primary);\n}\n\n.ps-model-detail .stat-bar {\n display: flex;\n gap: 10px;\n margin-top: 14px;\n flex-wrap: wrap;\n}\n.ps-model-detail .stat-bar .b {\n display: flex;\n flex-direction: column;\n gap: 2px;\n padding: 8px 12px;\n border: 1px solid var(--mj-border-subtle, var(--mj-border-default));\n border-radius: 8px;\n flex: 1;\n min-width: 90px;\n}\n.ps-model-detail .stat-bar .b strong {\n color: var(--mj-text-primary);\n font-size: 15px;\n}\n\n/* ---- feature importance bars ---- */\n.ps-model-detail .ps-fbar {\n display: flex;\n align-items: center;\n gap: 10px;\n margin-bottom: 8px;\n}\n.ps-model-detail .ps-fbar .name {\n width: 170px;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n flex-shrink: 0;\n color: var(--mj-text-secondary);\n}\n.ps-model-detail .ps-fbar .track {\n flex: 1;\n height: 8px;\n background: var(--mj-bg-surface-sunken, #f3f4f6);\n border-radius: 999px;\n overflow: hidden;\n}\n.ps-model-detail .ps-fbar .track span {\n display: block;\n height: 100%;\n background: var(--mj-brand-primary);\n border-radius: 999px;\n}\n.ps-model-detail .ps-fbar .track span.warn {\n background: var(--mj-status-warning, #d98213);\n}\n\n/* ---- leakage gate callout ---- */\n.ps-model-detail .ps-callout {\n display: flex;\n gap: 12px;\n padding: 14px 16px;\n border-radius: 8px;\n border: 1px solid var(--mj-border-default);\n background: var(--mj-bg-surface);\n}\n.ps-model-detail .ps-callout.gate {\n align-items: flex-start;\n}\n.ps-model-detail .ps-callout.gate i {\n font-size: 20px;\n margin-top: 2px;\n}\n.ps-model-detail .ps-callout.success {\n background: var(--mj-status-success-bg, #f0fdf4);\n border-color: color-mix(in srgb, var(--mj-status-success) 25%, transparent);\n color: var(--mj-status-success-text, #15803d);\n}\n.ps-model-detail .ps-callout.warn {\n background: var(--mj-status-warning-bg, #fffbeb);\n border-color: color-mix(in srgb, var(--mj-status-warning) 25%, transparent);\n color: var(--mj-status-warning-text, #b45309);\n}\n\n/* ---- confirmation modal ---- */\n.ps-model-detail .ps-modal-backdrop {\n position: fixed;\n inset: 0;\n background: rgba(0, 0, 0, 0.45);\n z-index: 10000;\n display: flex;\n align-items: center;\n justify-content: center;\n padding: 16px;\n}\n.ps-model-detail .ps-modal {\n background: var(--mj-bg-surface);\n border: 1px solid var(--mj-border-default);\n border-radius: 12px;\n width: 100%;\n max-width: 480px;\n box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 8px 10px -6px rgba(0, 0, 0, 0.1);\n overflow: hidden;\n}\n.ps-model-detail .ps-modal-head {\n display: flex;\n align-items: center;\n gap: 12px;\n padding: 16px 20px;\n border-bottom: 1px solid var(--mj-border-subtle, var(--mj-border-default));\n}\n.ps-model-detail .ps-modal-head .ico {\n width: 32px;\n height: 32px;\n border-radius: 8px;\n display: grid;\n place-items: center;\n background: var(--mj-brand-primary);\n color: var(--mj-text-inverse);\n font-size: 15px;\n}\n.ps-model-detail .ps-modal-head.warn .ico {\n background: var(--mj-status-warning, #d98213);\n}\n.ps-model-detail .ps-modal-head.danger .ico {\n background: var(--mj-status-error, #b91c1c);\n}\n.ps-model-detail .ps-modal-head h3 {\n margin: 0;\n font-size: 16px;\n font-weight: 700;\n}\n.ps-model-detail .ps-modal-body {\n padding: 20px;\n font-size: 13.5px;\n line-height: 1.5;\n}\n.ps-model-detail .ps-modal-foot {\n display: flex;\n align-items: center;\n gap: 10px;\n padding: 14px 20px;\n border-top: 1px solid var(--mj-border-subtle, var(--mj-border-default));\n background: var(--mj-bg-surface-sunken, #f9fafb);\n}\n.ps-model-detail .ps-field {\n display: flex;\n flex-direction: column;\n gap: 6px;\n}\n.ps-model-detail .ps-field label {\n font-size: 12px;\n font-weight: 600;\n color: var(--mj-text-secondary);\n}\n.ps-model-detail .ps-input {\n width: 100%;\n padding: 8px 10px;\n border: 1px solid var(--mj-border-default);\n border-radius: 6px;\n background: var(--mj-bg-surface);\n color: var(--mj-text-primary);\n font-family: inherit;\n font-size: 13px;\n resize: vertical;\n}\n.ps-model-detail .ps-input:focus {\n outline: none;\n border-color: var(--mj-brand-primary);\n}\n"] }]
|
|
679
|
+
}], null, { model: [{
|
|
680
|
+
type: Input
|
|
681
|
+
}], provider: [{
|
|
682
|
+
type: Input
|
|
683
|
+
}], currentUser: [{
|
|
684
|
+
type: Input
|
|
685
|
+
}], showActions: [{
|
|
686
|
+
type: Input
|
|
687
|
+
}], statusChanged: [{
|
|
688
|
+
type: Output
|
|
689
|
+
}], displayName: [{
|
|
690
|
+
type: Input
|
|
691
|
+
}] }); })();
|
|
692
|
+
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(PSModelDetailComponent, { className: "PSModelDetailComponent", filePath: "src/lib/custom/MLModels/ps-model-detail.component.ts", lineNumber: 65 }); })();
|
|
693
|
+
//# sourceMappingURL=ps-model-detail.component.js.map
|