@memberjunction/ng-record-process-studio 6.1.3 → 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.
@@ -0,0 +1,1220 @@
1
+ /**
2
+ * @fileoverview Visual authoring component for a Feature Pipeline (Infer Record Process).
3
+ * Configures the DataFeatureSpec (Context, Outputs & Constraints, Caching) and binds
4
+ * prompt, output mapping, and watermark strategy directly on the MJRecordProcessEntity.
5
+ *
6
+ * @module @memberjunction/ng-record-process-studio
7
+ */
8
+ import { ChangeDetectionStrategy, ChangeDetectorRef, Component, EventEmitter, Input, Output, inject, } from '@angular/core';
9
+ import { CommonModule } from '@angular/common';
10
+ import { FormsModule } from '@angular/forms';
11
+ import { BaseAngularComponent } from '@memberjunction/ng-base-types';
12
+ import { LogError, RunView } from '@memberjunction/core';
13
+ import { renderConstraintBlock, validateSpec, } from '@memberjunction/feature-pipelines';
14
+ import { SafeJSONParse, UUIDsEqual } from '@memberjunction/global';
15
+ import { MJButtonDirective } from '@memberjunction/ng-ui-components';
16
+ import * as i0 from "@angular/core";
17
+ import * as i1 from "@angular/forms";
18
+ const _forTrack0 = ($index, $item) => $item.ID;
19
+ const _forTrack1 = ($index, $item) => $item.Name;
20
+ function FeaturePipelineBuilderComponent_Conditional_20_For_7_Template(rf, ctx) { if (rf & 1) {
21
+ i0.ɵɵelementStart(0, "option", 14);
22
+ i0.ɵɵtext(1);
23
+ i0.ɵɵelementEnd();
24
+ } if (rf & 2) {
25
+ const doc_r3 = ctx.$implicit;
26
+ i0.ɵɵproperty("value", doc_r3.ID);
27
+ i0.ɵɵadvance();
28
+ i0.ɵɵtextInterpolate(doc_r3.Name);
29
+ } }
30
+ function FeaturePipelineBuilderComponent_Conditional_20_Template(rf, ctx) { if (rf & 1) {
31
+ const _r1 = i0.ɵɵgetCurrentView();
32
+ i0.ɵɵelementStart(0, "div", 6)(1, "label");
33
+ i0.ɵɵtext(2, "Entity Document");
34
+ i0.ɵɵelementEnd();
35
+ i0.ɵɵelementStart(3, "select", 7);
36
+ i0.ɵɵlistener("change", function FeaturePipelineBuilderComponent_Conditional_20_Template_select_change_3_listener($event) { i0.ɵɵrestoreView(_r1); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.onEntityDocChange($event)); });
37
+ i0.ɵɵelementStart(4, "option", 13);
38
+ i0.ɵɵtext(5, "\u2014 Select Entity Document \u2014");
39
+ i0.ɵɵelementEnd();
40
+ i0.ɵɵrepeaterCreate(6, FeaturePipelineBuilderComponent_Conditional_20_For_7_Template, 2, 2, "option", 14, _forTrack0);
41
+ i0.ɵɵelementEnd()();
42
+ } if (rf & 2) {
43
+ const ctx_r1 = i0.ɵɵnextContext();
44
+ i0.ɵɵadvance(3);
45
+ i0.ɵɵproperty("value", ctx_r1.spec.Context.EntityDocumentID || "");
46
+ i0.ɵɵadvance(3);
47
+ i0.ɵɵrepeater(ctx_r1.availableDocs);
48
+ } }
49
+ function FeaturePipelineBuilderComponent_Conditional_21_Template(rf, ctx) { if (rf & 1) {
50
+ const _r4 = i0.ɵɵgetCurrentView();
51
+ i0.ɵɵelementStart(0, "div", 6)(1, "label");
52
+ i0.ɵɵtext(2, "Query ID");
53
+ i0.ɵɵelementEnd();
54
+ i0.ɵɵelementStart(3, "input", 29);
55
+ i0.ɵɵlistener("input", function FeaturePipelineBuilderComponent_Conditional_21_Template_input_input_3_listener($event) { i0.ɵɵrestoreView(_r4); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.onQueryIDChange($event)); });
56
+ i0.ɵɵelementEnd()();
57
+ } if (rf & 2) {
58
+ const ctx_r1 = i0.ɵɵnextContext();
59
+ i0.ɵɵadvance(3);
60
+ i0.ɵɵproperty("value", ctx_r1.spec.Context.QueryID || "");
61
+ } }
62
+ function FeaturePipelineBuilderComponent_For_40_Template(rf, ctx) { if (rf & 1) {
63
+ i0.ɵɵelementStart(0, "option", 14);
64
+ i0.ɵɵtext(1);
65
+ i0.ɵɵelementEnd();
66
+ } if (rf & 2) {
67
+ const p_r5 = ctx.$implicit;
68
+ i0.ɵɵproperty("value", p_r5.ID);
69
+ i0.ɵɵadvance();
70
+ i0.ɵɵtextInterpolate(p_r5.Name);
71
+ } }
72
+ function FeaturePipelineBuilderComponent_Conditional_41_Template(rf, ctx) { if (rf & 1) {
73
+ i0.ɵɵelementStart(0, "div", 6)(1, "label");
74
+ i0.ɵɵtext(2, "Prompt Description");
75
+ i0.ɵɵelementEnd();
76
+ i0.ɵɵelementStart(3, "div", 30);
77
+ i0.ɵɵtext(4);
78
+ i0.ɵɵelementEnd()();
79
+ } if (rf & 2) {
80
+ const ctx_r1 = i0.ɵɵnextContext();
81
+ i0.ɵɵadvance(4);
82
+ i0.ɵɵtextInterpolate(ctx_r1.selectedPrompt.Description || "No description provided");
83
+ } }
84
+ function FeaturePipelineBuilderComponent_Conditional_42_Template(rf, ctx) { if (rf & 1) {
85
+ i0.ɵɵelementStart(0, "div", 15)(1, "label");
86
+ i0.ɵɵtext(2, "Injected Constraint Block ");
87
+ i0.ɵɵelementStart(3, "span", 31);
88
+ i0.ɵɵtext(4, "\u2014 automatically appended to prompt instructions");
89
+ i0.ɵɵelementEnd()();
90
+ i0.ɵɵelementStart(5, "pre", 32);
91
+ i0.ɵɵtext(6);
92
+ i0.ɵɵelementEnd()();
93
+ } if (rf & 2) {
94
+ const ctx_r1 = i0.ɵɵnextContext();
95
+ i0.ɵɵadvance(6);
96
+ i0.ɵɵtextInterpolate(ctx_r1.renderedConstraintPreview);
97
+ } }
98
+ function FeaturePipelineBuilderComponent_Conditional_55_Template(rf, ctx) { if (rf & 1) {
99
+ i0.ɵɵelementStart(0, "div", 20);
100
+ i0.ɵɵelement(1, "i", 33);
101
+ i0.ɵɵelementStart(2, "span");
102
+ i0.ɵɵtext(3, "No outputs defined. Click ");
103
+ i0.ɵɵelementStart(4, "strong");
104
+ i0.ɵɵtext(5, "Add Output");
105
+ i0.ɵɵelementEnd();
106
+ i0.ɵɵtext(6, " to configure at least one output attribute.");
107
+ i0.ɵɵelementEnd()();
108
+ } }
109
+ function FeaturePipelineBuilderComponent_Conditional_56_For_2_Conditional_27_For_7_Template(rf, ctx) { if (rf & 1) {
110
+ i0.ɵɵelementStart(0, "option", 14);
111
+ i0.ɵɵtext(1);
112
+ i0.ɵɵelementEnd();
113
+ } if (rf & 2) {
114
+ const f_r9 = ctx.$implicit;
115
+ i0.ɵɵproperty("value", f_r9.Name);
116
+ i0.ɵɵadvance();
117
+ i0.ɵɵtextInterpolate2("", f_r9.DisplayName || f_r9.Name, " (", f_r9.TSType, ")");
118
+ } }
119
+ function FeaturePipelineBuilderComponent_Conditional_56_For_2_Conditional_27_Template(rf, ctx) { if (rf & 1) {
120
+ const _r8 = i0.ɵɵgetCurrentView();
121
+ i0.ɵɵelementStart(0, "div", 6)(1, "label");
122
+ i0.ɵɵtext(2, "Target Field");
123
+ i0.ɵɵelementEnd();
124
+ i0.ɵɵelementStart(3, "select", 7);
125
+ i0.ɵɵlistener("change", function FeaturePipelineBuilderComponent_Conditional_56_For_2_Conditional_27_Template_select_change_3_listener($event) { i0.ɵɵrestoreView(_r8); const $index_r7 = i0.ɵɵnextContext().$index; const ctx_r1 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r1.updateFieldTarget($index_r7, $event)); });
126
+ i0.ɵɵelementStart(4, "option", 13);
127
+ i0.ɵɵtext(5, "\u2014 Select Column \u2014");
128
+ i0.ɵɵelementEnd();
129
+ i0.ɵɵrepeaterCreate(6, FeaturePipelineBuilderComponent_Conditional_56_For_2_Conditional_27_For_7_Template, 2, 3, "option", 14, _forTrack1);
130
+ i0.ɵɵelementEnd()();
131
+ } if (rf & 2) {
132
+ const output_r10 = i0.ɵɵnextContext().$implicit;
133
+ const ctx_r1 = i0.ɵɵnextContext(2);
134
+ i0.ɵɵadvance(3);
135
+ i0.ɵɵproperty("value", output_r10.Target.EntityFieldName);
136
+ i0.ɵɵadvance(3);
137
+ i0.ɵɵrepeater(ctx_r1.entityFields);
138
+ } }
139
+ function FeaturePipelineBuilderComponent_Conditional_56_For_2_Conditional_28_For_7_Template(rf, ctx) { if (rf & 1) {
140
+ i0.ɵɵelementStart(0, "option", 14);
141
+ i0.ɵɵtext(1);
142
+ i0.ɵɵelementEnd();
143
+ } if (rf & 2) {
144
+ const e_r12 = ctx.$implicit;
145
+ i0.ɵɵproperty("value", e_r12.Name);
146
+ i0.ɵɵadvance();
147
+ i0.ɵɵtextInterpolate(e_r12.DisplayName || e_r12.Name);
148
+ } }
149
+ function FeaturePipelineBuilderComponent_Conditional_56_For_2_Conditional_28_Template(rf, ctx) { if (rf & 1) {
150
+ const _r11 = i0.ɵɵgetCurrentView();
151
+ i0.ɵɵelementStart(0, "div", 6)(1, "label");
152
+ i0.ɵɵtext(2, "Child Entity");
153
+ i0.ɵɵelementEnd();
154
+ i0.ɵɵelementStart(3, "select", 7);
155
+ i0.ɵɵlistener("change", function FeaturePipelineBuilderComponent_Conditional_56_For_2_Conditional_28_Template_select_change_3_listener($event) { i0.ɵɵrestoreView(_r11); const $index_r7 = i0.ɵɵnextContext().$index; const ctx_r1 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r1.updateChildTargetEntity($index_r7, $event)); });
156
+ i0.ɵɵelementStart(4, "option", 13);
157
+ i0.ɵɵtext(5, "\u2014 Select Entity \u2014");
158
+ i0.ɵɵelementEnd();
159
+ i0.ɵɵrepeaterCreate(6, FeaturePipelineBuilderComponent_Conditional_56_For_2_Conditional_28_For_7_Template, 2, 2, "option", 14, _forTrack0);
160
+ i0.ɵɵelementEnd()();
161
+ } if (rf & 2) {
162
+ const output_r10 = i0.ɵɵnextContext().$implicit;
163
+ const ctx_r1 = i0.ɵɵnextContext(2);
164
+ i0.ɵɵadvance(3);
165
+ i0.ɵɵproperty("value", output_r10.Target.EntityName);
166
+ i0.ɵɵadvance(3);
167
+ i0.ɵɵrepeater(ctx_r1.availableEntities);
168
+ } }
169
+ function FeaturePipelineBuilderComponent_Conditional_56_For_2_Conditional_43_Template(rf, ctx) { if (rf & 1) {
170
+ const _r13 = i0.ɵɵgetCurrentView();
171
+ i0.ɵɵelementStart(0, "div", 6)(1, "label");
172
+ i0.ɵɵtext(2, "Violation Policy");
173
+ i0.ɵɵelementEnd();
174
+ i0.ɵɵelementStart(3, "select", 7);
175
+ i0.ɵɵlistener("change", function FeaturePipelineBuilderComponent_Conditional_56_For_2_Conditional_43_Template_select_change_3_listener($event) { i0.ɵɵrestoreView(_r13); const $index_r7 = i0.ɵɵnextContext().$index; const ctx_r1 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r1.updateViolationPolicy($index_r7, $event)); });
176
+ i0.ɵɵelementStart(4, "option", 49);
177
+ i0.ɵɵtext(5, "Fail Row");
178
+ i0.ɵɵelementEnd();
179
+ i0.ɵɵelementStart(6, "option", 50);
180
+ i0.ɵɵtext(7, "Set Output to Null");
181
+ i0.ɵɵelementEnd();
182
+ i0.ɵɵelementStart(8, "option", 51);
183
+ i0.ɵɵtext(9, "Coerce / Fallback");
184
+ i0.ɵɵelementEnd()()();
185
+ } if (rf & 2) {
186
+ const output_r10 = i0.ɵɵnextContext().$implicit;
187
+ i0.ɵɵadvance(3);
188
+ i0.ɵɵproperty("value", output_r10.Constraint.OnViolation || "fail");
189
+ } }
190
+ function FeaturePipelineBuilderComponent_Conditional_56_For_2_Conditional_44_Template(rf, ctx) { if (rf & 1) {
191
+ const _r14 = i0.ɵɵgetCurrentView();
192
+ i0.ɵɵelementStart(0, "div", 15)(1, "label");
193
+ i0.ɵɵtext(2, "Allowed Values (comma separated)");
194
+ i0.ɵɵelementEnd();
195
+ i0.ɵɵelementStart(3, "input", 52);
196
+ i0.ɵɵlistener("input", function FeaturePipelineBuilderComponent_Conditional_56_For_2_Conditional_44_Template_input_input_3_listener($event) { i0.ɵɵrestoreView(_r14); const $index_r7 = i0.ɵɵnextContext().$index; const ctx_r1 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r1.updateEnumValues($index_r7, $event)); });
197
+ i0.ɵɵelementEnd()();
198
+ } if (rf & 2) {
199
+ const output_r10 = i0.ɵɵnextContext().$implicit;
200
+ const ctx_r1 = i0.ɵɵnextContext(2);
201
+ i0.ɵɵadvance(3);
202
+ i0.ɵɵproperty("value", ctx_r1.getEnumValuesCsv(output_r10.Constraint));
203
+ } }
204
+ function FeaturePipelineBuilderComponent_Conditional_56_For_2_Conditional_45_Template(rf, ctx) { if (rf & 1) {
205
+ const _r15 = i0.ɵɵgetCurrentView();
206
+ i0.ɵɵelementStart(0, "div", 48)(1, "div", 6)(2, "label");
207
+ i0.ɵɵtext(3, "Min Value");
208
+ i0.ɵɵelementEnd();
209
+ i0.ɵɵelementStart(4, "input", 53);
210
+ i0.ɵɵlistener("input", function FeaturePipelineBuilderComponent_Conditional_56_For_2_Conditional_45_Template_input_input_4_listener($event) { i0.ɵɵrestoreView(_r15); const $index_r7 = i0.ɵɵnextContext().$index; const ctx_r1 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r1.updateNumericMin($index_r7, $event)); });
211
+ i0.ɵɵelementEnd()();
212
+ i0.ɵɵelementStart(5, "div", 6)(6, "label");
213
+ i0.ɵɵtext(7, "Max Value");
214
+ i0.ɵɵelementEnd();
215
+ i0.ɵɵelementStart(8, "input", 53);
216
+ i0.ɵɵlistener("input", function FeaturePipelineBuilderComponent_Conditional_56_For_2_Conditional_45_Template_input_input_8_listener($event) { i0.ɵɵrestoreView(_r15); const $index_r7 = i0.ɵɵnextContext().$index; const ctx_r1 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r1.updateNumericMax($index_r7, $event)); });
217
+ i0.ɵɵelementEnd()()();
218
+ } if (rf & 2) {
219
+ const output_r10 = i0.ɵɵnextContext().$implicit;
220
+ const ctx_r1 = i0.ɵɵnextContext(2);
221
+ i0.ɵɵadvance(4);
222
+ i0.ɵɵproperty("value", ctx_r1.getNumericMin(output_r10.Constraint));
223
+ i0.ɵɵadvance(4);
224
+ i0.ɵɵproperty("value", ctx_r1.getNumericMax(output_r10.Constraint));
225
+ } }
226
+ function FeaturePipelineBuilderComponent_Conditional_56_For_2_Template(rf, ctx) { if (rf & 1) {
227
+ const _r6 = i0.ɵɵgetCurrentView();
228
+ i0.ɵɵelementStart(0, "div", 34)(1, "div", 35)(2, "h4");
229
+ i0.ɵɵtext(3);
230
+ i0.ɵɵelementEnd();
231
+ i0.ɵɵelementStart(4, "button", 36);
232
+ i0.ɵɵlistener("click", function FeaturePipelineBuilderComponent_Conditional_56_For_2_Template_button_click_4_listener() { const $index_r7 = i0.ɵɵrestoreView(_r6).$index; const ctx_r1 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r1.removeOutput($index_r7)); });
233
+ i0.ɵɵelement(5, "i", 37);
234
+ i0.ɵɵtext(6, " Remove ");
235
+ i0.ɵɵelementEnd()();
236
+ i0.ɵɵelementStart(7, "div", 5)(8, "div", 6)(9, "label");
237
+ i0.ɵɵtext(10, "Output Name");
238
+ i0.ɵɵelementEnd();
239
+ i0.ɵɵelementStart(11, "input", 38);
240
+ i0.ɵɵlistener("input", function FeaturePipelineBuilderComponent_Conditional_56_For_2_Template_input_input_11_listener($event) { const $index_r7 = i0.ɵɵrestoreView(_r6).$index; const ctx_r1 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r1.updateOutputProp($index_r7, "Name", $event)); });
241
+ i0.ɵɵelementEnd()();
242
+ i0.ɵɵelementStart(12, "div", 6)(13, "label");
243
+ i0.ɵɵtext(14, "JSON Path / Ref");
244
+ i0.ɵɵelementEnd();
245
+ i0.ɵɵelementStart(15, "input", 39);
246
+ i0.ɵɵlistener("input", function FeaturePipelineBuilderComponent_Conditional_56_For_2_Template_input_input_15_listener($event) { const $index_r7 = i0.ɵɵrestoreView(_r6).$index; const ctx_r1 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r1.updateOutputProp($index_r7, "Ref", $event)); });
247
+ i0.ɵɵelementEnd()();
248
+ i0.ɵɵelementStart(16, "div", 6)(17, "label");
249
+ i0.ɵɵtext(18, "Target Mode");
250
+ i0.ɵɵelementEnd();
251
+ i0.ɵɵelementStart(19, "select", 7);
252
+ i0.ɵɵlistener("change", function FeaturePipelineBuilderComponent_Conditional_56_For_2_Template_select_change_19_listener($event) { const $index_r7 = i0.ɵɵrestoreView(_r6).$index; const ctx_r1 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r1.updateTargetMode($index_r7, $event)); });
253
+ i0.ɵɵelementStart(20, "option", 40);
254
+ i0.ɵɵtext(21, "Entity Column (field)");
255
+ i0.ɵɵelementEnd();
256
+ i0.ɵɵelementStart(22, "option", 41);
257
+ i0.ɵɵtext(23, "Child Rows (child)");
258
+ i0.ɵɵelementEnd();
259
+ i0.ɵɵelementStart(24, "option", 42);
260
+ i0.ɵɵtext(25, "Taxonomy Tags (tags)");
261
+ i0.ɵɵelementEnd()()()();
262
+ i0.ɵɵelementStart(26, "div", 27);
263
+ i0.ɵɵconditionalCreate(27, FeaturePipelineBuilderComponent_Conditional_56_For_2_Conditional_27_Template, 8, 1, "div", 6);
264
+ i0.ɵɵconditionalCreate(28, FeaturePipelineBuilderComponent_Conditional_56_For_2_Conditional_28_Template, 8, 1, "div", 6);
265
+ i0.ɵɵelementStart(29, "div", 6)(30, "label");
266
+ i0.ɵɵtext(31, "Constraint Type");
267
+ i0.ɵɵelementEnd();
268
+ i0.ɵɵelementStart(32, "select", 7);
269
+ i0.ɵɵlistener("change", function FeaturePipelineBuilderComponent_Conditional_56_For_2_Template_select_change_32_listener($event) { const $index_r7 = i0.ɵɵrestoreView(_r6).$index; const ctx_r1 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r1.updateConstraintType($index_r7, $event)); });
270
+ i0.ɵɵelementStart(33, "option", 43);
271
+ i0.ɵɵtext(34, "None / Unconstrained");
272
+ i0.ɵɵelementEnd();
273
+ i0.ɵɵelementStart(35, "option", 44);
274
+ i0.ɵɵtext(36, "Enum (Allowed Values)");
275
+ i0.ɵɵelementEnd();
276
+ i0.ɵɵelementStart(37, "option", 45);
277
+ i0.ɵɵtext(38, "Numeric (Min/Max)");
278
+ i0.ɵɵelementEnd();
279
+ i0.ɵɵelementStart(39, "option", 46);
280
+ i0.ɵɵtext(40, "Boolean");
281
+ i0.ɵɵelementEnd();
282
+ i0.ɵɵelementStart(41, "option", 47);
283
+ i0.ɵɵtext(42, "Free Text");
284
+ i0.ɵɵelementEnd()()();
285
+ i0.ɵɵconditionalCreate(43, FeaturePipelineBuilderComponent_Conditional_56_For_2_Conditional_43_Template, 10, 1, "div", 6);
286
+ i0.ɵɵelementEnd();
287
+ i0.ɵɵconditionalCreate(44, FeaturePipelineBuilderComponent_Conditional_56_For_2_Conditional_44_Template, 4, 1, "div", 15);
288
+ i0.ɵɵconditionalCreate(45, FeaturePipelineBuilderComponent_Conditional_56_For_2_Conditional_45_Template, 9, 2, "div", 48);
289
+ i0.ɵɵelementEnd();
290
+ } if (rf & 2) {
291
+ const output_r10 = ctx.$implicit;
292
+ const $index_r7 = ctx.$index;
293
+ i0.ɵɵadvance(3);
294
+ i0.ɵɵtextInterpolate2("Output #", $index_r7 + 1, ": ", output_r10.Name || "(unnamed)");
295
+ i0.ɵɵadvance(8);
296
+ i0.ɵɵproperty("value", output_r10.Name);
297
+ i0.ɵɵadvance(4);
298
+ i0.ɵɵproperty("value", output_r10.Ref);
299
+ i0.ɵɵadvance(4);
300
+ i0.ɵɵproperty("value", output_r10.Target.Mode);
301
+ i0.ɵɵadvance(8);
302
+ i0.ɵɵconditional(output_r10.Target.Mode === "field" ? 27 : -1);
303
+ i0.ɵɵadvance();
304
+ i0.ɵɵconditional(output_r10.Target.Mode === "child" ? 28 : -1);
305
+ i0.ɵɵadvance(4);
306
+ i0.ɵɵproperty("value", (output_r10.Constraint == null ? null : output_r10.Constraint.Type) || "none");
307
+ i0.ɵɵadvance(11);
308
+ i0.ɵɵconditional(output_r10.Constraint ? 43 : -1);
309
+ i0.ɵɵadvance();
310
+ i0.ɵɵconditional((output_r10.Constraint == null ? null : output_r10.Constraint.Type) === "enum" ? 44 : -1);
311
+ i0.ɵɵadvance();
312
+ i0.ɵɵconditional((output_r10.Constraint == null ? null : output_r10.Constraint.Type) === "numeric" ? 45 : -1);
313
+ } }
314
+ function FeaturePipelineBuilderComponent_Conditional_56_Template(rf, ctx) { if (rf & 1) {
315
+ i0.ɵɵelementStart(0, "div", 21);
316
+ i0.ɵɵrepeaterCreate(1, FeaturePipelineBuilderComponent_Conditional_56_For_2_Template, 46, 11, "div", 34, i0.ɵɵrepeaterTrackByIndex);
317
+ i0.ɵɵelementEnd();
318
+ } if (rf & 2) {
319
+ const ctx_r1 = i0.ɵɵnextContext();
320
+ i0.ɵɵadvance();
321
+ i0.ɵɵrepeater(ctx_r1.spec.Outputs);
322
+ } }
323
+ function FeaturePipelineBuilderComponent_Conditional_90_Template(rf, ctx) { if (rf & 1) {
324
+ const _r16 = i0.ɵɵgetCurrentView();
325
+ i0.ɵɵelementStart(0, "div", 27)(1, "div", 6)(2, "label");
326
+ i0.ɵɵtext(3, "Cache Key Fields (comma separated)");
327
+ i0.ɵɵelementEnd();
328
+ i0.ɵɵelementStart(4, "input", 54);
329
+ i0.ɵɵlistener("input", function FeaturePipelineBuilderComponent_Conditional_90_Template_input_input_4_listener($event) { i0.ɵɵrestoreView(_r16); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.updateCacheKeyFields($event)); });
330
+ i0.ɵɵelementEnd()();
331
+ i0.ɵɵelementStart(5, "div", 6)(6, "label");
332
+ i0.ɵɵtext(7, "Cache TTL (Seconds)");
333
+ i0.ɵɵelementEnd();
334
+ i0.ɵɵelementStart(8, "input", 53);
335
+ i0.ɵɵlistener("input", function FeaturePipelineBuilderComponent_Conditional_90_Template_input_input_8_listener($event) { i0.ɵɵrestoreView(_r16); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.updateCacheTTL($event)); });
336
+ i0.ɵɵelementEnd()();
337
+ i0.ɵɵelementStart(9, "div", 6)(10, "label");
338
+ i0.ɵɵtext(11, "Cache Scope");
339
+ i0.ɵɵelementEnd();
340
+ i0.ɵɵelementStart(12, "select", 7);
341
+ i0.ɵɵlistener("change", function FeaturePipelineBuilderComponent_Conditional_90_Template_select_change_12_listener($event) { i0.ɵɵrestoreView(_r16); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.updateCacheScope($event)); });
342
+ i0.ɵɵelementStart(13, "option", 55);
343
+ i0.ɵɵtext(14, "Pipeline (Isolated to this process)");
344
+ i0.ɵɵelementEnd();
345
+ i0.ɵɵelementStart(15, "option", 56);
346
+ i0.ɵɵtext(16, "Prompt (Shared across processes with same prompt)");
347
+ i0.ɵɵelementEnd()()()();
348
+ } if (rf & 2) {
349
+ const ctx_r1 = i0.ɵɵnextContext();
350
+ i0.ɵɵadvance(4);
351
+ i0.ɵɵproperty("value", ctx_r1.cacheKeyFieldsCsv);
352
+ i0.ɵɵadvance(4);
353
+ i0.ɵɵproperty("value", ctx_r1.spec.Caching.TTLSeconds ?? 86400);
354
+ i0.ɵɵadvance(4);
355
+ i0.ɵɵproperty("value", ctx_r1.spec.Caching.Scope || "pipeline");
356
+ } }
357
+ function FeaturePipelineBuilderComponent_Conditional_91_For_6_Conditional_4_Template(rf, ctx) { if (rf & 1) {
358
+ i0.ɵɵelementStart(0, "span", 31);
359
+ i0.ɵɵtext(1);
360
+ i0.ɵɵelementEnd();
361
+ } if (rf & 2) {
362
+ const issue_r17 = i0.ɵɵnextContext().$implicit;
363
+ i0.ɵɵadvance();
364
+ i0.ɵɵtextInterpolate1("(Fix: ", issue_r17.FixRecommendation, ")");
365
+ } }
366
+ function FeaturePipelineBuilderComponent_Conditional_91_For_6_Template(rf, ctx) { if (rf & 1) {
367
+ i0.ɵɵelementStart(0, "li")(1, "strong");
368
+ i0.ɵɵtext(2);
369
+ i0.ɵɵelementEnd();
370
+ i0.ɵɵtext(3);
371
+ i0.ɵɵconditionalCreate(4, FeaturePipelineBuilderComponent_Conditional_91_For_6_Conditional_4_Template, 2, 1, "span", 31);
372
+ i0.ɵɵelementEnd();
373
+ } if (rf & 2) {
374
+ const issue_r17 = ctx.$implicit;
375
+ i0.ɵɵadvance(2);
376
+ i0.ɵɵtextInterpolate(issue_r17.Field ? issue_r17.Field + ": " : "");
377
+ i0.ɵɵadvance();
378
+ i0.ɵɵtextInterpolate1("", issue_r17.Message, " ");
379
+ i0.ɵɵadvance();
380
+ i0.ɵɵconditional(issue_r17.FixRecommendation ? 4 : -1);
381
+ } }
382
+ function FeaturePipelineBuilderComponent_Conditional_91_Template(rf, ctx) { if (rf & 1) {
383
+ i0.ɵɵelementStart(0, "div", 28)(1, "h4");
384
+ i0.ɵɵelement(2, "i", 57);
385
+ i0.ɵɵtext(3, " Specification Issues");
386
+ i0.ɵɵelementEnd();
387
+ i0.ɵɵelementStart(4, "ul");
388
+ i0.ɵɵrepeaterCreate(5, FeaturePipelineBuilderComponent_Conditional_91_For_6_Template, 5, 3, "li", null, i0.ɵɵrepeaterTrackByIndex);
389
+ i0.ɵɵelementEnd()();
390
+ } if (rf & 2) {
391
+ const ctx_r1 = i0.ɵɵnextContext();
392
+ i0.ɵɵadvance(5);
393
+ i0.ɵɵrepeater(ctx_r1.validationErrors);
394
+ } }
395
+ export class FeaturePipelineBuilderComponent extends BaseAngularComponent {
396
+ cdr = inject(ChangeDetectorRef);
397
+ Record = null;
398
+ EntityID = null;
399
+ SpecChange = new EventEmitter();
400
+ ValidChange = new EventEmitter();
401
+ spec = {
402
+ Name: '',
403
+ Description: '',
404
+ PromptID: '',
405
+ Context: {},
406
+ Outputs: [],
407
+ Caching: { Cacheable: false },
408
+ };
409
+ availablePrompts = [];
410
+ availableDocs = [];
411
+ availableEntities = [];
412
+ entityFields = [];
413
+ selectedPrompt = null;
414
+ contextMode = 'fields';
415
+ fieldsCsv = '';
416
+ cacheKeyFieldsCsv = '';
417
+ validationErrors = [];
418
+ renderedConstraintPreview = '';
419
+ async ngOnInit() {
420
+ this.availableEntities = [...this.ProviderToUse.Entities].sort((a, b) => (a.DisplayName || a.Name).localeCompare(b.DisplayName || b.Name));
421
+ await this.loadPrompts();
422
+ await this.loadEntityDocs();
423
+ this.syncFromRecord();
424
+ }
425
+ ngOnChanges(changes) {
426
+ if (changes['Record'] || changes['EntityID']) {
427
+ this.syncFromRecord();
428
+ }
429
+ }
430
+ syncFromRecord() {
431
+ if (this.Record) {
432
+ this.spec.Name = this.Record.Name || '';
433
+ this.spec.Description = this.Record.Description || '';
434
+ this.spec.PromptID = this.Record.PromptID || '';
435
+ if (this.Record.Configuration) {
436
+ const parsed = SafeJSONParse(this.Record.Configuration);
437
+ if (parsed && typeof parsed === 'object') {
438
+ this.spec.Outputs = Array.isArray(parsed.Outputs) ? parsed.Outputs : [];
439
+ this.spec.Context = parsed.Context ?? {};
440
+ this.spec.Caching = parsed.Caching ?? { Cacheable: false };
441
+ this.spec.ProcessorExtensionKey = parsed.ProcessorExtensionKey;
442
+ }
443
+ }
444
+ }
445
+ if (!this.spec.Outputs) {
446
+ this.spec.Outputs = [];
447
+ }
448
+ if (!this.spec.Context) {
449
+ this.spec.Context = {};
450
+ }
451
+ if (this.spec.Context.EntityDocumentID) {
452
+ this.contextMode = 'document';
453
+ }
454
+ else if (this.spec.Context.QueryID) {
455
+ this.contextMode = 'query';
456
+ }
457
+ else {
458
+ this.contextMode = 'fields';
459
+ }
460
+ this.fieldsCsv = (this.spec.Context.Fields ?? []).join(', ');
461
+ this.cacheKeyFieldsCsv = (this.spec.Caching.KeyFields ?? []).join(', ');
462
+ const targetEntityID = this.Record?.EntityID || this.EntityID;
463
+ if (targetEntityID) {
464
+ const entity = this.ProviderToUse.EntityByID(targetEntityID);
465
+ this.entityFields = entity?.Fields ? [...entity.Fields].sort((a, b) => a.Name.localeCompare(b.Name)) : [];
466
+ }
467
+ else {
468
+ this.entityFields = [];
469
+ }
470
+ if (this.Record?.PromptID) {
471
+ this.selectedPrompt = this.availablePrompts.find((p) => UUIDsEqual(p.ID, this.Record?.PromptID)) ?? null;
472
+ }
473
+ this.recomputeValidation();
474
+ this.cdr.detectChanges();
475
+ }
476
+ async loadPrompts() {
477
+ try {
478
+ const rv = RunView.FromMetadataProvider(this.ProviderToUse);
479
+ const res = await rv.RunView({
480
+ EntityName: 'MJ: AI Prompts',
481
+ Fields: ['ID', 'Name', 'Description'],
482
+ ResultType: 'simple',
483
+ });
484
+ if (res.Success && Array.isArray(res.Results)) {
485
+ this.availablePrompts = res.Results;
486
+ }
487
+ else {
488
+ this.availablePrompts = [];
489
+ LogError(`Failed to load AI Prompts: ${res.ErrorMessage || 'unknown error'}`);
490
+ }
491
+ }
492
+ catch (error) {
493
+ this.availablePrompts = [];
494
+ LogError('Error loading AI Prompts', undefined, error);
495
+ }
496
+ }
497
+ async loadEntityDocs() {
498
+ try {
499
+ const rv = RunView.FromMetadataProvider(this.ProviderToUse);
500
+ const res = await rv.RunView({
501
+ EntityName: 'MJ: Entity Documents',
502
+ Fields: ['ID', 'Name', 'EntityID'],
503
+ ResultType: 'simple',
504
+ });
505
+ if (res.Success && Array.isArray(res.Results)) {
506
+ this.availableDocs = res.Results;
507
+ }
508
+ else {
509
+ this.availableDocs = [];
510
+ LogError(`Failed to load Entity Documents: ${res.ErrorMessage || 'unknown error'}`);
511
+ }
512
+ }
513
+ catch (error) {
514
+ this.availableDocs = [];
515
+ LogError('Error loading Entity Documents', undefined, error);
516
+ }
517
+ }
518
+ onContextModeChange(event) {
519
+ this.contextMode = event.target.value;
520
+ if (!this.spec.Context)
521
+ this.spec.Context = {};
522
+ if (this.contextMode === 'fields') {
523
+ this.spec.Context.EntityDocumentID = undefined;
524
+ this.spec.Context.QueryID = undefined;
525
+ }
526
+ else if (this.contextMode === 'document') {
527
+ this.spec.Context.QueryID = undefined;
528
+ }
529
+ else if (this.contextMode === 'query') {
530
+ this.spec.Context.EntityDocumentID = undefined;
531
+ }
532
+ this.emitChanges();
533
+ }
534
+ onEntityDocChange(event) {
535
+ if (!this.spec.Context)
536
+ this.spec.Context = {};
537
+ this.spec.Context.EntityDocumentID = event.target.value;
538
+ this.emitChanges();
539
+ }
540
+ onQueryIDChange(event) {
541
+ if (!this.spec.Context)
542
+ this.spec.Context = {};
543
+ this.spec.Context.QueryID = event.target.value;
544
+ this.emitChanges();
545
+ }
546
+ onFieldsCsvChange(event) {
547
+ const val = event.target.value;
548
+ this.fieldsCsv = val;
549
+ if (!this.spec.Context)
550
+ this.spec.Context = {};
551
+ this.spec.Context.Fields = val
552
+ .split(',')
553
+ .map((s) => s.trim())
554
+ .filter((s) => s.length > 0);
555
+ this.emitChanges();
556
+ }
557
+ onPromptChange(event) {
558
+ const promptID = event.target.value;
559
+ if (this.Record) {
560
+ this.Record.PromptID = promptID;
561
+ }
562
+ this.spec.PromptID = promptID;
563
+ this.selectedPrompt = this.availablePrompts.find((p) => UUIDsEqual(p.ID, promptID)) ?? null;
564
+ this.emitChanges();
565
+ }
566
+ addOutput() {
567
+ const newOutput = {
568
+ Name: `Output_${this.spec.Outputs.length + 1}`,
569
+ Ref: '$',
570
+ Target: {
571
+ Mode: 'field',
572
+ EntityFieldName: this.entityFields.length > 0 ? this.entityFields[0].Name : '',
573
+ },
574
+ };
575
+ this.spec.Outputs.push(newOutput);
576
+ this.emitChanges();
577
+ }
578
+ removeOutput(index) {
579
+ this.spec.Outputs.splice(index, 1);
580
+ this.emitChanges();
581
+ }
582
+ updateOutputProp(index, prop, event) {
583
+ const val = event.target.value;
584
+ this.spec.Outputs[index][prop] = val;
585
+ this.emitChanges();
586
+ }
587
+ updateTargetMode(index, event) {
588
+ const mode = event.target.value;
589
+ const current = this.spec.Outputs[index];
590
+ if (mode === 'field') {
591
+ current.Target = {
592
+ Mode: 'field',
593
+ EntityFieldName: this.entityFields.length > 0 ? this.entityFields[0].Name : '',
594
+ };
595
+ }
596
+ else if (mode === 'child') {
597
+ current.Target = {
598
+ Mode: 'child',
599
+ EntityName: this.availableEntities.length > 0 ? this.availableEntities[0].Name : '',
600
+ ParentField: 'ParentID',
601
+ Map: {},
602
+ };
603
+ }
604
+ else if (mode === 'tags') {
605
+ current.Target = {
606
+ Mode: 'tags',
607
+ RootTagID: '',
608
+ };
609
+ }
610
+ this.emitChanges();
611
+ }
612
+ updateFieldTarget(index, event) {
613
+ const fieldName = event.target.value;
614
+ const target = this.spec.Outputs[index].Target;
615
+ if (target.Mode === 'field') {
616
+ target.EntityFieldName = fieldName;
617
+ }
618
+ this.emitChanges();
619
+ }
620
+ updateChildTargetEntity(index, event) {
621
+ const entityName = event.target.value;
622
+ const target = this.spec.Outputs[index].Target;
623
+ if (target.Mode === 'child') {
624
+ target.EntityName = entityName;
625
+ }
626
+ this.emitChanges();
627
+ }
628
+ updateConstraintType(index, event) {
629
+ const type = event.target.value;
630
+ const output = this.spec.Outputs[index];
631
+ if (type === 'none') {
632
+ output.Constraint = undefined;
633
+ }
634
+ else if (type === 'enum') {
635
+ output.Constraint = { Type: 'enum', Values: [], OnViolation: 'fail' };
636
+ }
637
+ else if (type === 'numeric') {
638
+ output.Constraint = { Type: 'numeric', Min: 0, Max: 100, OnViolation: 'fail' };
639
+ }
640
+ else if (type === 'boolean') {
641
+ output.Constraint = { Type: 'boolean', OnViolation: 'fail' };
642
+ }
643
+ else if (type === 'freetext') {
644
+ output.Constraint = { Type: 'freetext', MaxLength: 500, OnViolation: 'fail' };
645
+ }
646
+ this.emitChanges();
647
+ }
648
+ updateViolationPolicy(index, event) {
649
+ const policy = event.target.value;
650
+ if (this.spec.Outputs[index].Constraint) {
651
+ this.spec.Outputs[index].Constraint.OnViolation = policy;
652
+ }
653
+ this.emitChanges();
654
+ }
655
+ getEnumValuesCsv(constraint) {
656
+ if (!constraint || constraint.Type !== 'enum' || !Array.isArray(constraint.Values)) {
657
+ return '';
658
+ }
659
+ return constraint.Values.join(', ');
660
+ }
661
+ getNumericMin(constraint) {
662
+ return constraint && constraint.Type === 'numeric' && constraint.Min !== undefined ? constraint.Min : '';
663
+ }
664
+ getNumericMax(constraint) {
665
+ return constraint && constraint.Type === 'numeric' && constraint.Max !== undefined ? constraint.Max : '';
666
+ }
667
+ updateEnumValues(index, event) {
668
+ const val = event.target.value;
669
+ const output = this.spec.Outputs[index];
670
+ if (!output.Constraint || output.Constraint.Type !== 'enum') {
671
+ output.Constraint = { Type: 'enum', Values: [], OnViolation: 'fail' };
672
+ }
673
+ output.Constraint.Values = val
674
+ .split(',')
675
+ .map((s) => s.trim())
676
+ .filter((s) => s.length > 0);
677
+ this.emitChanges();
678
+ }
679
+ updateNumericMin(index, event) {
680
+ const val = parseFloat(event.target.value);
681
+ const output = this.spec.Outputs[index];
682
+ if (output.Constraint && output.Constraint.Type === 'numeric') {
683
+ output.Constraint.Min = isNaN(val) ? undefined : val;
684
+ }
685
+ this.emitChanges();
686
+ }
687
+ updateNumericMax(index, event) {
688
+ const val = parseFloat(event.target.value);
689
+ const output = this.spec.Outputs[index];
690
+ if (output.Constraint && output.Constraint.Type === 'numeric') {
691
+ output.Constraint.Max = isNaN(val) ? undefined : val;
692
+ }
693
+ this.emitChanges();
694
+ }
695
+ updateWatermarkStrategy(event) {
696
+ if (this.Record) {
697
+ this.Record.WatermarkStrategy = event.target.value;
698
+ }
699
+ }
700
+ updateSkipUnchanged(event) {
701
+ if (this.Record) {
702
+ this.Record.SkipUnchanged = event.target.value === 'true';
703
+ }
704
+ }
705
+ updateCacheable(event) {
706
+ if (!this.spec.Caching)
707
+ this.spec.Caching = { Cacheable: false };
708
+ this.spec.Caching.Cacheable = event.target.value === 'true';
709
+ this.emitChanges();
710
+ }
711
+ updateCacheKeyFields(event) {
712
+ const val = event.target.value;
713
+ this.cacheKeyFieldsCsv = val;
714
+ if (!this.spec.Caching)
715
+ this.spec.Caching = { Cacheable: true };
716
+ this.spec.Caching.KeyFields = val
717
+ .split(',')
718
+ .map((s) => s.trim())
719
+ .filter((s) => s.length > 0);
720
+ this.emitChanges();
721
+ }
722
+ updateCacheTTL(event) {
723
+ const val = parseInt(event.target.value, 10);
724
+ if (!this.spec.Caching)
725
+ this.spec.Caching = { Cacheable: true };
726
+ this.spec.Caching.TTLSeconds = isNaN(val) ? 86400 : val;
727
+ this.emitChanges();
728
+ }
729
+ updateCacheScope(event) {
730
+ const val = event.target.value;
731
+ if (!this.spec.Caching)
732
+ this.spec.Caching = { Cacheable: true };
733
+ this.spec.Caching.Scope = val;
734
+ this.emitChanges();
735
+ }
736
+ emitChanges() {
737
+ if (this.Record) {
738
+ this.spec.Name = this.Record.Name || '';
739
+ this.spec.Description = this.Record.Description || '';
740
+ this.spec.PromptID = this.Record.PromptID || '';
741
+ this.Record.Configuration = JSON.stringify(this.spec);
742
+ this.syncOutputMappingToRecord();
743
+ }
744
+ this.recomputeValidation();
745
+ this.SpecChange.emit(this.spec);
746
+ this.cdr.detectChanges();
747
+ }
748
+ /**
749
+ * Automatically projects spec.Outputs into the record's OutputMapping JSON
750
+ * so that WriteBackProcessor works out of the box without duplicate manual config.
751
+ */
752
+ syncOutputMappingToRecord() {
753
+ if (!this.Record)
754
+ return;
755
+ const fields = Object.create(null);
756
+ for (const out of this.spec.Outputs) {
757
+ if (out.Target.Mode === 'field' && out.Target.EntityFieldName) {
758
+ fields[out.Target.EntityFieldName] = out.Ref;
759
+ }
760
+ }
761
+ this.Record.OutputMapping = JSON.stringify({ fields });
762
+ }
763
+ recomputeValidation() {
764
+ this.renderedConstraintPreview = renderConstraintBlock(this.spec.Outputs);
765
+ const entity = this.Record?.EntityID ? this.ProviderToUse.EntityByID(this.Record.EntityID) : null;
766
+ if (entity) {
767
+ const stub = {
768
+ Name: entity.Name,
769
+ Fields: (entity.Fields ?? []).map((f) => ({
770
+ Name: f.Name,
771
+ TSType: f.TSType,
772
+ IsVirtual: f.IsVirtual,
773
+ AllowsNull: f.AllowsNull,
774
+ RelatedEntity: f.RelatedEntity,
775
+ RelatedEntityID: f.RelatedEntityID,
776
+ EntityFieldValues: f.EntityFieldValues ? f.EntityFieldValues.map((v) => ({ Value: v.Value, Code: v.Code })) : undefined,
777
+ })),
778
+ };
779
+ this.validationErrors = validateSpec(this.spec, stub);
780
+ }
781
+ else {
782
+ this.validationErrors = this.spec.Outputs.length === 0
783
+ ? [{
784
+ Path: 'Outputs',
785
+ Severity: 'error',
786
+ Message: 'At least one output attribute must be configured',
787
+ FixRecommendation: 'Click Add Output to configure an output attribute',
788
+ }]
789
+ : [];
790
+ }
791
+ const hasErrors = this.validationErrors.some((i) => i.Severity === 'error');
792
+ this.ValidChange.emit(!hasErrors);
793
+ }
794
+ static ɵfac = /*@__PURE__*/ (() => { let ɵFeaturePipelineBuilderComponent_BaseFactory; return function FeaturePipelineBuilderComponent_Factory(__ngFactoryType__) { return (ɵFeaturePipelineBuilderComponent_BaseFactory || (ɵFeaturePipelineBuilderComponent_BaseFactory = i0.ɵɵgetInheritedFactory(FeaturePipelineBuilderComponent)))(__ngFactoryType__ || FeaturePipelineBuilderComponent); }; })();
795
+ static ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: FeaturePipelineBuilderComponent, selectors: [["mj-feature-pipeline-builder"]], inputs: { Record: "Record", EntityID: "EntityID" }, outputs: { SpecChange: "SpecChange", ValidChange: "ValidChange" }, features: [i0.ɵɵInheritDefinitionFeature, i0.ɵɵNgOnChangesFeature], decls: 92, vars: 13, consts: [[1, "fpb"], [1, "rpe-sec"], [1, "rpe-sec-h"], [1, "num"], [1, "rpe-desc"], [1, "rpe-grid3"], [1, "field"], [1, "mj-input", 3, "change", "value"], ["value", "fields"], ["value", "document"], ["value", "query"], ["placeholder", "e.g. FirstName, LastName, Title", 1, "mj-input", 3, "input", "value"], [1, "rpe-grid2"], ["value", "", "disabled", ""], [3, "value"], [1, "field", "rpe-mt"], [1, "rpe-sec-h", "fpb-split-h"], [1, "fpb-title-group"], ["mjButton", "", "size", "sm", "variant", "secondary", 3, "click"], [1, "fa-solid", "fa-plus"], [1, "fpb-empty-outputs"], [1, "fpb-outputs-list"], ["value", "Checksum"], ["value", "UpdatedAt"], ["value", "None"], ["value", "true"], ["value", "false"], [1, "rpe-grid3", "rpe-mt"], [1, "fpb-issues-card", "fpb-issues-card--error"], ["placeholder", "UUID of approved Query", 1, "mj-input", "mono", 3, "input", "value"], [1, "rpe-static-text"], [1, "muted"], [1, "fpb-code-preview"], [1, "fa-solid", "fa-arrow-turn-up"], [1, "fpb-output-card"], [1, "fpb-output-head"], ["mjButton", "", "size", "sm", "variant", "flat", 3, "click"], [1, "fa-solid", "fa-trash"], ["placeholder", "e.g. SentimentScore", 1, "mj-input", 3, "input", "value"], ["placeholder", "e.g. $.sentiment or $", 1, "mj-input", "mono", 3, "input", "value"], ["value", "field"], ["value", "child"], ["value", "tags"], ["value", "none"], ["value", "enum"], ["value", "numeric"], ["value", "boolean"], ["value", "freetext"], [1, "rpe-grid2", "rpe-mt"], ["value", "fail"], ["value", "null"], ["value", "coerce-to-other"], ["placeholder", "e.g. Positive, Neutral, Negative", 1, "mj-input", 3, "input", "value"], ["type", "number", 1, "mj-input", 3, "input", "value"], ["placeholder", "e.g. CurrentJobTitle", 1, "mj-input", 3, "input", "value"], ["value", "pipeline"], ["value", "prompt"], [1, "fa-solid", "fa-triangle-exclamation"]], template: function FeaturePipelineBuilderComponent_Template(rf, ctx) { if (rf & 1) {
796
+ i0.ɵɵelementStart(0, "div", 0)(1, "section", 1)(2, "div", 2)(3, "span", 3);
797
+ i0.ɵɵtext(4, "1");
798
+ i0.ɵɵelementEnd();
799
+ i0.ɵɵelementStart(5, "h3");
800
+ i0.ɵɵtext(6, "Context Source");
801
+ i0.ɵɵelementEnd()();
802
+ i0.ɵɵelementStart(7, "p", 4);
803
+ i0.ɵɵtext(8, "Choose how data from each record is extracted and formatted before being sent to the LLM prompt.");
804
+ i0.ɵɵelementEnd();
805
+ i0.ɵɵelementStart(9, "div", 5)(10, "div", 6)(11, "label");
806
+ i0.ɵɵtext(12, "Context Mode");
807
+ i0.ɵɵelementEnd();
808
+ i0.ɵɵelementStart(13, "select", 7);
809
+ i0.ɵɵlistener("change", function FeaturePipelineBuilderComponent_Template_select_change_13_listener($event) { return ctx.onContextModeChange($event); });
810
+ i0.ɵɵelementStart(14, "option", 8);
811
+ i0.ɵɵtext(15, "Record Fields");
812
+ i0.ɵɵelementEnd();
813
+ i0.ɵɵelementStart(16, "option", 9);
814
+ i0.ɵɵtext(17, "Entity Document (Template)");
815
+ i0.ɵɵelementEnd();
816
+ i0.ɵɵelementStart(18, "option", 10);
817
+ i0.ɵɵtext(19, "Query (Graph Traversal)");
818
+ i0.ɵɵelementEnd()()();
819
+ i0.ɵɵconditionalCreate(20, FeaturePipelineBuilderComponent_Conditional_20_Template, 8, 1, "div", 6);
820
+ i0.ɵɵconditionalCreate(21, FeaturePipelineBuilderComponent_Conditional_21_Template, 4, 1, "div", 6);
821
+ i0.ɵɵelementStart(22, "div", 6)(23, "label");
822
+ i0.ɵɵtext(24, "Field Filter (optional)");
823
+ i0.ɵɵelementEnd();
824
+ i0.ɵɵelementStart(25, "input", 11);
825
+ i0.ɵɵlistener("input", function FeaturePipelineBuilderComponent_Template_input_input_25_listener($event) { return ctx.onFieldsCsvChange($event); });
826
+ i0.ɵɵelementEnd()()()();
827
+ i0.ɵɵelementStart(26, "section", 1)(27, "div", 2)(28, "span", 3);
828
+ i0.ɵɵtext(29, "2");
829
+ i0.ɵɵelementEnd();
830
+ i0.ɵɵelementStart(30, "h3");
831
+ i0.ɵɵtext(31, "AI Prompt");
832
+ i0.ɵɵelementEnd()();
833
+ i0.ɵɵelementStart(32, "div", 12)(33, "div", 6)(34, "label");
834
+ i0.ɵɵtext(35, "Prompt");
835
+ i0.ɵɵelementEnd();
836
+ i0.ɵɵelementStart(36, "select", 7);
837
+ i0.ɵɵlistener("change", function FeaturePipelineBuilderComponent_Template_select_change_36_listener($event) { return ctx.onPromptChange($event); });
838
+ i0.ɵɵelementStart(37, "option", 13);
839
+ i0.ɵɵtext(38, "\u2014 Select AI Prompt \u2014");
840
+ i0.ɵɵelementEnd();
841
+ i0.ɵɵrepeaterCreate(39, FeaturePipelineBuilderComponent_For_40_Template, 2, 2, "option", 14, _forTrack0);
842
+ i0.ɵɵelementEnd()();
843
+ i0.ɵɵconditionalCreate(41, FeaturePipelineBuilderComponent_Conditional_41_Template, 5, 1, "div", 6);
844
+ i0.ɵɵelementEnd();
845
+ i0.ɵɵconditionalCreate(42, FeaturePipelineBuilderComponent_Conditional_42_Template, 7, 1, "div", 15);
846
+ i0.ɵɵelementEnd();
847
+ i0.ɵɵelementStart(43, "section", 1)(44, "div", 16)(45, "div", 17)(46, "span", 3);
848
+ i0.ɵɵtext(47, "3");
849
+ i0.ɵɵelementEnd();
850
+ i0.ɵɵelementStart(48, "h3");
851
+ i0.ɵɵtext(49, "Outputs & Constraints");
852
+ i0.ɵɵelementEnd()();
853
+ i0.ɵɵelementStart(50, "button", 18);
854
+ i0.ɵɵlistener("click", function FeaturePipelineBuilderComponent_Template_button_click_50_listener() { return ctx.addOutput(); });
855
+ i0.ɵɵelement(51, "i", 19);
856
+ i0.ɵɵtext(52, " Add Output ");
857
+ i0.ɵɵelementEnd()();
858
+ i0.ɵɵelementStart(53, "p", 4);
859
+ i0.ɵɵtext(54, "Define the structured outputs produced by the LLM, their type constraints, and target write-back destinations.");
860
+ i0.ɵɵelementEnd();
861
+ i0.ɵɵconditionalCreate(55, FeaturePipelineBuilderComponent_Conditional_55_Template, 7, 0, "div", 20)(56, FeaturePipelineBuilderComponent_Conditional_56_Template, 3, 0, "div", 21);
862
+ i0.ɵɵelementEnd();
863
+ i0.ɵɵelementStart(57, "section", 1)(58, "div", 2)(59, "span", 3);
864
+ i0.ɵɵtext(60, "4");
865
+ i0.ɵɵelementEnd();
866
+ i0.ɵɵelementStart(61, "h3");
867
+ i0.ɵɵtext(62, "Caching & Watermarks");
868
+ i0.ɵɵelementEnd()();
869
+ i0.ɵɵelementStart(63, "div", 5)(64, "div", 6)(65, "label");
870
+ i0.ɵɵtext(66, "Watermark Strategy");
871
+ i0.ɵɵelementEnd();
872
+ i0.ɵɵelementStart(67, "select", 7);
873
+ i0.ɵɵlistener("change", function FeaturePipelineBuilderComponent_Template_select_change_67_listener($event) { return ctx.updateWatermarkStrategy($event); });
874
+ i0.ɵɵelementStart(68, "option", 22);
875
+ i0.ɵɵtext(69, "Checksum (Content Hash + Prompt Hash)");
876
+ i0.ɵɵelementEnd();
877
+ i0.ɵɵelementStart(70, "option", 23);
878
+ i0.ɵɵtext(71, "UpdatedAt (Compare modified timestamp)");
879
+ i0.ɵɵelementEnd();
880
+ i0.ɵɵelementStart(72, "option", 24);
881
+ i0.ɵɵtext(73, "None (Always process all records)");
882
+ i0.ɵɵelementEnd()()();
883
+ i0.ɵɵelementStart(74, "div", 6)(75, "label");
884
+ i0.ɵɵtext(76, "Skip Unchanged Records");
885
+ i0.ɵɵelementEnd();
886
+ i0.ɵɵelementStart(77, "select", 7);
887
+ i0.ɵɵlistener("change", function FeaturePipelineBuilderComponent_Template_select_change_77_listener($event) { return ctx.updateSkipUnchanged($event); });
888
+ i0.ɵɵelementStart(78, "option", 25);
889
+ i0.ɵɵtext(79, "Yes \u2014 Skip unchanged rows");
890
+ i0.ɵɵelementEnd();
891
+ i0.ɵɵelementStart(80, "option", 26);
892
+ i0.ɵɵtext(81, "No \u2014 Recompute every time");
893
+ i0.ɵɵelementEnd()()();
894
+ i0.ɵɵelementStart(82, "div", 6)(83, "label");
895
+ i0.ɵɵtext(84, "Enable Result Cache");
896
+ i0.ɵɵelementEnd();
897
+ i0.ɵɵelementStart(85, "select", 7);
898
+ i0.ɵɵlistener("change", function FeaturePipelineBuilderComponent_Template_select_change_85_listener($event) { return ctx.updateCacheable($event); });
899
+ i0.ɵɵelementStart(86, "option", 25);
900
+ i0.ɵɵtext(87, "Yes \u2014 Cache outputs by distinct key");
901
+ i0.ɵɵelementEnd();
902
+ i0.ɵɵelementStart(88, "option", 26);
903
+ i0.ɵɵtext(89, "No \u2014 Compute per row");
904
+ i0.ɵɵelementEnd()()()();
905
+ i0.ɵɵconditionalCreate(90, FeaturePipelineBuilderComponent_Conditional_90_Template, 17, 3, "div", 27);
906
+ i0.ɵɵelementEnd();
907
+ i0.ɵɵconditionalCreate(91, FeaturePipelineBuilderComponent_Conditional_91_Template, 7, 0, "div", 28);
908
+ i0.ɵɵelementEnd();
909
+ } if (rf & 2) {
910
+ i0.ɵɵadvance(13);
911
+ i0.ɵɵproperty("value", ctx.contextMode);
912
+ i0.ɵɵadvance(7);
913
+ i0.ɵɵconditional(ctx.contextMode === "document" ? 20 : -1);
914
+ i0.ɵɵadvance();
915
+ i0.ɵɵconditional(ctx.contextMode === "query" ? 21 : -1);
916
+ i0.ɵɵadvance(4);
917
+ i0.ɵɵproperty("value", ctx.fieldsCsv);
918
+ i0.ɵɵadvance(11);
919
+ i0.ɵɵproperty("value", (ctx.Record == null ? null : ctx.Record.PromptID) || "");
920
+ i0.ɵɵadvance(3);
921
+ i0.ɵɵrepeater(ctx.availablePrompts);
922
+ i0.ɵɵadvance(2);
923
+ i0.ɵɵconditional(ctx.selectedPrompt ? 41 : -1);
924
+ i0.ɵɵadvance();
925
+ i0.ɵɵconditional(ctx.renderedConstraintPreview ? 42 : -1);
926
+ i0.ɵɵadvance(13);
927
+ i0.ɵɵconditional(!ctx.spec.Outputs || ctx.spec.Outputs.length === 0 ? 55 : 56);
928
+ i0.ɵɵadvance(12);
929
+ i0.ɵɵproperty("value", (ctx.Record == null ? null : ctx.Record.WatermarkStrategy) || "Checksum");
930
+ i0.ɵɵadvance(10);
931
+ i0.ɵɵproperty("value", (ctx.Record == null ? null : ctx.Record.SkipUnchanged) ? "true" : "false");
932
+ i0.ɵɵadvance(8);
933
+ i0.ɵɵproperty("value", ctx.spec.Caching.Cacheable ? "true" : "false");
934
+ i0.ɵɵadvance(5);
935
+ i0.ɵɵconditional(ctx.spec.Caching.Cacheable ? 90 : -1);
936
+ i0.ɵɵadvance();
937
+ i0.ɵɵconditional(ctx.validationErrors.length > 0 ? 91 : -1);
938
+ } }, dependencies: [CommonModule, FormsModule, i1.NgSelectOption, i1.ɵNgSelectMultipleOption, MJButtonDirective], styles: [".fpb[_ngcontent-%COMP%] { display: flex; flex-direction: column; gap: 20px; }\n .rpe-desc[_ngcontent-%COMP%] { font-size: 13px; color: var(--mj-text-secondary); margin: -6px 0 16px 0; }\n .rpe-sec[_ngcontent-%COMP%] { background: var(--mj-bg-surface-card); border: 1px solid var(--mj-border-subtle); border-radius: var(--mj-radius-md, 10px); padding: 18px 20px; }\n .rpe-sec-h[_ngcontent-%COMP%] { display: flex; align-items: center; gap: 11px; margin-bottom: 14px; }\n .rpe-sec-h[_ngcontent-%COMP%] h3[_ngcontent-%COMP%] { margin: 0; font-size: 16px; font-weight: 700; color: var(--mj-text-primary); }\n .rpe-sec-h[_ngcontent-%COMP%] .num[_ngcontent-%COMP%] { width: 24px; height: 24px; border-radius: 50%; background: var(--mj-brand-primary); color: #fff; display: grid; place-items: center; font-size: 12px; font-weight: 800; }\n .fpb-split-h[_ngcontent-%COMP%] { justify-content: space-between; }\n .fpb-title-group[_ngcontent-%COMP%] { display: flex; align-items: center; gap: 11px; }\n .rpe-grid2[_ngcontent-%COMP%] { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; }\n .rpe-grid3[_ngcontent-%COMP%] { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 16px; }\n .field[_ngcontent-%COMP%] { display: flex; flex-direction: column; gap: 6px; }\n .field[_ngcontent-%COMP%] label[_ngcontent-%COMP%] { font-size: 12.5px; font-weight: 600; color: var(--mj-text-secondary); }\n .rpe-mt[_ngcontent-%COMP%] { margin-top: 14px; }\n .mono[_ngcontent-%COMP%] { font-family: monospace; font-size: 12.5px; }\n .muted[_ngcontent-%COMP%] { color: var(--mj-text-muted); font-weight: 400; }\n .rpe-static-text[_ngcontent-%COMP%] { font-size: 13px; color: var(--mj-text-muted); padding: 7px 0; }\n .fpb-code-preview[_ngcontent-%COMP%] { background: var(--mj-bg-surface-sunken, #0f172a); color: var(--mj-text-code, var(--mj-text-primary, #e2e8f0)); border: 1px solid var(--mj-border-subtle, #334155); padding: 12px; border-radius: 6px; font-size: 12px; font-family: var(--mj-font-mono, monospace); white-space: pre-wrap; word-break: break-word; max-height: 160px; overflow-y: auto; margin: 0; }\n .fpb-empty-outputs[_ngcontent-%COMP%] { display: flex; align-items: center; gap: 10px; padding: 20px; border: 1px dashed var(--mj-border-subtle); border-radius: 8px; color: var(--mj-text-muted); font-size: 13px; }\n .fpb-outputs-list[_ngcontent-%COMP%] { display: flex; flex-direction: column; gap: 16px; }\n .fpb-output-card[_ngcontent-%COMP%] { border: 1px solid var(--mj-border-subtle); border-radius: 8px; padding: 14px 16px; background: var(--mj-bg-surface); }\n .fpb-output-head[_ngcontent-%COMP%] { display: flex; justify-content: space-between; align-items: center; margin-bottom: 12px; border-bottom: 1px solid var(--mj-border-subtle); padding-bottom: 8px; }\n .fpb-output-head[_ngcontent-%COMP%] h4[_ngcontent-%COMP%] { margin: 0; font-size: 14px; font-weight: 600; }\n .fpb-issues-card[_ngcontent-%COMP%] { border-radius: 8px; padding: 12px 16px; font-size: 13px; }\n .fpb-issues-card--error[_ngcontent-%COMP%] { background: rgba(239, 68, 68, 0.08); border: 1px solid var(--mj-status-error); color: var(--mj-status-error-text); }\n .fpb-issues-card[_ngcontent-%COMP%] h4[_ngcontent-%COMP%] { margin: 0 0 8px 0; font-size: 14px; display: flex; align-items: center; gap: 8px; }\n .fpb-issues-card[_ngcontent-%COMP%] ul[_ngcontent-%COMP%] { margin: 0; padding-left: 20px; }"], changeDetection: 0 });
939
+ }
940
+ (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(FeaturePipelineBuilderComponent, [{
941
+ type: Component,
942
+ args: [{ selector: 'mj-feature-pipeline-builder', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, imports: [CommonModule, FormsModule, MJButtonDirective], template: `
943
+ <div class="fpb">
944
+ <!-- STAGE 1: CONTEXT SOURCE -->
945
+ <section class="rpe-sec">
946
+ <div class="rpe-sec-h">
947
+ <span class="num">1</span>
948
+ <h3>Context Source</h3>
949
+ </div>
950
+ <p class="rpe-desc">Choose how data from each record is extracted and formatted before being sent to the LLM prompt.</p>
951
+
952
+ <div class="rpe-grid3">
953
+ <div class="field">
954
+ <label>Context Mode</label>
955
+ <select class="mj-input" [value]="contextMode" (change)="onContextModeChange($event)">
956
+ <option value="fields">Record Fields</option>
957
+ <option value="document">Entity Document (Template)</option>
958
+ <option value="query">Query (Graph Traversal)</option>
959
+ </select>
960
+ </div>
961
+
962
+ @if (contextMode === 'document') {
963
+ <div class="field">
964
+ <label>Entity Document</label>
965
+ <select class="mj-input" [value]="spec.Context.EntityDocumentID || ''" (change)="onEntityDocChange($event)">
966
+ <option value="" disabled>— Select Entity Document —</option>
967
+ @for (doc of availableDocs; track doc.ID) {
968
+ <option [value]="doc.ID">{{ doc.Name }}</option>
969
+ }
970
+ </select>
971
+ </div>
972
+ }
973
+
974
+ @if (contextMode === 'query') {
975
+ <div class="field">
976
+ <label>Query ID</label>
977
+ <input class="mj-input mono" [value]="spec.Context.QueryID || ''" (input)="onQueryIDChange($event)" placeholder="UUID of approved Query">
978
+ </div>
979
+ }
980
+
981
+ <div class="field">
982
+ <label>Field Filter (optional)</label>
983
+ <input class="mj-input" [value]="fieldsCsv" (input)="onFieldsCsvChange($event)" placeholder="e.g. FirstName, LastName, Title">
984
+ </div>
985
+ </div>
986
+ </section>
987
+
988
+ <!-- STAGE 2: PROMPT & INJECTED CONSTRAINTS -->
989
+ <section class="rpe-sec">
990
+ <div class="rpe-sec-h">
991
+ <span class="num">2</span>
992
+ <h3>AI Prompt</h3>
993
+ </div>
994
+ <div class="rpe-grid2">
995
+ <div class="field">
996
+ <label>Prompt</label>
997
+ <select class="mj-input" [value]="Record?.PromptID || ''" (change)="onPromptChange($event)">
998
+ <option value="" disabled>— Select AI Prompt —</option>
999
+ @for (p of availablePrompts; track p.ID) {
1000
+ <option [value]="p.ID">{{ p.Name }}</option>
1001
+ }
1002
+ </select>
1003
+ </div>
1004
+ @if (selectedPrompt) {
1005
+ <div class="field">
1006
+ <label>Prompt Description</label>
1007
+ <div class="rpe-static-text">{{ selectedPrompt.Description || 'No description provided' }}</div>
1008
+ </div>
1009
+ }
1010
+ </div>
1011
+
1012
+ @if (renderedConstraintPreview) {
1013
+ <div class="field rpe-mt">
1014
+ <label>Injected Constraint Block <span class="muted">— automatically appended to prompt instructions</span></label>
1015
+ <pre class="fpb-code-preview">{{ renderedConstraintPreview }}</pre>
1016
+ </div>
1017
+ }
1018
+ </section>
1019
+
1020
+ <!-- STAGE 3: OUTPUTS & CONSTRAINTS -->
1021
+ <section class="rpe-sec">
1022
+ <div class="rpe-sec-h fpb-split-h">
1023
+ <div class="fpb-title-group">
1024
+ <span class="num">3</span>
1025
+ <h3>Outputs & Constraints</h3>
1026
+ </div>
1027
+ <button mjButton size="sm" variant="secondary" (click)="addOutput()">
1028
+ <i class="fa-solid fa-plus"></i> Add Output
1029
+ </button>
1030
+ </div>
1031
+ <p class="rpe-desc">Define the structured outputs produced by the LLM, their type constraints, and target write-back destinations.</p>
1032
+
1033
+ @if (!spec.Outputs || spec.Outputs.length === 0) {
1034
+ <div class="fpb-empty-outputs">
1035
+ <i class="fa-solid fa-arrow-turn-up"></i>
1036
+ <span>No outputs defined. Click <strong>Add Output</strong> to configure at least one output attribute.</span>
1037
+ </div>
1038
+ } @else {
1039
+ <div class="fpb-outputs-list">
1040
+ @for (output of spec.Outputs; track $index) {
1041
+ <div class="fpb-output-card">
1042
+ <div class="fpb-output-head">
1043
+ <h4>Output #{{ $index + 1 }}: {{ output.Name || '(unnamed)' }}</h4>
1044
+ <button mjButton size="sm" variant="flat" (click)="removeOutput($index)">
1045
+ <i class="fa-solid fa-trash"></i> Remove
1046
+ </button>
1047
+ </div>
1048
+
1049
+ <div class="rpe-grid3">
1050
+ <div class="field">
1051
+ <label>Output Name</label>
1052
+ <input class="mj-input" [value]="output.Name" (input)="updateOutputProp($index, 'Name', $event)" placeholder="e.g. SentimentScore">
1053
+ </div>
1054
+ <div class="field">
1055
+ <label>JSON Path / Ref</label>
1056
+ <input class="mj-input mono" [value]="output.Ref" (input)="updateOutputProp($index, 'Ref', $event)" placeholder="e.g. $.sentiment or $">
1057
+ </div>
1058
+ <div class="field">
1059
+ <label>Target Mode</label>
1060
+ <select class="mj-input" [value]="output.Target.Mode" (change)="updateTargetMode($index, $event)">
1061
+ <option value="field">Entity Column (field)</option>
1062
+ <option value="child">Child Rows (child)</option>
1063
+ <option value="tags">Taxonomy Tags (tags)</option>
1064
+ </select>
1065
+ </div>
1066
+ </div>
1067
+
1068
+ <!-- Target details -->
1069
+ <div class="rpe-grid3 rpe-mt">
1070
+ @if (output.Target.Mode === 'field') {
1071
+ <div class="field">
1072
+ <label>Target Field</label>
1073
+ <select class="mj-input" [value]="output.Target.EntityFieldName" (change)="updateFieldTarget($index, $event)">
1074
+ <option value="" disabled>— Select Column —</option>
1075
+ @for (f of entityFields; track f.Name) {
1076
+ <option [value]="f.Name">{{ f.DisplayName || f.Name }} ({{ f.TSType }})</option>
1077
+ }
1078
+ </select>
1079
+ </div>
1080
+ }
1081
+
1082
+ @if (output.Target.Mode === 'child') {
1083
+ <div class="field">
1084
+ <label>Child Entity</label>
1085
+ <select class="mj-input" [value]="output.Target.EntityName" (change)="updateChildTargetEntity($index, $event)">
1086
+ <option value="" disabled>— Select Entity —</option>
1087
+ @for (e of availableEntities; track e.ID) {
1088
+ <option [value]="e.Name">{{ e.DisplayName || e.Name }}</option>
1089
+ }
1090
+ </select>
1091
+ </div>
1092
+ }
1093
+
1094
+ <!-- Constraint details -->
1095
+ <div class="field">
1096
+ <label>Constraint Type</label>
1097
+ <select class="mj-input" [value]="output.Constraint?.Type || 'none'" (change)="updateConstraintType($index, $event)">
1098
+ <option value="none">None / Unconstrained</option>
1099
+ <option value="enum">Enum (Allowed Values)</option>
1100
+ <option value="numeric">Numeric (Min/Max)</option>
1101
+ <option value="boolean">Boolean</option>
1102
+ <option value="freetext">Free Text</option>
1103
+ </select>
1104
+ </div>
1105
+
1106
+ @if (output.Constraint) {
1107
+ <div class="field">
1108
+ <label>Violation Policy</label>
1109
+ <select class="mj-input" [value]="output.Constraint.OnViolation || 'fail'" (change)="updateViolationPolicy($index, $event)">
1110
+ <option value="fail">Fail Row</option>
1111
+ <option value="null">Set Output to Null</option>
1112
+ <option value="coerce-to-other">Coerce / Fallback</option>
1113
+ </select>
1114
+ </div>
1115
+ }
1116
+ </div>
1117
+
1118
+ <!-- Constraint params -->
1119
+ @if (output.Constraint?.Type === 'enum') {
1120
+ <div class="field rpe-mt">
1121
+ <label>Allowed Values (comma separated)</label>
1122
+ <input class="mj-input" [value]="getEnumValuesCsv(output.Constraint)" (input)="updateEnumValues($index, $event)" placeholder="e.g. Positive, Neutral, Negative">
1123
+ </div>
1124
+ }
1125
+ @if (output.Constraint?.Type === 'numeric') {
1126
+ <div class="rpe-grid2 rpe-mt">
1127
+ <div class="field">
1128
+ <label>Min Value</label>
1129
+ <input class="mj-input" type="number" [value]="getNumericMin(output.Constraint)" (input)="updateNumericMin($index, $event)">
1130
+ </div>
1131
+ <div class="field">
1132
+ <label>Max Value</label>
1133
+ <input class="mj-input" type="number" [value]="getNumericMax(output.Constraint)" (input)="updateNumericMax($index, $event)">
1134
+ </div>
1135
+ </div>
1136
+ }
1137
+ </div>
1138
+ }
1139
+ </div>
1140
+ }
1141
+ </section>
1142
+
1143
+ <!-- STAGE 4: MATERIALIZATION & WATERMARK -->
1144
+ <section class="rpe-sec">
1145
+ <div class="rpe-sec-h">
1146
+ <span class="num">4</span>
1147
+ <h3>Caching & Watermarks</h3>
1148
+ </div>
1149
+ <div class="rpe-grid3">
1150
+ <div class="field">
1151
+ <label>Watermark Strategy</label>
1152
+ <select class="mj-input" [value]="Record?.WatermarkStrategy || 'Checksum'" (change)="updateWatermarkStrategy($event)">
1153
+ <option value="Checksum">Checksum (Content Hash + Prompt Hash)</option>
1154
+ <option value="UpdatedAt">UpdatedAt (Compare modified timestamp)</option>
1155
+ <option value="None">None (Always process all records)</option>
1156
+ </select>
1157
+ </div>
1158
+
1159
+ <div class="field">
1160
+ <label>Skip Unchanged Records</label>
1161
+ <select class="mj-input" [value]="Record?.SkipUnchanged ? 'true' : 'false'" (change)="updateSkipUnchanged($event)">
1162
+ <option value="true">Yes — Skip unchanged rows</option>
1163
+ <option value="false">No — Recompute every time</option>
1164
+ </select>
1165
+ </div>
1166
+
1167
+ <div class="field">
1168
+ <label>Enable Result Cache</label>
1169
+ <select class="mj-input" [value]="spec.Caching.Cacheable ? 'true' : 'false'" (change)="updateCacheable($event)">
1170
+ <option value="true">Yes — Cache outputs by distinct key</option>
1171
+ <option value="false">No — Compute per row</option>
1172
+ </select>
1173
+ </div>
1174
+ </div>
1175
+
1176
+ @if (spec.Caching.Cacheable) {
1177
+ <div class="rpe-grid3 rpe-mt">
1178
+ <div class="field">
1179
+ <label>Cache Key Fields (comma separated)</label>
1180
+ <input class="mj-input" [value]="cacheKeyFieldsCsv" (input)="updateCacheKeyFields($event)" placeholder="e.g. CurrentJobTitle">
1181
+ </div>
1182
+ <div class="field">
1183
+ <label>Cache TTL (Seconds)</label>
1184
+ <input class="mj-input" type="number" [value]="spec.Caching.TTLSeconds ?? 86400" (input)="updateCacheTTL($event)">
1185
+ </div>
1186
+ <div class="field">
1187
+ <label>Cache Scope</label>
1188
+ <select class="mj-input" [value]="spec.Caching.Scope || 'pipeline'" (change)="updateCacheScope($event)">
1189
+ <option value="pipeline">Pipeline (Isolated to this process)</option>
1190
+ <option value="prompt">Prompt (Shared across processes with same prompt)</option>
1191
+ </select>
1192
+ </div>
1193
+ </div>
1194
+ }
1195
+ </section>
1196
+
1197
+ <!-- VALIDATION ISSUES -->
1198
+ @if (validationErrors.length > 0) {
1199
+ <div class="fpb-issues-card fpb-issues-card--error">
1200
+ <h4><i class="fa-solid fa-triangle-exclamation"></i> Specification Issues</h4>
1201
+ <ul>
1202
+ @for (issue of validationErrors; track $index) {
1203
+ <li><strong>{{ issue.Field ? issue.Field + ': ' : '' }}</strong>{{ issue.Message }} @if (issue.FixRecommendation) { <span class="muted">(Fix: {{ issue.FixRecommendation }})</span> }</li>
1204
+ }
1205
+ </ul>
1206
+ </div>
1207
+ }
1208
+ </div>
1209
+ `, styles: ["\n .fpb { display: flex; flex-direction: column; gap: 20px; }\n .rpe-desc { font-size: 13px; color: var(--mj-text-secondary); margin: -6px 0 16px 0; }\n .rpe-sec { background: var(--mj-bg-surface-card); border: 1px solid var(--mj-border-subtle); border-radius: var(--mj-radius-md, 10px); padding: 18px 20px; }\n .rpe-sec-h { display: flex; align-items: center; gap: 11px; margin-bottom: 14px; }\n .rpe-sec-h h3 { margin: 0; font-size: 16px; font-weight: 700; color: var(--mj-text-primary); }\n .rpe-sec-h .num { width: 24px; height: 24px; border-radius: 50%; background: var(--mj-brand-primary); color: #fff; display: grid; place-items: center; font-size: 12px; font-weight: 800; }\n .fpb-split-h { justify-content: space-between; }\n .fpb-title-group { display: flex; align-items: center; gap: 11px; }\n .rpe-grid2 { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; }\n .rpe-grid3 { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 16px; }\n .field { display: flex; flex-direction: column; gap: 6px; }\n .field label { font-size: 12.5px; font-weight: 600; color: var(--mj-text-secondary); }\n .rpe-mt { margin-top: 14px; }\n .mono { font-family: monospace; font-size: 12.5px; }\n .muted { color: var(--mj-text-muted); font-weight: 400; }\n .rpe-static-text { font-size: 13px; color: var(--mj-text-muted); padding: 7px 0; }\n .fpb-code-preview { background: var(--mj-bg-surface-sunken, #0f172a); color: var(--mj-text-code, var(--mj-text-primary, #e2e8f0)); border: 1px solid var(--mj-border-subtle, #334155); padding: 12px; border-radius: 6px; font-size: 12px; font-family: var(--mj-font-mono, monospace); white-space: pre-wrap; word-break: break-word; max-height: 160px; overflow-y: auto; margin: 0; }\n .fpb-empty-outputs { display: flex; align-items: center; gap: 10px; padding: 20px; border: 1px dashed var(--mj-border-subtle); border-radius: 8px; color: var(--mj-text-muted); font-size: 13px; }\n .fpb-outputs-list { display: flex; flex-direction: column; gap: 16px; }\n .fpb-output-card { border: 1px solid var(--mj-border-subtle); border-radius: 8px; padding: 14px 16px; background: var(--mj-bg-surface); }\n .fpb-output-head { display: flex; justify-content: space-between; align-items: center; margin-bottom: 12px; border-bottom: 1px solid var(--mj-border-subtle); padding-bottom: 8px; }\n .fpb-output-head h4 { margin: 0; font-size: 14px; font-weight: 600; }\n .fpb-issues-card { border-radius: 8px; padding: 12px 16px; font-size: 13px; }\n .fpb-issues-card--error { background: rgba(239, 68, 68, 0.08); border: 1px solid var(--mj-status-error); color: var(--mj-status-error-text); }\n .fpb-issues-card h4 { margin: 0 0 8px 0; font-size: 14px; display: flex; align-items: center; gap: 8px; }\n .fpb-issues-card ul { margin: 0; padding-left: 20px; }\n "] }]
1210
+ }], null, { Record: [{
1211
+ type: Input
1212
+ }], EntityID: [{
1213
+ type: Input
1214
+ }], SpecChange: [{
1215
+ type: Output
1216
+ }], ValidChange: [{
1217
+ type: Output
1218
+ }] }); })();
1219
+ (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(FeaturePipelineBuilderComponent, { className: "FeaturePipelineBuilderComponent", filePath: "lib/feature-pipeline-builder/feature-pipeline-builder.component.ts", lineNumber: 346 }); })();
1220
+ //# sourceMappingURL=feature-pipeline-builder.component.js.map