@memberjunction/ng-dashboards 5.46.0 → 5.47.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/dist/PredictiveStudio/at-risk.view-models.d.ts +33 -0
- package/dist/PredictiveStudio/at-risk.view-models.d.ts.map +1 -1
- package/dist/PredictiveStudio/at-risk.view-models.js +69 -2
- package/dist/PredictiveStudio/at-risk.view-models.js.map +1 -1
- package/dist/PredictiveStudio/components/ps-catalog.component.js +2 -2
- package/dist/PredictiveStudio/components/ps-catalog.component.js.map +1 -1
- package/dist/PredictiveStudio/components/ps-compare.component.js +2 -2
- package/dist/PredictiveStudio/components/ps-compare.component.js.map +1 -1
- package/dist/PredictiveStudio/components/ps-confirm-modal.component.js +2 -2
- package/dist/PredictiveStudio/components/ps-confirm-modal.component.js.map +1 -1
- package/dist/PredictiveStudio/components/ps-experiments.component.js +2 -2
- package/dist/PredictiveStudio/components/ps-experiments.component.js.map +1 -1
- package/dist/PredictiveStudio/components/ps-home.component.js +2 -2
- package/dist/PredictiveStudio/components/ps-home.component.js.map +1 -1
- package/dist/PredictiveStudio/components/ps-operate-dialog.component.js +2 -2
- package/dist/PredictiveStudio/components/ps-operate-dialog.component.js.map +1 -1
- package/dist/PredictiveStudio/components/ps-pipelines.component.d.ts.map +1 -1
- package/dist/PredictiveStudio/components/ps-pipelines.component.js +306 -297
- package/dist/PredictiveStudio/components/ps-pipelines.component.js.map +1 -1
- package/dist/PredictiveStudio/components/ps-production.component.js +2 -2
- package/dist/PredictiveStudio/components/ps-production.component.js.map +1 -1
- package/dist/PredictiveStudio/components/ps-registry.component.d.ts +1 -1
- package/dist/PredictiveStudio/components/ps-registry.component.d.ts.map +1 -1
- package/dist/PredictiveStudio/components/ps-registry.component.js +15 -9
- package/dist/PredictiveStudio/components/ps-registry.component.js.map +1 -1
- package/dist/PredictiveStudio/predictive-studio-agent-context.d.ts +115 -0
- package/dist/PredictiveStudio/predictive-studio-agent-context.d.ts.map +1 -0
- package/dist/PredictiveStudio/predictive-studio-agent-context.js +119 -0
- package/dist/PredictiveStudio/predictive-studio-agent-context.js.map +1 -0
- package/dist/PredictiveStudio/predictive-studio-copilot.view-models.d.ts +36 -0
- package/dist/PredictiveStudio/predictive-studio-copilot.view-models.d.ts.map +1 -0
- package/dist/PredictiveStudio/predictive-studio-copilot.view-models.js +52 -0
- package/dist/PredictiveStudio/predictive-studio-copilot.view-models.js.map +1 -0
- package/dist/PredictiveStudio/resources/ps-models-resource.component.d.ts +9 -1
- package/dist/PredictiveStudio/resources/ps-models-resource.component.d.ts.map +1 -1
- package/dist/PredictiveStudio/resources/ps-models-resource.component.js +133 -56
- package/dist/PredictiveStudio/resources/ps-models-resource.component.js.map +1 -1
- package/dist/PredictiveStudio/resources/ps-predictions-resource.component.d.ts +50 -1
- package/dist/PredictiveStudio/resources/ps-predictions-resource.component.d.ts.map +1 -1
- package/dist/PredictiveStudio/resources/ps-predictions-resource.component.js +591 -165
- package/dist/PredictiveStudio/resources/ps-predictions-resource.component.js.map +1 -1
- package/dist/PredictiveStudio/resources/ps-resource-base.d.ts +17 -0
- package/dist/PredictiveStudio/resources/ps-resource-base.d.ts.map +1 -1
- package/dist/PredictiveStudio/resources/ps-resource-base.js +27 -1
- package/dist/PredictiveStudio/resources/ps-resource-base.js.map +1 -1
- package/dist/PredictiveStudio/resources/ps-studio-resource.component.d.ts +11 -1
- package/dist/PredictiveStudio/resources/ps-studio-resource.component.d.ts.map +1 -1
- package/dist/PredictiveStudio/resources/ps-studio-resource.component.js +199 -99
- package/dist/PredictiveStudio/resources/ps-studio-resource.component.js.map +1 -1
- package/package.json +59 -59
|
@@ -11,6 +11,11 @@
|
|
|
11
11
|
/** A scored record in the at-risk list. */
|
|
12
12
|
export interface AtRiskRow {
|
|
13
13
|
recordId: string;
|
|
14
|
+
/**
|
|
15
|
+
* Human-readable label for the record (e.g. the member's name/email), resolved from the model's
|
|
16
|
+
* target entity. Null until resolved; the UI falls back to {@link recordId} so the row is never blank.
|
|
17
|
+
*/
|
|
18
|
+
label: string | null;
|
|
14
19
|
/** 0–1 prediction score (probability / risk). */
|
|
15
20
|
score: number;
|
|
16
21
|
/** Risk as a 0–100 integer, for display. */
|
|
@@ -19,7 +24,28 @@ export interface AtRiskRow {
|
|
|
19
24
|
class: string | null;
|
|
20
25
|
/** Risk band, for color. */
|
|
21
26
|
band: 'high' | 'medium' | 'low';
|
|
27
|
+
/**
|
|
28
|
+
* Top signed per-record drivers behind THIS row's prediction (P1-5), humanized + one-hot-collapsed for
|
|
29
|
+
* display. `up: true` pushed the risk up, `false` down. Null when the model doesn't produce per-record
|
|
30
|
+
* attribution (tree/ensemble/multiclass) — the UI then shows the model's global drivers instead.
|
|
31
|
+
*/
|
|
32
|
+
drivers: RowDriver[] | null;
|
|
22
33
|
}
|
|
34
|
+
/** A humanized, signed per-record driver for the at-risk row's inline "why". */
|
|
35
|
+
export interface RowDriver {
|
|
36
|
+
/** Display label (humanized, one-hot base collapsed). */
|
|
37
|
+
label: string;
|
|
38
|
+
/** Signed contribution magnitude for this row. */
|
|
39
|
+
value: number;
|
|
40
|
+
/** Whether this pushed the risk UP (value > 0) or down. */
|
|
41
|
+
up: boolean;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Build a human-readable label for a record from its (simple) row of the target entity — preferring a
|
|
45
|
+
* single `Name`, else `FirstName`+`LastName`, else `Email`, else any first non-empty string field.
|
|
46
|
+
* Returns null when nothing usable is found (caller falls back to the record id).
|
|
47
|
+
*/
|
|
48
|
+
export declare function labelFromRecord(row: Record<string, unknown> | undefined | null): string | null;
|
|
23
49
|
/** The raw per-record detail the list is built from (a slice of `MJ: Process Run Details`). */
|
|
24
50
|
export interface RunDetailLike {
|
|
25
51
|
recordId: string;
|
|
@@ -34,4 +60,11 @@ export declare function parseAtRiskRows(details: RunDetailLike[]): AtRiskRow[];
|
|
|
34
60
|
* highest-importance feature names — so a business user sees "what's driving this", not raw weights.
|
|
35
61
|
*/
|
|
36
62
|
export declare function topGlobalDrivers(featureImportanceJson: string | null | undefined, n?: number): string[];
|
|
63
|
+
/**
|
|
64
|
+
* Turn a raw feature/column name into a readable label — `RetentionOverdueInvoices` → `Retention Overdue
|
|
65
|
+
* Invoices`, `overdue_invoices` → `Overdue Invoices`, and a one-hot `MembershipType=Student` →
|
|
66
|
+
* `Membership Type = Student`. Splits camelCase + snake/kebab, spaces one-hot `=`, collapses whitespace,
|
|
67
|
+
* and capitalizes the first letter. Already-spaced labels pass through unchanged.
|
|
68
|
+
*/
|
|
69
|
+
export declare function humanizeFeatureName(name: string): string;
|
|
37
70
|
//# sourceMappingURL=at-risk.view-models.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"at-risk.view-models.d.ts","sourceRoot":"","sources":["../../src/PredictiveStudio/at-risk.view-models.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,2CAA2C;AAC3C,MAAM,WAAW,SAAS;IACxB,QAAQ,EAAE,MAAM,CAAC;IACjB,iDAAiD;IACjD,KAAK,EAAE,MAAM,CAAC;IACd,4CAA4C;IAC5C,OAAO,EAAE,MAAM,CAAC;IAChB,4DAA4D;IAC5D,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,4BAA4B;IAC5B,IAAI,EAAE,MAAM,GAAG,QAAQ,GAAG,KAAK,CAAC;
|
|
1
|
+
{"version":3,"file":"at-risk.view-models.d.ts","sourceRoot":"","sources":["../../src/PredictiveStudio/at-risk.view-models.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,2CAA2C;AAC3C,MAAM,WAAW,SAAS;IACxB,QAAQ,EAAE,MAAM,CAAC;IACjB;;;OAGG;IACH,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,iDAAiD;IACjD,KAAK,EAAE,MAAM,CAAC;IACd,4CAA4C;IAC5C,OAAO,EAAE,MAAM,CAAC;IAChB,4DAA4D;IAC5D,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,4BAA4B;IAC5B,IAAI,EAAE,MAAM,GAAG,QAAQ,GAAG,KAAK,CAAC;IAChC;;;;OAIG;IACH,OAAO,EAAE,SAAS,EAAE,GAAG,IAAI,CAAC;CAC7B;AAED,gFAAgF;AAChF,MAAM,WAAW,SAAS;IACxB,yDAAyD;IACzD,KAAK,EAAE,MAAM,CAAC;IACd,kDAAkD;IAClD,KAAK,EAAE,MAAM,CAAC;IACd,2DAA2D;IAC3D,EAAE,EAAE,OAAO,CAAC;CACb;AAkBD;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,GAAG,IAAI,GAAG,MAAM,GAAG,IAAI,CAe9F;AAED,+FAA+F;AAC/F,MAAM,WAAW,aAAa;IAC5B,QAAQ,EAAE,MAAM,CAAC;IACjB,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC/B;AAMD,0FAA0F;AAC1F,wBAAgB,eAAe,CAAC,OAAO,EAAE,aAAa,EAAE,GAAG,SAAS,EAAE,CA8BrE;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,qBAAqB,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EAAE,CAAC,SAAI,GAAG,MAAM,EAAE,CA4BlG;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAQxD"}
|
|
@@ -8,6 +8,50 @@
|
|
|
8
8
|
*
|
|
9
9
|
* Framework-free + deterministic → unit-tested with no Angular.
|
|
10
10
|
*/
|
|
11
|
+
/** Parse + humanize the raw per-record `drivers` (post-preprocessing `feature`/`value`) into {@link RowDriver}s. */
|
|
12
|
+
function parseRowDrivers(raw) {
|
|
13
|
+
if (!Array.isArray(raw) || raw.length === 0)
|
|
14
|
+
return null;
|
|
15
|
+
const out = [];
|
|
16
|
+
for (const d of raw) {
|
|
17
|
+
const feature = typeof d?.feature === 'string' ? d.feature : '';
|
|
18
|
+
const value = typeof d?.value === 'number' ? d.value : NaN;
|
|
19
|
+
if (!feature || !Number.isFinite(value))
|
|
20
|
+
continue;
|
|
21
|
+
// Keep the one-hot category: for a per-record "why", the category IS the story — "Membership Type =
|
|
22
|
+
// Student lowers risk" is actionable where a collapsed "Membership Type" is close to meaningless.
|
|
23
|
+
// (Collapsing across categories is only right for GLOBAL importance — see topGlobalDrivers.)
|
|
24
|
+
out.push({ label: humanizeFeatureName(feature), value, up: value > 0 });
|
|
25
|
+
}
|
|
26
|
+
return out.length > 0 ? out : null;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Build a human-readable label for a record from its (simple) row of the target entity — preferring a
|
|
30
|
+
* single `Name`, else `FirstName`+`LastName`, else `Email`, else any first non-empty string field.
|
|
31
|
+
* Returns null when nothing usable is found (caller falls back to the record id).
|
|
32
|
+
*/
|
|
33
|
+
export function labelFromRecord(row) {
|
|
34
|
+
if (!row)
|
|
35
|
+
return null;
|
|
36
|
+
const str = (v) => (typeof v === 'string' ? v.trim() : '');
|
|
37
|
+
const name = str(row['Name']);
|
|
38
|
+
if (name)
|
|
39
|
+
return name;
|
|
40
|
+
const full = `${str(row['FirstName'])} ${str(row['LastName'])}`.trim();
|
|
41
|
+
const email = str(row['Email']);
|
|
42
|
+
if (full && email)
|
|
43
|
+
return `${full} (${email})`;
|
|
44
|
+
if (full)
|
|
45
|
+
return full;
|
|
46
|
+
if (email)
|
|
47
|
+
return email;
|
|
48
|
+
for (const v of Object.values(row)) {
|
|
49
|
+
const s = str(v);
|
|
50
|
+
if (s && s.length <= 120)
|
|
51
|
+
return s;
|
|
52
|
+
}
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
11
55
|
function bandFor(score) {
|
|
12
56
|
return score >= 0.7 ? 'high' : score >= 0.4 ? 'medium' : 'low';
|
|
13
57
|
}
|
|
@@ -29,7 +73,15 @@ export function parseAtRiskRows(details) {
|
|
|
29
73
|
if (typeof p.score !== 'number' || !Number.isFinite(p.score))
|
|
30
74
|
continue;
|
|
31
75
|
const score = p.score;
|
|
32
|
-
rows.push({
|
|
76
|
+
rows.push({
|
|
77
|
+
recordId: d.recordId,
|
|
78
|
+
label: null,
|
|
79
|
+
score,
|
|
80
|
+
riskPct: Math.round(score * 100),
|
|
81
|
+
class: p.class ?? null,
|
|
82
|
+
band: bandFor(score),
|
|
83
|
+
drivers: parseRowDrivers(p.drivers),
|
|
84
|
+
});
|
|
33
85
|
}
|
|
34
86
|
return rows.sort((a, b) => b.score - a.score);
|
|
35
87
|
}
|
|
@@ -72,6 +124,21 @@ export function topGlobalDrivers(featureImportanceJson, n = 3) {
|
|
|
72
124
|
continue;
|
|
73
125
|
byFeature.set(base, Math.max(byFeature.get(base) ?? 0, weight));
|
|
74
126
|
}
|
|
75
|
-
return [...byFeature.entries()].sort((a, b) => b[1] - a[1]).slice(0, n).map(([name]) => name);
|
|
127
|
+
return [...byFeature.entries()].sort((a, b) => b[1] - a[1]).slice(0, n).map(([name]) => humanizeFeatureName(name));
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Turn a raw feature/column name into a readable label — `RetentionOverdueInvoices` → `Retention Overdue
|
|
131
|
+
* Invoices`, `overdue_invoices` → `Overdue Invoices`, and a one-hot `MembershipType=Student` →
|
|
132
|
+
* `Membership Type = Student`. Splits camelCase + snake/kebab, spaces one-hot `=`, collapses whitespace,
|
|
133
|
+
* and capitalizes the first letter. Already-spaced labels pass through unchanged.
|
|
134
|
+
*/
|
|
135
|
+
export function humanizeFeatureName(name) {
|
|
136
|
+
return name
|
|
137
|
+
.replace(/[_-]+/g, ' ')
|
|
138
|
+
.replace(/([a-z0-9])([A-Z])/g, '$1 $2')
|
|
139
|
+
.replace(/\s*=\s*/g, ' = ')
|
|
140
|
+
.replace(/\s+/g, ' ')
|
|
141
|
+
.trim()
|
|
142
|
+
.replace(/^\w/, (c) => c.toUpperCase());
|
|
76
143
|
}
|
|
77
144
|
//# sourceMappingURL=at-risk.view-models.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"at-risk.view-models.js","sourceRoot":"","sources":["../../src/PredictiveStudio/at-risk.view-models.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Pure derivations for the business workspace's **ranked at-risk list** — turning a model's per-record\n * scoring results (`MJ: Process Run Details` payloads) into a \"who to focus on\" list sorted by risk, and\n * the model's global feature importance into plain-language \"what's driving this\" drivers.\n *\n * Honest about the gap: MJ stores only GLOBAL feature importance (not per-record/SHAP), so we surface the\n * top global drivers once for the whole prediction rather than fabricating per-member attribution.\n *\n * Framework-free + deterministic → unit-tested with no Angular.\n */\n\n/** A scored record in the at-risk list. */\nexport interface AtRiskRow {\n recordId: string;\n /** 0–1 prediction score (probability / risk). */\n score: number;\n /** Risk as a 0–100 integer, for display. */\n riskPct: number;\n /** Predicted class label, when present (classification). */\n class: string | null;\n /** Risk band, for color. */\n band: 'high' | 'medium' | 'low';\n}\n\n/** The raw per-record detail the list is built from (a slice of `MJ: Process Run Details`). */\nexport interface RunDetailLike {\n recordId: string;\n ResultPayload?: string | null;\n}\n\nfunction bandFor(score: number): AtRiskRow['band'] {\n return score >= 0.7 ? 'high' : score >= 0.4 ? 'medium' : 'low';\n}\n\n/** Parse + rank the per-record predictions into the at-risk list (highest risk first). */\nexport function parseAtRiskRows(details: RunDetailLike[]): AtRiskRow[] {\n const rows: AtRiskRow[] = [];\n for (const d of details) {\n if (!d.ResultPayload) continue;\n let parsed: { score?: number; class?: string; output?: { score?: number; class?: string } };\n try {\n parsed = JSON.parse(d.ResultPayload);\n } catch {\n continue;\n }\n // Write-back runs nest the prediction under `output`; generic runs carry it at the top level.\n const p = parsed.output ?? parsed;\n if (typeof p.score !== 'number' || !Number.isFinite(p.score)) continue;\n const score = p.score;\n rows.push({ recordId: d.recordId, score, riskPct: Math.round(score * 100), class: p.class ?? null, band: bandFor(score) });\n }\n return rows.sort((a, b) => b.score - a.score);\n}\n\n/**\n * The top plain-language drivers for the whole prediction, from the model's global feature importance.\n * Accepts the object form (`{\"MembershipType=Student\": 0.9, ...}`) or the array form\n * (`[{feature, importance}]`), strips one-hot `=value` suffixes, de-duplicates, and returns the\n * highest-importance feature names — so a business user sees \"what's driving this\", not raw weights.\n */\nexport function topGlobalDrivers(featureImportanceJson: string | null | undefined, n = 3): string[] {\n if (!featureImportanceJson) return [];\n let parsed: unknown;\n try {\n parsed = JSON.parse(featureImportanceJson);\n } catch {\n return [];\n }\n const pairs: Array<{ name: string; weight: number }> = [];\n if (Array.isArray(parsed)) {\n for (const e of parsed as Array<Record<string, unknown>>) {\n const name = (e.feature ?? e.name) as string | undefined;\n const weight = (e.importance ?? e.value) as number | undefined;\n if (typeof name === 'string' && typeof weight === 'number') pairs.push({ name, weight: Math.abs(weight) });\n }\n } else if (parsed && typeof parsed === 'object') {\n for (const [name, weight] of Object.entries(parsed as Record<string, unknown>)) {\n if (typeof weight === 'number') pairs.push({ name, weight: Math.abs(weight) });\n }\n }\n // Collapse one-hot columns (\"Feature=Value\" → \"Feature\"), keeping each feature's max weight.\n const byFeature = new Map<string, number>();\n for (const { name, weight } of pairs) {\n const base = name.split('=')[0].trim();\n if (!base) continue;\n byFeature.set(base, Math.max(byFeature.get(base) ?? 0, weight));\n }\n return [...byFeature.entries()].sort((a, b) => b[1] - a[1]).slice(0, n).map(([name]) => name);\n}\n"]}
|
|
1
|
+
{"version":3,"file":"at-risk.view-models.js","sourceRoot":"","sources":["../../src/PredictiveStudio/at-risk.view-models.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Pure derivations for the business workspace's **ranked at-risk list** — turning a model's per-record\n * scoring results (`MJ: Process Run Details` payloads) into a \"who to focus on\" list sorted by risk, and\n * the model's global feature importance into plain-language \"what's driving this\" drivers.\n *\n * Honest about the gap: MJ stores only GLOBAL feature importance (not per-record/SHAP), so we surface the\n * top global drivers once for the whole prediction rather than fabricating per-member attribution.\n *\n * Framework-free + deterministic → unit-tested with no Angular.\n */\n\n/** A scored record in the at-risk list. */\nexport interface AtRiskRow {\n recordId: string;\n /**\n * Human-readable label for the record (e.g. the member's name/email), resolved from the model's\n * target entity. Null until resolved; the UI falls back to {@link recordId} so the row is never blank.\n */\n label: string | null;\n /** 0–1 prediction score (probability / risk). */\n score: number;\n /** Risk as a 0–100 integer, for display. */\n riskPct: number;\n /** Predicted class label, when present (classification). */\n class: string | null;\n /** Risk band, for color. */\n band: 'high' | 'medium' | 'low';\n /**\n * Top signed per-record drivers behind THIS row's prediction (P1-5), humanized + one-hot-collapsed for\n * display. `up: true` pushed the risk up, `false` down. Null when the model doesn't produce per-record\n * attribution (tree/ensemble/multiclass) — the UI then shows the model's global drivers instead.\n */\n drivers: RowDriver[] | null;\n}\n\n/** A humanized, signed per-record driver for the at-risk row's inline \"why\". */\nexport interface RowDriver {\n /** Display label (humanized, one-hot base collapsed). */\n label: string;\n /** Signed contribution magnitude for this row. */\n value: number;\n /** Whether this pushed the risk UP (value > 0) or down. */\n up: boolean;\n}\n\n/** Parse + humanize the raw per-record `drivers` (post-preprocessing `feature`/`value`) into {@link RowDriver}s. */\nfunction parseRowDrivers(raw: unknown): RowDriver[] | null {\n if (!Array.isArray(raw) || raw.length === 0) return null;\n const out: RowDriver[] = [];\n for (const d of raw as Array<{ feature?: unknown; value?: unknown }>) {\n const feature = typeof d?.feature === 'string' ? d.feature : '';\n const value = typeof d?.value === 'number' ? d.value : NaN;\n if (!feature || !Number.isFinite(value)) continue;\n // Keep the one-hot category: for a per-record \"why\", the category IS the story — \"Membership Type =\n // Student lowers risk\" is actionable where a collapsed \"Membership Type\" is close to meaningless.\n // (Collapsing across categories is only right for GLOBAL importance — see topGlobalDrivers.)\n out.push({ label: humanizeFeatureName(feature), value, up: value > 0 });\n }\n return out.length > 0 ? out : null;\n}\n\n/**\n * Build a human-readable label for a record from its (simple) row of the target entity — preferring a\n * single `Name`, else `FirstName`+`LastName`, else `Email`, else any first non-empty string field.\n * Returns null when nothing usable is found (caller falls back to the record id).\n */\nexport function labelFromRecord(row: Record<string, unknown> | undefined | null): string | null {\n if (!row) return null;\n const str = (v: unknown): string => (typeof v === 'string' ? v.trim() : '');\n const name = str(row['Name']);\n if (name) return name;\n const full = `${str(row['FirstName'])} ${str(row['LastName'])}`.trim();\n const email = str(row['Email']);\n if (full && email) return `${full} (${email})`;\n if (full) return full;\n if (email) return email;\n for (const v of Object.values(row)) {\n const s = str(v);\n if (s && s.length <= 120) return s;\n }\n return null;\n}\n\n/** The raw per-record detail the list is built from (a slice of `MJ: Process Run Details`). */\nexport interface RunDetailLike {\n recordId: string;\n ResultPayload?: string | null;\n}\n\nfunction bandFor(score: number): AtRiskRow['band'] {\n return score >= 0.7 ? 'high' : score >= 0.4 ? 'medium' : 'low';\n}\n\n/** Parse + rank the per-record predictions into the at-risk list (highest risk first). */\nexport function parseAtRiskRows(details: RunDetailLike[]): AtRiskRow[] {\n const rows: AtRiskRow[] = [];\n for (const d of details) {\n if (!d.ResultPayload) continue;\n let parsed: {\n score?: number;\n class?: string;\n drivers?: unknown;\n output?: { score?: number; class?: string; drivers?: unknown };\n };\n try {\n parsed = JSON.parse(d.ResultPayload);\n } catch {\n continue;\n }\n // Write-back runs nest the prediction under `output`; generic runs carry it at the top level.\n const p = parsed.output ?? parsed;\n if (typeof p.score !== 'number' || !Number.isFinite(p.score)) continue;\n const score = p.score;\n rows.push({\n recordId: d.recordId,\n label: null,\n score,\n riskPct: Math.round(score * 100),\n class: p.class ?? null,\n band: bandFor(score),\n drivers: parseRowDrivers(p.drivers),\n });\n }\n return rows.sort((a, b) => b.score - a.score);\n}\n\n/**\n * The top plain-language drivers for the whole prediction, from the model's global feature importance.\n * Accepts the object form (`{\"MembershipType=Student\": 0.9, ...}`) or the array form\n * (`[{feature, importance}]`), strips one-hot `=value` suffixes, de-duplicates, and returns the\n * highest-importance feature names — so a business user sees \"what's driving this\", not raw weights.\n */\nexport function topGlobalDrivers(featureImportanceJson: string | null | undefined, n = 3): string[] {\n if (!featureImportanceJson) return [];\n let parsed: unknown;\n try {\n parsed = JSON.parse(featureImportanceJson);\n } catch {\n return [];\n }\n const pairs: Array<{ name: string; weight: number }> = [];\n if (Array.isArray(parsed)) {\n for (const e of parsed as Array<Record<string, unknown>>) {\n const name = (e.feature ?? e.name) as string | undefined;\n const weight = (e.importance ?? e.value) as number | undefined;\n if (typeof name === 'string' && typeof weight === 'number') pairs.push({ name, weight: Math.abs(weight) });\n }\n } else if (parsed && typeof parsed === 'object') {\n for (const [name, weight] of Object.entries(parsed as Record<string, unknown>)) {\n if (typeof weight === 'number') pairs.push({ name, weight: Math.abs(weight) });\n }\n }\n // Collapse one-hot columns (\"Feature=Value\" → \"Feature\"), keeping each feature's max weight.\n const byFeature = new Map<string, number>();\n for (const { name, weight } of pairs) {\n const base = name.split('=')[0].trim();\n if (!base) continue;\n byFeature.set(base, Math.max(byFeature.get(base) ?? 0, weight));\n }\n return [...byFeature.entries()].sort((a, b) => b[1] - a[1]).slice(0, n).map(([name]) => humanizeFeatureName(name));\n}\n\n/**\n * Turn a raw feature/column name into a readable label — `RetentionOverdueInvoices` → `Retention Overdue\n * Invoices`, `overdue_invoices` → `Overdue Invoices`, and a one-hot `MembershipType=Student` →\n * `Membership Type = Student`. Splits camelCase + snake/kebab, spaces one-hot `=`, collapses whitespace,\n * and capitalizes the first letter. Already-spaced labels pass through unchanged.\n */\nexport function humanizeFeatureName(name: string): string {\n return name\n .replace(/[_-]+/g, ' ')\n .replace(/([a-z0-9])([A-Z])/g, '$1 $2')\n .replace(/\\s*=\\s*/g, ' = ')\n .replace(/\\s+/g, ' ')\n .trim()\n .replace(/^\\w/, (c) => c.toUpperCase());\n}\n"]}
|
|
@@ -443,7 +443,7 @@ export class PSCatalogComponent {
|
|
|
443
443
|
i0.ɵɵconditional(ctx.selectedUseCaseIds.length > 0 ? 15 : -1);
|
|
444
444
|
i0.ɵɵadvance();
|
|
445
445
|
i0.ɵɵconditional(ctx.cards.length === 0 ? 16 : 17);
|
|
446
|
-
} }, dependencies: [CommonModule, MJButtonDirective], styles: ["/* ============================================================================\n Predictive Studio \u2014 shared panel styles (design-token compliant)\n Maps the mockup design system onto MemberJunction semantic --mj-* tokens.\n Loaded by every PS panel component (styleUrls) under ViewEncapsulation.None,\n so the class names are global within the PS dashboard subtree.\n No hardcoded colors except the intentional categorical node/algorithm palette\n (declared once as --ps-* custom props below) which must stay distinct across\n light/dark to remain readable as diagram accents.\n ============================================================================ */\n\n/* Categorical accents for DAG node types + algorithm chips. These are\n data-visualization colors that must remain distinct regardless of theme\n (allowed exception #3 in the design-token rules). */\n.ps-panel {\n --ps-node-source: #6b7280;\n --ps-node-feature: #2050c8;\n --ps-node-embedding: #7b3ff0;\n --ps-node-target: #1f9d57;\n --ps-node-algorithm: #d98213;\n /* budget-gauge categorical accents (reuse the node palette so light/dark stay distinct) */\n --ps-gauge-cost: var(--ps-node-algorithm);\n --ps-gauge-iterations: var(--ps-node-feature);\n}\n\n.ps-panel {\n display: block;\n height: 100%;\n}\n\n/* ---- generic layout helpers ---- */\n.ps-row { display: flex; gap: 16px; }\n.ps-col { display: flex; flex-direction: column; gap: 16px; }\n.ps-wrap { flex-wrap: wrap; }\n.ps-spacer { flex: 1; }\n.ps-muted { color: var(--mj-text-muted); }\n.ps-small { font-size: 12.5px; }\n.ps-mono { font-family: ui-monospace, 'SF Mono', Menlo, Consolas, monospace; font-size: 12.5px; }\n.ps-section-title {\n font-size: 12px; text-transform: uppercase; letter-spacing: .06em;\n color: var(--mj-text-muted); margin: 4px 0 2px; font-weight: 700;\n}\n\n/* ---- cards ---- */\n.ps-card {\n background: var(--mj-bg-surface);\n border: 1px solid var(--mj-border-default);\n border-radius: 10px;\n box-shadow: var(--mj-shadow-sm);\n overflow: hidden;\n}\n.ps-card-head {\n display: flex; align-items: center; gap: 10px;\n padding: 14px 16px; border-bottom: 1px solid var(--mj-border-subtle);\n}\n.ps-card-head h3 { font-size: 14.5px; margin: 0; font-weight: 650; color: var(--mj-text-primary); }\n.ps-card-body { padding: 16px; }\n\n/* ---- KPI tiles ---- */\n.ps-kpi {\n background: var(--mj-bg-surface); border: 1px solid var(--mj-border-default);\n border-radius: 10px; padding: 16px 18px; box-shadow: var(--mj-shadow-sm);\n}\n.ps-kpi .label { font-size: 12px; color: var(--mj-text-muted); font-weight: 600; }\n.ps-kpi .val { font-size: 26px; font-weight: 720; letter-spacing: -.02em; margin-top: 2px; color: var(--mj-text-primary); }\n\n/* ---- badges ---- */\n.ps-badge {\n display: inline-flex; align-items: center; gap: 5px;\n padding: 2px 9px; border-radius: 999px; font-size: 11.5px; font-weight: 650; line-height: 1.7;\n}\n.ps-badge.gray { background: var(--mj-bg-surface-sunken); color: var(--mj-text-secondary); }\n.ps-badge.blue { background: var(--mj-status-info-bg); color: var(--mj-status-info-text); }\n.ps-badge.green { background: var(--mj-status-success-bg); color: var(--mj-status-success-text); }\n.ps-badge.amber { background: var(--mj-status-warning-bg); color: var(--mj-status-warning-text); }\n.ps-badge.red { background: var(--mj-status-error-bg); color: var(--mj-status-error-text); }\n.ps-dot { width: 7px; height: 7px; border-radius: 50%; display: inline-block; }\n\n/* recommendation-level chips (the ranking matrix payoff) */\n.ps-lvl {\n display: inline-flex; align-items: center; justify-content: center;\n min-width: 92px; padding: 4px 10px; border-radius: 7px;\n font-weight: 650; font-size: 11.5px; color: var(--mj-text-inverse);\n}\n.ps-lvl-Primary { background: var(--mj-status-success); }\n.ps-lvl-Strong { background: var(--mj-status-info); }\n.ps-lvl-Viable { background: var(--mj-status-warning); }\n.ps-lvl-Weak { background: var(--mj-text-disabled); }\n.ps-lvl-NotRecommended { background: var(--mj-bg-surface-sunken); color: var(--mj-text-muted); }\n\n/* ---- tags ---- */\n.ps-tag {\n display: inline-block; padding: 2px 8px; border-radius: 6px;\n background: var(--mj-bg-surface-sunken); color: var(--mj-text-secondary);\n font-size: 11.5px; font-weight: 600;\n}\n.ps-tag.muted { opacity: .6; }\n.ps-divider { height: 1px; background: var(--mj-border-subtle); margin: 8px 0; }\n\n/* ---- tables ---- */\n.ps-table { width: 100%; border-collapse: collapse; font-size: 13px; }\n.ps-table th {\n text-align: left; font-size: 11.5px; text-transform: uppercase; letter-spacing: .04em;\n color: var(--mj-text-muted); font-weight: 700; padding: 9px 12px;\n border-bottom: 1px solid var(--mj-border-default);\n}\n.ps-table td { padding: 11px 12px; border-bottom: 1px solid var(--mj-border-subtle); color: var(--mj-text-primary); }\n.ps-table tr:hover td { background: var(--mj-bg-surface-hover); }\n.ps-table .num { text-align: right; font-variant-numeric: tabular-nums; }\n\n.ps-rank {\n width: 22px; height: 22px; border-radius: 6px; background: var(--mj-bg-surface-sunken);\n display: inline-grid; place-items: center; font-weight: 700; font-size: 11.5px; color: var(--mj-text-secondary);\n}\n.ps-rank.gold { background: var(--mj-status-warning-bg); color: var(--mj-status-warning-text); }\n\n/* ---- progress bars ---- */\n.ps-bar { height: 8px; border-radius: 999px; background: var(--mj-bg-surface-sunken); overflow: hidden; }\n.ps-bar > span { display: block; height: 100%; border-radius: 999px; background: var(--mj-brand-primary); }\n.ps-bar.warn > span { background: var(--mj-status-warning); }\n.ps-bar.run > span { background: var(--mj-brand-primary); }\n\n.ps-fbar { display: grid; grid-template-columns: 150px 1fr 48px; align-items: center; gap: 10px; padding: 5px 0; }\n.ps-fbar .track { height: 14px; border-radius: 5px; background: var(--mj-bg-surface-sunken); overflow: hidden; }\n.ps-fbar .track > span { display: block; height: 100%; background: var(--mj-brand-primary); }\n.ps-fbar .track > span.warn { background: var(--mj-status-warning); }\n.ps-fbar .name { color: var(--mj-text-secondary); }\n\n/* gauge */\n.ps-gauge {\n position: relative; width: 64px; height: 64px; border-radius: 50%; flex: 0 0 auto;\n background: conic-gradient(var(--ps-gauge-color, var(--mj-brand-primary)) calc(var(--ps-p, 40) * 1%), var(--mj-bg-surface-sunken) 0);\n display: grid; place-items: center;\n}\n.ps-gauge::after { content: ''; position: absolute; inset: 9px; background: var(--mj-bg-surface); border-radius: 50%; }\n.ps-gauge .g-val { position: relative; font-weight: 720; font-size: 14px; color: var(--mj-text-primary); }\n\n/* warning / info callouts */\n.ps-callout { display: flex; gap: 11px; padding: 12px 14px; border-radius: 10px; border: 1px solid; }\n.ps-callout.warn { background: var(--mj-status-warning-bg); border-color: var(--mj-status-warning-border); color: var(--mj-status-warning-text); }\n.ps-callout.info { background: var(--mj-status-info-bg); border-color: var(--mj-status-info-border); color: var(--mj-status-info-text); }\n.ps-callout.success { background: var(--mj-status-success-bg); border-color: var(--mj-status-success-border); color: var(--mj-status-success-text); }\n.ps-callout i { font-size: 16px; margin-top: 1px; }\n\n/* lifecycle stepper */\n.ps-stepper { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }\n.ps-step { display: flex; align-items: center; gap: 6px; font-size: 12px; font-weight: 600; color: var(--mj-text-muted); }\n.ps-step .pip {\n width: 22px; height: 22px; border-radius: 50%; border: 2px solid var(--mj-border-strong);\n display: grid; place-items: center; font-size: 11px;\n}\n.ps-step.done { color: var(--mj-status-success-text); }\n.ps-step.done .pip { background: var(--mj-status-success); border-color: var(--mj-status-success); color: var(--mj-text-inverse); }\n.ps-step.curr { color: var(--mj-brand-primary); }\n.ps-step.curr .pip { border-color: var(--mj-brand-primary); color: var(--mj-brand-primary); }\n.ps-step .ln { width: 26px; height: 2px; background: var(--mj-border-strong); }\n\n/* ---- DAG flow canvas ---- */\n.ps-flow {\n position: relative;\n background:\n linear-gradient(var(--mj-border-subtle) 1px, transparent 1px) 0 0 / 22px 22px,\n linear-gradient(90deg, var(--mj-border-subtle) 1px, transparent 1px) 0 0 / 22px 22px,\n var(--mj-bg-surface-card);\n border: 1px solid var(--mj-border-default); border-radius: 10px;\n min-height: 620px; overflow: hidden;\n}\n.ps-node {\n position: absolute; width: 190px; background: var(--mj-bg-surface);\n border: 1px solid var(--mj-border-strong); border-left-width: 3px; border-radius: 10px;\n box-shadow: var(--mj-shadow-sm); cursor: pointer;\n}\n.ps-node .nh {\n display: flex; align-items: center; gap: 8px; padding: 9px 11px;\n border-bottom: 1px solid var(--mj-border-subtle); font-weight: 650; font-size: 12.5px; color: var(--mj-text-primary);\n}\n.ps-node .nh i.tile {\n width: 24px; height: 24px; border-radius: 6px; display: grid; place-items: center;\n color: var(--mj-text-inverse); font-size: 12px; flex: 0 0 auto;\n}\n.ps-node .nh .ps-tag { margin-left: auto; }\n.ps-node .nb { padding: 9px 11px; font-size: 12px; color: var(--mj-text-secondary); }\n.ps-node .nb-row { display: flex; justify-content: space-between; gap: 8px; padding: 2px 0; }\n.ps-node .nb-row .k { color: var(--mj-text-muted); }\n.ps-node.src { border-left-color: var(--ps-node-source); }\n.ps-node.src .nh i.tile { background: var(--ps-node-source); }\n.ps-node.feat { border-left-color: var(--ps-node-feature); }\n.ps-node.feat .nh i.tile { background: var(--ps-node-feature); }\n.ps-node.emb { border-left-color: var(--ps-node-embedding); }\n.ps-node.emb .nh i.tile { background: var(--ps-node-embedding); }\n.ps-node.target { border-left-color: var(--ps-node-target); }\n.ps-node.target .nh i.tile { background: var(--ps-node-target); }\n.ps-node.algo { border-left-color: var(--ps-node-algorithm); }\n.ps-node.algo .nh i.tile { background: var(--ps-node-algorithm); }\n.ps-node.output { border-left-color: var(--mj-status-success); }\n.ps-node.output .nh i.tile { background: var(--mj-status-success); }\n.ps-node.selected {\n border-color: var(--mj-brand-primary);\n box-shadow: 0 0 0 3px color-mix(in srgb, var(--mj-brand-primary) 18%, transparent);\n}\n.ps-node .port {\n position: absolute; width: 11px; height: 11px; border-radius: 50%;\n background: var(--mj-bg-surface); border: 2px solid var(--mj-brand-primary);\n top: 50%; transform: translateY(-50%);\n}\n.ps-node .port.in { left: -6px; }\n.ps-node .port.out { right: -6px; }\nsvg.ps-edges { position: absolute; inset: 0; width: 100%; height: 100%; pointer-events: none; }\nsvg.ps-edges path { stroke: color-mix(in srgb, var(--mj-brand-primary) 45%, transparent); stroke-width: 2; fill: none; }\n\n/* palette chips */\n.ps-pchip {\n display: inline-flex; align-items: center; gap: 7px; padding: 6px 11px; border-radius: 8px;\n border: 1px dashed var(--mj-border-strong); background: var(--mj-bg-surface);\n font-size: 12.5px; font-weight: 600; color: var(--mj-text-secondary); cursor: grab;\n}\n.ps-pchip i { font-size: 12px; color: var(--mj-text-inverse); width: 20px; height: 20px; border-radius: 5px; display: grid; place-items: center; }\n.ps-pchip.s i { background: var(--ps-node-source); }\n.ps-pchip.f i { background: var(--ps-node-feature); }\n.ps-pchip.e i { background: var(--ps-node-embedding); }\n.ps-pchip.t i { background: var(--ps-node-target); }\n.ps-pchip.a i { background: var(--ps-node-algorithm); }\n\n/* segmented control */\n.ps-seg { display: inline-flex; border: 1px solid var(--mj-border-default); border-radius: 8px; overflow: hidden; }\n.ps-seg button {\n border: none; background: var(--mj-bg-surface); color: var(--mj-text-secondary);\n padding: 6px 12px; font-size: 12.5px; font-weight: 600; cursor: pointer;\n border-right: 1px solid var(--mj-border-default);\n}\n.ps-seg button:last-child { border-right: none; }\n.ps-seg button.on { background: var(--mj-brand-primary); color: var(--mj-text-inverse); }\n\n/* field control */\n.ps-field { margin-bottom: 14px; }\n.ps-field > label {\n display: block; font-size: 11px; text-transform: uppercase; letter-spacing: .04em;\n color: var(--mj-text-muted); font-weight: 700; margin-bottom: 6px;\n}\n.ps-input {\n width: 100%; padding: 8px 10px; border: 1px solid var(--mj-border-default);\n border-radius: 6px; font-size: 13px; background: var(--mj-bg-surface); color: var(--mj-text-primary);\n box-sizing: border-box;\n}\n.ps-input:focus { outline: none; border-color: var(--mj-brand-primary); box-shadow: 0 0 0 3px color-mix(in srgb, var(--mj-brand-primary) 12%, transparent); }\n\n/* loading container */\n.ps-loading { display: flex; align-items: center; justify-content: center; padding: 60px 0; }\n\n/* ---- empty / prompt states ---- */\n.ps-empty {\n display: flex; flex-direction: column; align-items: center; justify-content: center;\n text-align: center; gap: 10px; padding: 56px 24px; color: var(--mj-text-muted);\n}\n.ps-empty .ps-empty-ico {\n width: 60px; height: 60px; border-radius: 16px; display: grid; place-items: center;\n background: var(--mj-bg-surface-sunken); color: var(--mj-text-disabled); font-size: 24px;\n}\n.ps-empty h3 { margin: 4px 0 0; font-size: 16px; font-weight: 650; color: var(--mj-text-secondary); }\n.ps-empty p { margin: 0; max-width: 420px; font-size: 13px; line-height: 1.5; }\n\n/* ---- confirm modal ---- */\n.ps-modal-backdrop {\n position: fixed; inset: 0; z-index: 1000;\n background: var(--mj-bg-overlay);\n display: flex; align-items: center; justify-content: center; padding: 24px;\n}\n.ps-modal {\n width: 100%; max-width: 480px; background: var(--mj-bg-surface);\n border: 1px solid var(--mj-border-default); border-radius: 14px;\n box-shadow: var(--mj-shadow-lg, var(--mj-shadow-sm)); overflow: hidden;\n}\n.ps-modal-head {\n display: flex; align-items: center; gap: 12px; padding: 18px 20px;\n border-bottom: 1px solid var(--mj-border-subtle);\n}\n.ps-modal-head .ico {\n width: 38px; height: 38px; border-radius: 10px; display: grid; place-items: center;\n font-size: 16px; flex: 0 0 auto;\n background: var(--mj-status-info-bg); color: var(--mj-status-info-text);\n}\n.ps-modal-head.warn .ico { background: var(--mj-status-warning-bg); color: var(--mj-status-warning-text); }\n.ps-modal-head.danger .ico { background: var(--mj-status-error-bg); color: var(--mj-status-error-text); }\n.ps-modal-head h3 { margin: 0; font-size: 15.5px; font-weight: 650; color: var(--mj-text-primary); }\n.ps-modal-body { padding: 18px 20px; font-size: 13.5px; line-height: 1.55; color: var(--mj-text-secondary); }\n.ps-modal-body .ps-field { margin-top: 14px; margin-bottom: 0; }\n.ps-modal-foot {\n display: flex; align-items: center; gap: 10px; padding: 14px 20px;\n border-top: 1px solid var(--mj-border-subtle); background: var(--mj-bg-surface-card);\n}\n.ps-modal-foot .ps-spacer { flex: 1; }\n", ".ps-catalog { display: flex; flex-direction: column; gap: 16px; }\n\n.ps-catalog .guide-card .guide-head { display: flex; gap: 12px; align-items: flex-start; }\n.ps-catalog .guide-head i { font-size: 18px; margin-top: 2px; }\n.ps-catalog .guide-head h3 { font-size: 14.5px; color: var(--mj-text-primary); margin-bottom: 4px; }\n.ps-catalog .guide-head .ps-muted { max-width: 560px; }\n\n.ps-catalog .chips-row { display: flex; align-items: center; gap: 12px; margin-top: 14px; flex-wrap: wrap; }\n.ps-catalog .chips { display: flex; gap: 8px; flex-wrap: wrap; }\n.ps-catalog .chip {\n display: inline-flex; align-items: center; gap: 7px; padding: 7px 13px; border-radius: 999px;\n border: 1px solid var(--mj-border-default); background: var(--mj-bg-surface);\n color: var(--mj-text-secondary); font-size: 12.5px; font-weight: 600; cursor: pointer; transition: all .12s;\n}\n.ps-catalog .chip:hover { background: var(--mj-bg-surface-hover); }\n.ps-catalog .chip.on { background: var(--mj-brand-primary); border-color: var(--mj-brand-primary); color: var(--mj-text-inverse); }\n.ps-catalog .chip i { font-size: 12px; }\n.ps-catalog .clearx {\n display: inline-flex; align-items: center; gap: 6px; background: transparent; border: none;\n color: var(--mj-text-muted); font-size: 12.5px; font-weight: 600; cursor: pointer;\n}\n.ps-catalog .clearx:hover { color: var(--mj-status-error-text); }\n.ps-catalog .reco-banner { margin-top: 14px; }\n\n/* gallery */\n.ps-catalog .gallery { display: grid; grid-template-columns: repeat(auto-fill, minmax(340px, 1fr)); gap: 16px; }\n.ps-catalog .acard {\n position: relative; display: flex; flex-direction: column;\n background: var(--mj-bg-surface); border: 1px solid var(--mj-border-default);\n border-radius: 10px; box-shadow: var(--mj-shadow-sm); overflow: hidden; transition: opacity .15s, box-shadow .15s, border-color .15s;\n}\n.ps-catalog .acard.hit { border-color: var(--mj-brand-primary); box-shadow: 0 0 0 3px color-mix(in srgb, var(--mj-brand-primary) 14%, transparent); }\n.ps-catalog .acard.dim { opacity: .46; }\n.ps-catalog .ribbon { position: absolute; top: 12px; right: 12px; z-index: 1; }\n\n.ps-catalog .ah { display: flex; align-items: center; gap: 12px; padding: 16px 16px 0; }\n.ps-catalog .ah .ic {\n width: 46px; height: 46px; border-radius: 12px; display: grid; place-items: center; font-size: 20px; flex: 0 0 auto;\n background: color-mix(in srgb, var(--mj-brand-primary) 12%, var(--mj-bg-surface)); color: var(--mj-brand-primary);\n}\n.ps-catalog .ah .nm { font-size: 15.5px; font-weight: 650; color: var(--mj-text-primary); }\n.ps-catalog .ah .key { color: var(--mj-text-muted); }\n\n.ps-catalog .ab { padding: 14px 16px; display: flex; flex-direction: column; gap: 11px; flex: 1; }\n.ps-catalog .kv-title { font-size: 11px; text-transform: uppercase; letter-spacing: .04em; color: var(--mj-text-muted); font-weight: 700; }\n.ps-catalog .meta-row { display: flex; flex-wrap: wrap; gap: 6px; }\n\n.ps-catalog .af {\n display: flex; align-items: center; gap: 8px; padding: 12px 16px;\n border-top: 1px solid var(--mj-border-subtle); background: var(--mj-bg-surface-card);\n}\n\n/* Inline algorithm detail panel (toggled by the Details button) */\n.ps-catalog .adetail {\n padding: 14px 16px; border-top: 1px solid var(--mj-border-subtle);\n background: var(--mj-bg-surface-sunken); display: flex; flex-direction: column; gap: 8px;\n}\n.ps-catalog .adetail-desc { margin: 0; line-height: 1.45; color: var(--mj-text-secondary); }\n.ps-catalog .dkv { display: flex; justify-content: space-between; gap: 12px; font-size: 12.5px; }\n.ps-catalog .dk { color: var(--mj-text-muted); font-weight: 650; }\n.ps-catalog .dv { color: var(--mj-text-primary); text-align: right; }\n.ps-catalog .hp-list { display: flex; flex-direction: column; gap: 3px; margin-top: 4px; }\n.ps-catalog .hp { display: flex; justify-content: space-between; gap: 12px; font-size: 12px; }\n.ps-catalog .adetail-foot { display: flex; justify-content: flex-start; margin-top: 6px; }\n"], encapsulation: 2 });
|
|
446
|
+
} }, dependencies: [CommonModule, MJButtonDirective], styles: ["/* ============================================================================\n Predictive Studio \u2014 shared panel styles (design-token compliant)\n Maps the mockup design system onto MemberJunction semantic --mj-* tokens.\n Loaded by every PS panel component (styleUrls) under ViewEncapsulation.None,\n so the class names are global within the PS dashboard subtree.\n No hardcoded colors except the intentional categorical node/algorithm palette\n (declared once as --ps-* custom props below) which must stay distinct across\n light/dark to remain readable as diagram accents.\n ============================================================================ */\n\n/* Categorical accents for DAG node types + algorithm chips. These are\n data-visualization colors that must remain distinct regardless of theme\n (allowed exception #3 in the design-token rules). */\n.ps-panel {\n --ps-node-source: #6b7280;\n --ps-node-feature: #2050c8;\n --ps-node-embedding: #7b3ff0;\n --ps-node-target: #1f9d57;\n --ps-node-algorithm: #d98213;\n /* budget-gauge categorical accents (reuse the node palette so light/dark stay distinct) */\n --ps-gauge-cost: var(--ps-node-algorithm);\n --ps-gauge-iterations: var(--ps-node-feature);\n}\n\n.ps-panel {\n display: block;\n height: 100%;\n}\n\n/* ---- generic layout helpers ---- */\n.ps-row { display: flex; gap: 16px; }\n.ps-col { display: flex; flex-direction: column; gap: 16px; }\n.ps-wrap { flex-wrap: wrap; }\n.ps-spacer { flex: 1; }\n.ps-muted { color: var(--mj-text-muted); }\n.ps-small { font-size: 12.5px; }\n.ps-mono { font-family: ui-monospace, 'SF Mono', Menlo, Consolas, monospace; font-size: 12.5px; }\n.ps-section-title {\n font-size: 12px; text-transform: uppercase; letter-spacing: .06em;\n color: var(--mj-text-muted); margin: 4px 0 2px; font-weight: 700;\n}\n\n/* ---- cards ---- */\n.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-card-head {\n display: flex; align-items: center; gap: 10px;\n padding: 14px 16px; border-bottom: 1px solid var(--mj-border-subtle);\n}\n.ps-card-head h3 { font-size: 14.5px; margin: 0; font-weight: 650; color: var(--mj-text-primary); }\n.ps-card-body { padding: 16px; }\n\n/* ---- KPI tiles ---- */\n.ps-kpi {\n background: var(--mj-bg-surface); border: 1px solid var(--mj-border-default);\n border-radius: 8px; padding: 16px 18px;\n}\n.ps-kpi .label { font-size: 12px; color: var(--mj-text-muted); font-weight: 600; }\n.ps-kpi .val { font-size: 26px; font-weight: 720; letter-spacing: -.02em; margin-top: 2px; color: var(--mj-text-primary); }\n\n/* ---- badges ---- */\n.ps-badge {\n display: inline-flex; align-items: center; gap: 5px;\n padding: 2px 9px; border-radius: 999px; font-size: 11.5px; font-weight: 650; line-height: 1.7;\n}\n.ps-badge.gray { background: var(--mj-bg-surface-sunken); color: var(--mj-text-secondary); }\n.ps-badge.blue { background: var(--mj-status-info-bg); color: var(--mj-status-info-text); }\n.ps-badge.green { background: var(--mj-status-success-bg); color: var(--mj-status-success-text); }\n.ps-badge.amber { background: var(--mj-status-warning-bg); color: var(--mj-status-warning-text); }\n.ps-badge.red { background: var(--mj-status-error-bg); color: var(--mj-status-error-text); }\n.ps-dot { width: 7px; height: 7px; border-radius: 50%; display: inline-block; }\n\n/* recommendation-level chips (the ranking matrix payoff) */\n.ps-lvl {\n display: inline-flex; align-items: center; justify-content: center;\n min-width: 92px; padding: 4px 10px; border-radius: 7px;\n font-weight: 650; font-size: 11.5px; color: var(--mj-text-inverse);\n}\n.ps-lvl-Primary { background: var(--mj-status-success); }\n.ps-lvl-Strong { background: var(--mj-status-info); }\n.ps-lvl-Viable { background: var(--mj-status-warning); }\n.ps-lvl-Weak { background: var(--mj-text-disabled); }\n.ps-lvl-NotRecommended { background: var(--mj-bg-surface-sunken); color: var(--mj-text-muted); }\n\n/* ---- tags ---- */\n.ps-tag {\n display: inline-block; padding: 2px 8px; border-radius: 6px;\n background: var(--mj-bg-surface-sunken); color: var(--mj-text-secondary);\n font-size: 11.5px; font-weight: 600;\n}\n.ps-tag.muted { opacity: .6; }\n.ps-divider { height: 1px; background: var(--mj-border-subtle); margin: 8px 0; }\n\n/* ---- tables ---- */\n.ps-table { width: 100%; border-collapse: collapse; font-size: 13px; }\n.ps-table th {\n text-align: left; font-size: 11.5px; text-transform: uppercase; letter-spacing: .04em;\n color: var(--mj-text-muted); font-weight: 700; padding: 9px 12px;\n border-bottom: 1px solid var(--mj-border-default);\n}\n.ps-table td { padding: 11px 12px; border-bottom: 1px solid var(--mj-border-subtle); color: var(--mj-text-primary); }\n.ps-table tr:hover td { background: var(--mj-bg-surface-hover); }\n.ps-table .num { text-align: right; font-variant-numeric: tabular-nums; }\n\n.ps-rank {\n width: 22px; height: 22px; border-radius: 6px; background: var(--mj-bg-surface-sunken);\n display: inline-grid; place-items: center; font-weight: 700; font-size: 11.5px; color: var(--mj-text-secondary);\n}\n.ps-rank.gold { background: var(--mj-status-warning-bg); color: var(--mj-status-warning-text); }\n\n/* ---- progress bars ---- */\n.ps-bar { height: 8px; border-radius: 999px; background: var(--mj-bg-surface-sunken); overflow: hidden; }\n.ps-bar > span { display: block; height: 100%; border-radius: 999px; background: var(--mj-brand-primary); }\n.ps-bar.warn > span { background: var(--mj-status-warning); }\n.ps-bar.run > span { background: var(--mj-brand-primary); }\n\n.ps-fbar { display: grid; grid-template-columns: minmax(150px, 1.3fr) 2fr 56px; align-items: center; gap: 14px; padding: 8px 0; }\n.ps-fbar + .ps-fbar { border-top: 1px solid var(--mj-border-subtle); }\n.ps-fbar .track { height: 10px; border-radius: var(--mj-radius-full); background: var(--mj-bg-surface-sunken); overflow: hidden; }\n.ps-fbar .track > span { display: block; height: 100%; border-radius: var(--mj-radius-full); background: var(--mj-brand-primary); }\n.ps-fbar .track > span.warn { background: var(--mj-status-warning); }\n.ps-fbar .name { color: var(--mj-text-primary); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n.ps-fbar > .ps-mono { text-align: right; color: var(--mj-text-secondary); font-variant-numeric: tabular-nums; }\n\n/* gauge */\n.ps-gauge {\n position: relative; width: 64px; height: 64px; border-radius: 50%; flex: 0 0 auto;\n background: conic-gradient(var(--ps-gauge-color, var(--mj-brand-primary)) calc(var(--ps-p, 40) * 1%), var(--mj-bg-surface-sunken) 0);\n display: grid; place-items: center;\n}\n.ps-gauge::after { content: ''; position: absolute; inset: 9px; background: var(--mj-bg-surface); border-radius: 50%; }\n.ps-gauge .g-val { position: relative; font-weight: 720; font-size: 14px; color: var(--mj-text-primary); }\n\n/* warning / info callouts */\n.ps-callout { display: flex; gap: 11px; padding: 12px 14px; border-radius: 10px; border: 1px solid; }\n.ps-callout.warn { background: var(--mj-status-warning-bg); border-color: var(--mj-status-warning-border); color: var(--mj-status-warning-text); }\n.ps-callout.info { background: var(--mj-status-info-bg); border-color: var(--mj-status-info-border); color: var(--mj-status-info-text); }\n.ps-callout.success { background: var(--mj-status-success-bg); border-color: var(--mj-status-success-border); color: var(--mj-status-success-text); }\n.ps-callout i { font-size: 16px; margin-top: 1px; }\n\n/* lifecycle stepper */\n.ps-stepper { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }\n.ps-step { display: flex; align-items: center; gap: 6px; font-size: 12px; font-weight: 600; color: var(--mj-text-muted); }\n.ps-step .pip {\n width: 22px; height: 22px; border-radius: 50%; border: 2px solid var(--mj-border-strong);\n display: grid; place-items: center; font-size: 11px;\n}\n.ps-step.done { color: var(--mj-status-success-text); }\n.ps-step.done .pip { background: var(--mj-status-success); border-color: var(--mj-status-success); color: var(--mj-text-inverse); }\n.ps-step.curr { color: var(--mj-brand-primary); }\n.ps-step.curr .pip { border-color: var(--mj-brand-primary); color: var(--mj-brand-primary); }\n.ps-step .ln { width: 26px; height: 2px; background: var(--mj-border-strong); }\n\n/* ---- DAG flow canvas ---- */\n.ps-flow {\n position: relative;\n background:\n linear-gradient(var(--mj-border-subtle) 1px, transparent 1px) 0 0 / 22px 22px,\n linear-gradient(90deg, var(--mj-border-subtle) 1px, transparent 1px) 0 0 / 22px 22px,\n var(--mj-bg-surface-card);\n border: 1px solid var(--mj-border-default); border-radius: 10px;\n min-height: 620px; overflow: hidden;\n}\n.ps-node {\n position: absolute; width: 190px; background: var(--mj-bg-surface);\n border: 1px solid var(--mj-border-strong); border-left-width: 3px; border-radius: 8px;\n cursor: pointer;\n}\n.ps-node .nh {\n display: flex; align-items: center; gap: 8px; padding: 9px 11px;\n border-bottom: 1px solid var(--mj-border-subtle); font-weight: 650; font-size: 12.5px; color: var(--mj-text-primary);\n}\n.ps-node .nh i.tile {\n width: 24px; height: 24px; border-radius: 6px; display: grid; place-items: center;\n color: var(--mj-text-inverse); font-size: 12px; flex: 0 0 auto;\n}\n.ps-node .nh .ps-tag { margin-left: auto; }\n.ps-node .nb { padding: 9px 11px; font-size: 12px; color: var(--mj-text-secondary); }\n.ps-node .nb-row { display: flex; justify-content: space-between; gap: 8px; padding: 2px 0; }\n.ps-node .nb-row .k { color: var(--mj-text-muted); }\n.ps-node.src { border-left-color: var(--ps-node-source); }\n.ps-node.src .nh i.tile { background: var(--ps-node-source); }\n.ps-node.feat { border-left-color: var(--ps-node-feature); }\n.ps-node.feat .nh i.tile { background: var(--ps-node-feature); }\n.ps-node.emb { border-left-color: var(--ps-node-embedding); }\n.ps-node.emb .nh i.tile { background: var(--ps-node-embedding); }\n.ps-node.target { border-left-color: var(--ps-node-target); }\n.ps-node.target .nh i.tile { background: var(--ps-node-target); }\n.ps-node.algo { border-left-color: var(--ps-node-algorithm); }\n.ps-node.algo .nh i.tile { background: var(--ps-node-algorithm); }\n.ps-node.output { border-left-color: var(--mj-status-success); }\n.ps-node.output .nh i.tile { background: var(--mj-status-success); }\n.ps-node.selected {\n border-color: var(--mj-brand-primary);\n box-shadow: 0 0 0 3px color-mix(in srgb, var(--mj-brand-primary) 18%, transparent);\n}\n.ps-node .port {\n position: absolute; width: 11px; height: 11px; border-radius: 50%;\n background: var(--mj-bg-surface); border: 2px solid var(--mj-brand-primary);\n top: 50%; transform: translateY(-50%);\n}\n.ps-node .port.in { left: -6px; }\n.ps-node .port.out { right: -6px; }\nsvg.ps-edges { position: absolute; inset: 0; width: 100%; height: 100%; pointer-events: none; }\nsvg.ps-edges path { stroke: color-mix(in srgb, var(--mj-brand-primary) 45%, transparent); stroke-width: 2; fill: none; }\n\n/* palette chips */\n.ps-pchip {\n display: inline-flex; align-items: center; gap: 7px; padding: 6px 11px; border-radius: 8px;\n border: 1px dashed var(--mj-border-strong); background: var(--mj-bg-surface);\n font-size: 12.5px; font-weight: 600; color: var(--mj-text-secondary); cursor: grab;\n}\n.ps-pchip i { font-size: 12px; color: var(--mj-text-inverse); width: 20px; height: 20px; border-radius: 5px; display: grid; place-items: center; }\n.ps-pchip.s i { background: var(--ps-node-source); }\n.ps-pchip.f i { background: var(--ps-node-feature); }\n.ps-pchip.e i { background: var(--ps-node-embedding); }\n.ps-pchip.t i { background: var(--ps-node-target); }\n.ps-pchip.a i { background: var(--ps-node-algorithm); }\n\n/* segmented control */\n.ps-seg { display: inline-flex; border: 1px solid var(--mj-border-default); border-radius: 8px; overflow: hidden; }\n.ps-seg button {\n border: none; background: var(--mj-bg-surface); color: var(--mj-text-secondary);\n padding: 6px 12px; font-size: 12.5px; font-weight: 600; cursor: pointer;\n border-right: 1px solid var(--mj-border-default);\n}\n.ps-seg button:last-child { border-right: none; }\n.ps-seg button.on { background: var(--mj-brand-primary); color: var(--mj-text-inverse); }\n\n/* field control */\n.ps-field { margin-bottom: 14px; }\n.ps-field > label {\n display: block; font-size: 11px; text-transform: uppercase; letter-spacing: .04em;\n color: var(--mj-text-muted); font-weight: 700; margin-bottom: 6px;\n}\n.ps-input {\n width: 100%; padding: 8px 10px; border: 1px solid var(--mj-border-default);\n border-radius: 6px; font-size: 13px; background: var(--mj-bg-surface); color: var(--mj-text-primary);\n box-sizing: border-box;\n}\n.ps-input:focus { outline: none; border-color: var(--mj-brand-primary); box-shadow: 0 0 0 3px color-mix(in srgb, var(--mj-brand-primary) 12%, transparent); }\n\n/* loading container */\n.ps-loading { display: flex; align-items: center; justify-content: center; padding: 60px 0; }\n\n/* ---- empty / prompt states ---- */\n.ps-empty {\n display: flex; flex-direction: column; align-items: center; justify-content: center;\n text-align: center; gap: 10px; padding: 56px 24px; color: var(--mj-text-muted);\n}\n.ps-empty .ps-empty-ico {\n width: 60px; height: 60px; border-radius: 16px; display: grid; place-items: center;\n background: var(--mj-bg-surface-sunken); color: var(--mj-text-disabled); font-size: 24px;\n}\n.ps-empty h3 { margin: 4px 0 0; font-size: 16px; font-weight: 650; color: var(--mj-text-secondary); }\n.ps-empty p { margin: 0; max-width: 420px; font-size: 13px; line-height: 1.5; }\n\n/* ---- confirm modal ---- */\n.ps-modal-backdrop {\n position: fixed; inset: 0; z-index: 1000;\n background: var(--mj-bg-overlay);\n display: flex; align-items: center; justify-content: center; padding: 24px;\n}\n.ps-modal {\n width: 100%; max-width: 480px; background: var(--mj-bg-surface);\n border: 1px solid var(--mj-border-default); border-radius: 14px;\n box-shadow: var(--mj-shadow-lg, var(--mj-shadow-sm)); overflow: hidden;\n}\n.ps-modal-head {\n display: flex; align-items: center; gap: 12px; padding: 18px 20px;\n border-bottom: 1px solid var(--mj-border-subtle);\n}\n.ps-modal-head .ico {\n width: 38px; height: 38px; border-radius: 10px; display: grid; place-items: center;\n font-size: 16px; flex: 0 0 auto;\n background: var(--mj-status-info-bg); color: var(--mj-status-info-text);\n}\n.ps-modal-head.warn .ico { background: var(--mj-status-warning-bg); color: var(--mj-status-warning-text); }\n.ps-modal-head.danger .ico { background: var(--mj-status-error-bg); color: var(--mj-status-error-text); }\n.ps-modal-head h3 { margin: 0; font-size: 15.5px; font-weight: 650; color: var(--mj-text-primary); }\n.ps-modal-body { padding: 18px 20px; font-size: 13.5px; line-height: 1.55; color: var(--mj-text-secondary); }\n.ps-modal-body .ps-field { margin-top: 14px; margin-bottom: 0; }\n.ps-modal-foot {\n display: flex; align-items: center; gap: 10px; padding: 14px 20px;\n border-top: 1px solid var(--mj-border-subtle); background: var(--mj-bg-surface-card);\n}\n.ps-modal-foot .ps-spacer { flex: 1; }\n", ".ps-catalog { display: flex; flex-direction: column; gap: 16px; }\n\n.ps-catalog .guide-card .guide-head { display: flex; gap: 12px; align-items: flex-start; }\n.ps-catalog .guide-head i { font-size: 18px; margin-top: 2px; }\n.ps-catalog .guide-head h3 { font-size: 14.5px; color: var(--mj-text-primary); margin-bottom: 4px; }\n.ps-catalog .guide-head .ps-muted { max-width: 560px; }\n\n.ps-catalog .chips-row { display: flex; align-items: center; gap: 12px; margin-top: 14px; flex-wrap: wrap; }\n.ps-catalog .chips { display: flex; gap: 8px; flex-wrap: wrap; }\n.ps-catalog .chip {\n display: inline-flex; align-items: center; gap: 7px; padding: 7px 13px; border-radius: 999px;\n border: 1px solid var(--mj-border-default); background: var(--mj-bg-surface);\n color: var(--mj-text-secondary); font-size: 12.5px; font-weight: 600; cursor: pointer; transition: all .12s;\n}\n.ps-catalog .chip:hover { background: var(--mj-bg-surface-hover); }\n.ps-catalog .chip.on { background: var(--mj-brand-primary); border-color: var(--mj-brand-primary); color: var(--mj-text-inverse); }\n.ps-catalog .chip i { font-size: 12px; }\n.ps-catalog .clearx {\n display: inline-flex; align-items: center; gap: 6px; background: transparent; border: none;\n color: var(--mj-text-muted); font-size: 12.5px; font-weight: 600; cursor: pointer;\n}\n.ps-catalog .clearx:hover { color: var(--mj-status-error-text); }\n.ps-catalog .reco-banner { margin-top: 14px; }\n\n/* gallery */\n.ps-catalog .gallery { display: grid; grid-template-columns: repeat(auto-fill, minmax(340px, 1fr)); gap: 16px; }\n.ps-catalog .acard {\n position: relative; display: flex; flex-direction: column;\n background: var(--mj-bg-surface); border: 1px solid var(--mj-border-default);\n border-radius: 8px; overflow: hidden; transition: opacity .15s, border-color .12s, background .12s;\n}\n.ps-catalog .acard:hover { border-color: var(--mj-brand-primary); background: color-mix(in srgb, var(--mj-brand-primary) 4%, var(--mj-bg-surface)); }\n.ps-catalog .acard.hit { border-color: var(--mj-brand-primary); box-shadow: 0 0 0 3px color-mix(in srgb, var(--mj-brand-primary) 14%, transparent); }\n.ps-catalog .acard.dim { opacity: .46; }\n.ps-catalog .ribbon { position: absolute; top: 12px; right: 12px; z-index: 1; }\n\n.ps-catalog .ah { display: flex; align-items: center; gap: 12px; padding: 16px 16px 0; }\n.ps-catalog .ah .ic {\n width: 46px; height: 46px; border-radius: 12px; display: grid; place-items: center; font-size: 20px; flex: 0 0 auto;\n background: color-mix(in srgb, var(--mj-brand-primary) 12%, var(--mj-bg-surface)); color: var(--mj-brand-primary);\n}\n.ps-catalog .ah .nm { font-size: 15.5px; font-weight: 650; color: var(--mj-text-primary); }\n.ps-catalog .ah .key { color: var(--mj-text-muted); }\n\n.ps-catalog .ab { padding: 14px 16px; display: flex; flex-direction: column; gap: 11px; flex: 1; }\n.ps-catalog .kv-title { font-size: 11px; text-transform: uppercase; letter-spacing: .04em; color: var(--mj-text-muted); font-weight: 700; }\n.ps-catalog .meta-row { display: flex; flex-wrap: wrap; gap: 6px; }\n\n.ps-catalog .af {\n display: flex; align-items: center; gap: 8px; padding: 12px 16px;\n border-top: 1px solid var(--mj-border-subtle); background: var(--mj-bg-surface-card);\n}\n\n/* Inline algorithm detail panel (toggled by the Details button) */\n.ps-catalog .adetail {\n padding: 14px 16px; border-top: 1px solid var(--mj-border-subtle);\n background: var(--mj-bg-surface-sunken); display: flex; flex-direction: column; gap: 8px;\n}\n.ps-catalog .adetail-desc { margin: 0; line-height: 1.45; color: var(--mj-text-secondary); }\n.ps-catalog .dkv { display: flex; justify-content: space-between; gap: 12px; font-size: 12.5px; }\n.ps-catalog .dk { color: var(--mj-text-muted); font-weight: 650; }\n.ps-catalog .dv { color: var(--mj-text-primary); text-align: right; }\n.ps-catalog .hp-list { display: flex; flex-direction: column; gap: 3px; margin-top: 4px; }\n.ps-catalog .hp { display: flex; justify-content: space-between; gap: 12px; font-size: 12px; }\n.ps-catalog .adetail-foot { display: flex; justify-content: flex-start; margin-top: 6px; }\n"], encapsulation: 2 });
|
|
447
447
|
}
|
|
448
448
|
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(PSCatalogComponent, [{
|
|
449
449
|
type: Component,
|
|
@@ -569,7 +569,7 @@ export class PSCatalogComponent {
|
|
|
569
569
|
</div>
|
|
570
570
|
}
|
|
571
571
|
</div>
|
|
572
|
-
`, styles: ["/* ============================================================================\n Predictive Studio \u2014 shared panel styles (design-token compliant)\n Maps the mockup design system onto MemberJunction semantic --mj-* tokens.\n Loaded by every PS panel component (styleUrls) under ViewEncapsulation.None,\n so the class names are global within the PS dashboard subtree.\n No hardcoded colors except the intentional categorical node/algorithm palette\n (declared once as --ps-* custom props below) which must stay distinct across\n light/dark to remain readable as diagram accents.\n ============================================================================ */\n\n/* Categorical accents for DAG node types + algorithm chips. These are\n data-visualization colors that must remain distinct regardless of theme\n (allowed exception #3 in the design-token rules). */\n.ps-panel {\n --ps-node-source: #6b7280;\n --ps-node-feature: #2050c8;\n --ps-node-embedding: #7b3ff0;\n --ps-node-target: #1f9d57;\n --ps-node-algorithm: #d98213;\n /* budget-gauge categorical accents (reuse the node palette so light/dark stay distinct) */\n --ps-gauge-cost: var(--ps-node-algorithm);\n --ps-gauge-iterations: var(--ps-node-feature);\n}\n\n.ps-panel {\n display: block;\n height: 100%;\n}\n\n/* ---- generic layout helpers ---- */\n.ps-row { display: flex; gap: 16px; }\n.ps-col { display: flex; flex-direction: column; gap: 16px; }\n.ps-wrap { flex-wrap: wrap; }\n.ps-spacer { flex: 1; }\n.ps-muted { color: var(--mj-text-muted); }\n.ps-small { font-size: 12.5px; }\n.ps-mono { font-family: ui-monospace, 'SF Mono', Menlo, Consolas, monospace; font-size: 12.5px; }\n.ps-section-title {\n font-size: 12px; text-transform: uppercase; letter-spacing: .06em;\n color: var(--mj-text-muted); margin: 4px 0 2px; font-weight: 700;\n}\n\n/* ---- cards ---- */\n.ps-card {\n background: var(--mj-bg-surface);\n border: 1px solid var(--mj-border-default);\n border-radius: 10px;\n box-shadow: var(--mj-shadow-sm);\n overflow: hidden;\n}\n.ps-card-head {\n display: flex; align-items: center; gap: 10px;\n padding: 14px 16px; border-bottom: 1px solid var(--mj-border-subtle);\n}\n.ps-card-head h3 { font-size: 14.5px; margin: 0; font-weight: 650; color: var(--mj-text-primary); }\n.ps-card-body { padding: 16px; }\n\n/* ---- KPI tiles ---- */\n.ps-kpi {\n background: var(--mj-bg-surface); border: 1px solid var(--mj-border-default);\n border-radius: 10px; padding: 16px 18px; box-shadow: var(--mj-shadow-sm);\n}\n.ps-kpi .label { font-size: 12px; color: var(--mj-text-muted); font-weight: 600; }\n.ps-kpi .val { font-size: 26px; font-weight: 720; letter-spacing: -.02em; margin-top: 2px; color: var(--mj-text-primary); }\n\n/* ---- badges ---- */\n.ps-badge {\n display: inline-flex; align-items: center; gap: 5px;\n padding: 2px 9px; border-radius: 999px; font-size: 11.5px; font-weight: 650; line-height: 1.7;\n}\n.ps-badge.gray { background: var(--mj-bg-surface-sunken); color: var(--mj-text-secondary); }\n.ps-badge.blue { background: var(--mj-status-info-bg); color: var(--mj-status-info-text); }\n.ps-badge.green { background: var(--mj-status-success-bg); color: var(--mj-status-success-text); }\n.ps-badge.amber { background: var(--mj-status-warning-bg); color: var(--mj-status-warning-text); }\n.ps-badge.red { background: var(--mj-status-error-bg); color: var(--mj-status-error-text); }\n.ps-dot { width: 7px; height: 7px; border-radius: 50%; display: inline-block; }\n\n/* recommendation-level chips (the ranking matrix payoff) */\n.ps-lvl {\n display: inline-flex; align-items: center; justify-content: center;\n min-width: 92px; padding: 4px 10px; border-radius: 7px;\n font-weight: 650; font-size: 11.5px; color: var(--mj-text-inverse);\n}\n.ps-lvl-Primary { background: var(--mj-status-success); }\n.ps-lvl-Strong { background: var(--mj-status-info); }\n.ps-lvl-Viable { background: var(--mj-status-warning); }\n.ps-lvl-Weak { background: var(--mj-text-disabled); }\n.ps-lvl-NotRecommended { background: var(--mj-bg-surface-sunken); color: var(--mj-text-muted); }\n\n/* ---- tags ---- */\n.ps-tag {\n display: inline-block; padding: 2px 8px; border-radius: 6px;\n background: var(--mj-bg-surface-sunken); color: var(--mj-text-secondary);\n font-size: 11.5px; font-weight: 600;\n}\n.ps-tag.muted { opacity: .6; }\n.ps-divider { height: 1px; background: var(--mj-border-subtle); margin: 8px 0; }\n\n/* ---- tables ---- */\n.ps-table { width: 100%; border-collapse: collapse; font-size: 13px; }\n.ps-table th {\n text-align: left; font-size: 11.5px; text-transform: uppercase; letter-spacing: .04em;\n color: var(--mj-text-muted); font-weight: 700; padding: 9px 12px;\n border-bottom: 1px solid var(--mj-border-default);\n}\n.ps-table td { padding: 11px 12px; border-bottom: 1px solid var(--mj-border-subtle); color: var(--mj-text-primary); }\n.ps-table tr:hover td { background: var(--mj-bg-surface-hover); }\n.ps-table .num { text-align: right; font-variant-numeric: tabular-nums; }\n\n.ps-rank {\n width: 22px; height: 22px; border-radius: 6px; background: var(--mj-bg-surface-sunken);\n display: inline-grid; place-items: center; font-weight: 700; font-size: 11.5px; color: var(--mj-text-secondary);\n}\n.ps-rank.gold { background: var(--mj-status-warning-bg); color: var(--mj-status-warning-text); }\n\n/* ---- progress bars ---- */\n.ps-bar { height: 8px; border-radius: 999px; background: var(--mj-bg-surface-sunken); overflow: hidden; }\n.ps-bar > span { display: block; height: 100%; border-radius: 999px; background: var(--mj-brand-primary); }\n.ps-bar.warn > span { background: var(--mj-status-warning); }\n.ps-bar.run > span { background: var(--mj-brand-primary); }\n\n.ps-fbar { display: grid; grid-template-columns: 150px 1fr 48px; align-items: center; gap: 10px; padding: 5px 0; }\n.ps-fbar .track { height: 14px; border-radius: 5px; background: var(--mj-bg-surface-sunken); overflow: hidden; }\n.ps-fbar .track > span { display: block; height: 100%; background: var(--mj-brand-primary); }\n.ps-fbar .track > span.warn { background: var(--mj-status-warning); }\n.ps-fbar .name { color: var(--mj-text-secondary); }\n\n/* gauge */\n.ps-gauge {\n position: relative; width: 64px; height: 64px; border-radius: 50%; flex: 0 0 auto;\n background: conic-gradient(var(--ps-gauge-color, var(--mj-brand-primary)) calc(var(--ps-p, 40) * 1%), var(--mj-bg-surface-sunken) 0);\n display: grid; place-items: center;\n}\n.ps-gauge::after { content: ''; position: absolute; inset: 9px; background: var(--mj-bg-surface); border-radius: 50%; }\n.ps-gauge .g-val { position: relative; font-weight: 720; font-size: 14px; color: var(--mj-text-primary); }\n\n/* warning / info callouts */\n.ps-callout { display: flex; gap: 11px; padding: 12px 14px; border-radius: 10px; border: 1px solid; }\n.ps-callout.warn { background: var(--mj-status-warning-bg); border-color: var(--mj-status-warning-border); color: var(--mj-status-warning-text); }\n.ps-callout.info { background: var(--mj-status-info-bg); border-color: var(--mj-status-info-border); color: var(--mj-status-info-text); }\n.ps-callout.success { background: var(--mj-status-success-bg); border-color: var(--mj-status-success-border); color: var(--mj-status-success-text); }\n.ps-callout i { font-size: 16px; margin-top: 1px; }\n\n/* lifecycle stepper */\n.ps-stepper { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }\n.ps-step { display: flex; align-items: center; gap: 6px; font-size: 12px; font-weight: 600; color: var(--mj-text-muted); }\n.ps-step .pip {\n width: 22px; height: 22px; border-radius: 50%; border: 2px solid var(--mj-border-strong);\n display: grid; place-items: center; font-size: 11px;\n}\n.ps-step.done { color: var(--mj-status-success-text); }\n.ps-step.done .pip { background: var(--mj-status-success); border-color: var(--mj-status-success); color: var(--mj-text-inverse); }\n.ps-step.curr { color: var(--mj-brand-primary); }\n.ps-step.curr .pip { border-color: var(--mj-brand-primary); color: var(--mj-brand-primary); }\n.ps-step .ln { width: 26px; height: 2px; background: var(--mj-border-strong); }\n\n/* ---- DAG flow canvas ---- */\n.ps-flow {\n position: relative;\n background:\n linear-gradient(var(--mj-border-subtle) 1px, transparent 1px) 0 0 / 22px 22px,\n linear-gradient(90deg, var(--mj-border-subtle) 1px, transparent 1px) 0 0 / 22px 22px,\n var(--mj-bg-surface-card);\n border: 1px solid var(--mj-border-default); border-radius: 10px;\n min-height: 620px; overflow: hidden;\n}\n.ps-node {\n position: absolute; width: 190px; background: var(--mj-bg-surface);\n border: 1px solid var(--mj-border-strong); border-left-width: 3px; border-radius: 10px;\n box-shadow: var(--mj-shadow-sm); cursor: pointer;\n}\n.ps-node .nh {\n display: flex; align-items: center; gap: 8px; padding: 9px 11px;\n border-bottom: 1px solid var(--mj-border-subtle); font-weight: 650; font-size: 12.5px; color: var(--mj-text-primary);\n}\n.ps-node .nh i.tile {\n width: 24px; height: 24px; border-radius: 6px; display: grid; place-items: center;\n color: var(--mj-text-inverse); font-size: 12px; flex: 0 0 auto;\n}\n.ps-node .nh .ps-tag { margin-left: auto; }\n.ps-node .nb { padding: 9px 11px; font-size: 12px; color: var(--mj-text-secondary); }\n.ps-node .nb-row { display: flex; justify-content: space-between; gap: 8px; padding: 2px 0; }\n.ps-node .nb-row .k { color: var(--mj-text-muted); }\n.ps-node.src { border-left-color: var(--ps-node-source); }\n.ps-node.src .nh i.tile { background: var(--ps-node-source); }\n.ps-node.feat { border-left-color: var(--ps-node-feature); }\n.ps-node.feat .nh i.tile { background: var(--ps-node-feature); }\n.ps-node.emb { border-left-color: var(--ps-node-embedding); }\n.ps-node.emb .nh i.tile { background: var(--ps-node-embedding); }\n.ps-node.target { border-left-color: var(--ps-node-target); }\n.ps-node.target .nh i.tile { background: var(--ps-node-target); }\n.ps-node.algo { border-left-color: var(--ps-node-algorithm); }\n.ps-node.algo .nh i.tile { background: var(--ps-node-algorithm); }\n.ps-node.output { border-left-color: var(--mj-status-success); }\n.ps-node.output .nh i.tile { background: var(--mj-status-success); }\n.ps-node.selected {\n border-color: var(--mj-brand-primary);\n box-shadow: 0 0 0 3px color-mix(in srgb, var(--mj-brand-primary) 18%, transparent);\n}\n.ps-node .port {\n position: absolute; width: 11px; height: 11px; border-radius: 50%;\n background: var(--mj-bg-surface); border: 2px solid var(--mj-brand-primary);\n top: 50%; transform: translateY(-50%);\n}\n.ps-node .port.in { left: -6px; }\n.ps-node .port.out { right: -6px; }\nsvg.ps-edges { position: absolute; inset: 0; width: 100%; height: 100%; pointer-events: none; }\nsvg.ps-edges path { stroke: color-mix(in srgb, var(--mj-brand-primary) 45%, transparent); stroke-width: 2; fill: none; }\n\n/* palette chips */\n.ps-pchip {\n display: inline-flex; align-items: center; gap: 7px; padding: 6px 11px; border-radius: 8px;\n border: 1px dashed var(--mj-border-strong); background: var(--mj-bg-surface);\n font-size: 12.5px; font-weight: 600; color: var(--mj-text-secondary); cursor: grab;\n}\n.ps-pchip i { font-size: 12px; color: var(--mj-text-inverse); width: 20px; height: 20px; border-radius: 5px; display: grid; place-items: center; }\n.ps-pchip.s i { background: var(--ps-node-source); }\n.ps-pchip.f i { background: var(--ps-node-feature); }\n.ps-pchip.e i { background: var(--ps-node-embedding); }\n.ps-pchip.t i { background: var(--ps-node-target); }\n.ps-pchip.a i { background: var(--ps-node-algorithm); }\n\n/* segmented control */\n.ps-seg { display: inline-flex; border: 1px solid var(--mj-border-default); border-radius: 8px; overflow: hidden; }\n.ps-seg button {\n border: none; background: var(--mj-bg-surface); color: var(--mj-text-secondary);\n padding: 6px 12px; font-size: 12.5px; font-weight: 600; cursor: pointer;\n border-right: 1px solid var(--mj-border-default);\n}\n.ps-seg button:last-child { border-right: none; }\n.ps-seg button.on { background: var(--mj-brand-primary); color: var(--mj-text-inverse); }\n\n/* field control */\n.ps-field { margin-bottom: 14px; }\n.ps-field > label {\n display: block; font-size: 11px; text-transform: uppercase; letter-spacing: .04em;\n color: var(--mj-text-muted); font-weight: 700; margin-bottom: 6px;\n}\n.ps-input {\n width: 100%; padding: 8px 10px; border: 1px solid var(--mj-border-default);\n border-radius: 6px; font-size: 13px; background: var(--mj-bg-surface); color: var(--mj-text-primary);\n box-sizing: border-box;\n}\n.ps-input:focus { outline: none; border-color: var(--mj-brand-primary); box-shadow: 0 0 0 3px color-mix(in srgb, var(--mj-brand-primary) 12%, transparent); }\n\n/* loading container */\n.ps-loading { display: flex; align-items: center; justify-content: center; padding: 60px 0; }\n\n/* ---- empty / prompt states ---- */\n.ps-empty {\n display: flex; flex-direction: column; align-items: center; justify-content: center;\n text-align: center; gap: 10px; padding: 56px 24px; color: var(--mj-text-muted);\n}\n.ps-empty .ps-empty-ico {\n width: 60px; height: 60px; border-radius: 16px; display: grid; place-items: center;\n background: var(--mj-bg-surface-sunken); color: var(--mj-text-disabled); font-size: 24px;\n}\n.ps-empty h3 { margin: 4px 0 0; font-size: 16px; font-weight: 650; color: var(--mj-text-secondary); }\n.ps-empty p { margin: 0; max-width: 420px; font-size: 13px; line-height: 1.5; }\n\n/* ---- confirm modal ---- */\n.ps-modal-backdrop {\n position: fixed; inset: 0; z-index: 1000;\n background: var(--mj-bg-overlay);\n display: flex; align-items: center; justify-content: center; padding: 24px;\n}\n.ps-modal {\n width: 100%; max-width: 480px; background: var(--mj-bg-surface);\n border: 1px solid var(--mj-border-default); border-radius: 14px;\n box-shadow: var(--mj-shadow-lg, var(--mj-shadow-sm)); overflow: hidden;\n}\n.ps-modal-head {\n display: flex; align-items: center; gap: 12px; padding: 18px 20px;\n border-bottom: 1px solid var(--mj-border-subtle);\n}\n.ps-modal-head .ico {\n width: 38px; height: 38px; border-radius: 10px; display: grid; place-items: center;\n font-size: 16px; flex: 0 0 auto;\n background: var(--mj-status-info-bg); color: var(--mj-status-info-text);\n}\n.ps-modal-head.warn .ico { background: var(--mj-status-warning-bg); color: var(--mj-status-warning-text); }\n.ps-modal-head.danger .ico { background: var(--mj-status-error-bg); color: var(--mj-status-error-text); }\n.ps-modal-head h3 { margin: 0; font-size: 15.5px; font-weight: 650; color: var(--mj-text-primary); }\n.ps-modal-body { padding: 18px 20px; font-size: 13.5px; line-height: 1.55; color: var(--mj-text-secondary); }\n.ps-modal-body .ps-field { margin-top: 14px; margin-bottom: 0; }\n.ps-modal-foot {\n display: flex; align-items: center; gap: 10px; padding: 14px 20px;\n border-top: 1px solid var(--mj-border-subtle); background: var(--mj-bg-surface-card);\n}\n.ps-modal-foot .ps-spacer { flex: 1; }\n", ".ps-catalog { display: flex; flex-direction: column; gap: 16px; }\n\n.ps-catalog .guide-card .guide-head { display: flex; gap: 12px; align-items: flex-start; }\n.ps-catalog .guide-head i { font-size: 18px; margin-top: 2px; }\n.ps-catalog .guide-head h3 { font-size: 14.5px; color: var(--mj-text-primary); margin-bottom: 4px; }\n.ps-catalog .guide-head .ps-muted { max-width: 560px; }\n\n.ps-catalog .chips-row { display: flex; align-items: center; gap: 12px; margin-top: 14px; flex-wrap: wrap; }\n.ps-catalog .chips { display: flex; gap: 8px; flex-wrap: wrap; }\n.ps-catalog .chip {\n display: inline-flex; align-items: center; gap: 7px; padding: 7px 13px; border-radius: 999px;\n border: 1px solid var(--mj-border-default); background: var(--mj-bg-surface);\n color: var(--mj-text-secondary); font-size: 12.5px; font-weight: 600; cursor: pointer; transition: all .12s;\n}\n.ps-catalog .chip:hover { background: var(--mj-bg-surface-hover); }\n.ps-catalog .chip.on { background: var(--mj-brand-primary); border-color: var(--mj-brand-primary); color: var(--mj-text-inverse); }\n.ps-catalog .chip i { font-size: 12px; }\n.ps-catalog .clearx {\n display: inline-flex; align-items: center; gap: 6px; background: transparent; border: none;\n color: var(--mj-text-muted); font-size: 12.5px; font-weight: 600; cursor: pointer;\n}\n.ps-catalog .clearx:hover { color: var(--mj-status-error-text); }\n.ps-catalog .reco-banner { margin-top: 14px; }\n\n/* gallery */\n.ps-catalog .gallery { display: grid; grid-template-columns: repeat(auto-fill, minmax(340px, 1fr)); gap: 16px; }\n.ps-catalog .acard {\n position: relative; display: flex; flex-direction: column;\n background: var(--mj-bg-surface); border: 1px solid var(--mj-border-default);\n border-radius: 10px; box-shadow: var(--mj-shadow-sm); overflow: hidden; transition: opacity .15s, box-shadow .15s, border-color .15s;\n}\n.ps-catalog .acard.hit { border-color: var(--mj-brand-primary); box-shadow: 0 0 0 3px color-mix(in srgb, var(--mj-brand-primary) 14%, transparent); }\n.ps-catalog .acard.dim { opacity: .46; }\n.ps-catalog .ribbon { position: absolute; top: 12px; right: 12px; z-index: 1; }\n\n.ps-catalog .ah { display: flex; align-items: center; gap: 12px; padding: 16px 16px 0; }\n.ps-catalog .ah .ic {\n width: 46px; height: 46px; border-radius: 12px; display: grid; place-items: center; font-size: 20px; flex: 0 0 auto;\n background: color-mix(in srgb, var(--mj-brand-primary) 12%, var(--mj-bg-surface)); color: var(--mj-brand-primary);\n}\n.ps-catalog .ah .nm { font-size: 15.5px; font-weight: 650; color: var(--mj-text-primary); }\n.ps-catalog .ah .key { color: var(--mj-text-muted); }\n\n.ps-catalog .ab { padding: 14px 16px; display: flex; flex-direction: column; gap: 11px; flex: 1; }\n.ps-catalog .kv-title { font-size: 11px; text-transform: uppercase; letter-spacing: .04em; color: var(--mj-text-muted); font-weight: 700; }\n.ps-catalog .meta-row { display: flex; flex-wrap: wrap; gap: 6px; }\n\n.ps-catalog .af {\n display: flex; align-items: center; gap: 8px; padding: 12px 16px;\n border-top: 1px solid var(--mj-border-subtle); background: var(--mj-bg-surface-card);\n}\n\n/* Inline algorithm detail panel (toggled by the Details button) */\n.ps-catalog .adetail {\n padding: 14px 16px; border-top: 1px solid var(--mj-border-subtle);\n background: var(--mj-bg-surface-sunken); display: flex; flex-direction: column; gap: 8px;\n}\n.ps-catalog .adetail-desc { margin: 0; line-height: 1.45; color: var(--mj-text-secondary); }\n.ps-catalog .dkv { display: flex; justify-content: space-between; gap: 12px; font-size: 12.5px; }\n.ps-catalog .dk { color: var(--mj-text-muted); font-weight: 650; }\n.ps-catalog .dv { color: var(--mj-text-primary); text-align: right; }\n.ps-catalog .hp-list { display: flex; flex-direction: column; gap: 3px; margin-top: 4px; }\n.ps-catalog .hp { display: flex; justify-content: space-between; gap: 12px; font-size: 12px; }\n.ps-catalog .adetail-foot { display: flex; justify-content: flex-start; margin-top: 6px; }\n"] }]
|
|
572
|
+
`, styles: ["/* ============================================================================\n Predictive Studio \u2014 shared panel styles (design-token compliant)\n Maps the mockup design system onto MemberJunction semantic --mj-* tokens.\n Loaded by every PS panel component (styleUrls) under ViewEncapsulation.None,\n so the class names are global within the PS dashboard subtree.\n No hardcoded colors except the intentional categorical node/algorithm palette\n (declared once as --ps-* custom props below) which must stay distinct across\n light/dark to remain readable as diagram accents.\n ============================================================================ */\n\n/* Categorical accents for DAG node types + algorithm chips. These are\n data-visualization colors that must remain distinct regardless of theme\n (allowed exception #3 in the design-token rules). */\n.ps-panel {\n --ps-node-source: #6b7280;\n --ps-node-feature: #2050c8;\n --ps-node-embedding: #7b3ff0;\n --ps-node-target: #1f9d57;\n --ps-node-algorithm: #d98213;\n /* budget-gauge categorical accents (reuse the node palette so light/dark stay distinct) */\n --ps-gauge-cost: var(--ps-node-algorithm);\n --ps-gauge-iterations: var(--ps-node-feature);\n}\n\n.ps-panel {\n display: block;\n height: 100%;\n}\n\n/* ---- generic layout helpers ---- */\n.ps-row { display: flex; gap: 16px; }\n.ps-col { display: flex; flex-direction: column; gap: 16px; }\n.ps-wrap { flex-wrap: wrap; }\n.ps-spacer { flex: 1; }\n.ps-muted { color: var(--mj-text-muted); }\n.ps-small { font-size: 12.5px; }\n.ps-mono { font-family: ui-monospace, 'SF Mono', Menlo, Consolas, monospace; font-size: 12.5px; }\n.ps-section-title {\n font-size: 12px; text-transform: uppercase; letter-spacing: .06em;\n color: var(--mj-text-muted); margin: 4px 0 2px; font-weight: 700;\n}\n\n/* ---- cards ---- */\n.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-card-head {\n display: flex; align-items: center; gap: 10px;\n padding: 14px 16px; border-bottom: 1px solid var(--mj-border-subtle);\n}\n.ps-card-head h3 { font-size: 14.5px; margin: 0; font-weight: 650; color: var(--mj-text-primary); }\n.ps-card-body { padding: 16px; }\n\n/* ---- KPI tiles ---- */\n.ps-kpi {\n background: var(--mj-bg-surface); border: 1px solid var(--mj-border-default);\n border-radius: 8px; padding: 16px 18px;\n}\n.ps-kpi .label { font-size: 12px; color: var(--mj-text-muted); font-weight: 600; }\n.ps-kpi .val { font-size: 26px; font-weight: 720; letter-spacing: -.02em; margin-top: 2px; color: var(--mj-text-primary); }\n\n/* ---- badges ---- */\n.ps-badge {\n display: inline-flex; align-items: center; gap: 5px;\n padding: 2px 9px; border-radius: 999px; font-size: 11.5px; font-weight: 650; line-height: 1.7;\n}\n.ps-badge.gray { background: var(--mj-bg-surface-sunken); color: var(--mj-text-secondary); }\n.ps-badge.blue { background: var(--mj-status-info-bg); color: var(--mj-status-info-text); }\n.ps-badge.green { background: var(--mj-status-success-bg); color: var(--mj-status-success-text); }\n.ps-badge.amber { background: var(--mj-status-warning-bg); color: var(--mj-status-warning-text); }\n.ps-badge.red { background: var(--mj-status-error-bg); color: var(--mj-status-error-text); }\n.ps-dot { width: 7px; height: 7px; border-radius: 50%; display: inline-block; }\n\n/* recommendation-level chips (the ranking matrix payoff) */\n.ps-lvl {\n display: inline-flex; align-items: center; justify-content: center;\n min-width: 92px; padding: 4px 10px; border-radius: 7px;\n font-weight: 650; font-size: 11.5px; color: var(--mj-text-inverse);\n}\n.ps-lvl-Primary { background: var(--mj-status-success); }\n.ps-lvl-Strong { background: var(--mj-status-info); }\n.ps-lvl-Viable { background: var(--mj-status-warning); }\n.ps-lvl-Weak { background: var(--mj-text-disabled); }\n.ps-lvl-NotRecommended { background: var(--mj-bg-surface-sunken); color: var(--mj-text-muted); }\n\n/* ---- tags ---- */\n.ps-tag {\n display: inline-block; padding: 2px 8px; border-radius: 6px;\n background: var(--mj-bg-surface-sunken); color: var(--mj-text-secondary);\n font-size: 11.5px; font-weight: 600;\n}\n.ps-tag.muted { opacity: .6; }\n.ps-divider { height: 1px; background: var(--mj-border-subtle); margin: 8px 0; }\n\n/* ---- tables ---- */\n.ps-table { width: 100%; border-collapse: collapse; font-size: 13px; }\n.ps-table th {\n text-align: left; font-size: 11.5px; text-transform: uppercase; letter-spacing: .04em;\n color: var(--mj-text-muted); font-weight: 700; padding: 9px 12px;\n border-bottom: 1px solid var(--mj-border-default);\n}\n.ps-table td { padding: 11px 12px; border-bottom: 1px solid var(--mj-border-subtle); color: var(--mj-text-primary); }\n.ps-table tr:hover td { background: var(--mj-bg-surface-hover); }\n.ps-table .num { text-align: right; font-variant-numeric: tabular-nums; }\n\n.ps-rank {\n width: 22px; height: 22px; border-radius: 6px; background: var(--mj-bg-surface-sunken);\n display: inline-grid; place-items: center; font-weight: 700; font-size: 11.5px; color: var(--mj-text-secondary);\n}\n.ps-rank.gold { background: var(--mj-status-warning-bg); color: var(--mj-status-warning-text); }\n\n/* ---- progress bars ---- */\n.ps-bar { height: 8px; border-radius: 999px; background: var(--mj-bg-surface-sunken); overflow: hidden; }\n.ps-bar > span { display: block; height: 100%; border-radius: 999px; background: var(--mj-brand-primary); }\n.ps-bar.warn > span { background: var(--mj-status-warning); }\n.ps-bar.run > span { background: var(--mj-brand-primary); }\n\n.ps-fbar { display: grid; grid-template-columns: minmax(150px, 1.3fr) 2fr 56px; align-items: center; gap: 14px; padding: 8px 0; }\n.ps-fbar + .ps-fbar { border-top: 1px solid var(--mj-border-subtle); }\n.ps-fbar .track { height: 10px; border-radius: var(--mj-radius-full); background: var(--mj-bg-surface-sunken); overflow: hidden; }\n.ps-fbar .track > span { display: block; height: 100%; border-radius: var(--mj-radius-full); background: var(--mj-brand-primary); }\n.ps-fbar .track > span.warn { background: var(--mj-status-warning); }\n.ps-fbar .name { color: var(--mj-text-primary); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n.ps-fbar > .ps-mono { text-align: right; color: var(--mj-text-secondary); font-variant-numeric: tabular-nums; }\n\n/* gauge */\n.ps-gauge {\n position: relative; width: 64px; height: 64px; border-radius: 50%; flex: 0 0 auto;\n background: conic-gradient(var(--ps-gauge-color, var(--mj-brand-primary)) calc(var(--ps-p, 40) * 1%), var(--mj-bg-surface-sunken) 0);\n display: grid; place-items: center;\n}\n.ps-gauge::after { content: ''; position: absolute; inset: 9px; background: var(--mj-bg-surface); border-radius: 50%; }\n.ps-gauge .g-val { position: relative; font-weight: 720; font-size: 14px; color: var(--mj-text-primary); }\n\n/* warning / info callouts */\n.ps-callout { display: flex; gap: 11px; padding: 12px 14px; border-radius: 10px; border: 1px solid; }\n.ps-callout.warn { background: var(--mj-status-warning-bg); border-color: var(--mj-status-warning-border); color: var(--mj-status-warning-text); }\n.ps-callout.info { background: var(--mj-status-info-bg); border-color: var(--mj-status-info-border); color: var(--mj-status-info-text); }\n.ps-callout.success { background: var(--mj-status-success-bg); border-color: var(--mj-status-success-border); color: var(--mj-status-success-text); }\n.ps-callout i { font-size: 16px; margin-top: 1px; }\n\n/* lifecycle stepper */\n.ps-stepper { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }\n.ps-step { display: flex; align-items: center; gap: 6px; font-size: 12px; font-weight: 600; color: var(--mj-text-muted); }\n.ps-step .pip {\n width: 22px; height: 22px; border-radius: 50%; border: 2px solid var(--mj-border-strong);\n display: grid; place-items: center; font-size: 11px;\n}\n.ps-step.done { color: var(--mj-status-success-text); }\n.ps-step.done .pip { background: var(--mj-status-success); border-color: var(--mj-status-success); color: var(--mj-text-inverse); }\n.ps-step.curr { color: var(--mj-brand-primary); }\n.ps-step.curr .pip { border-color: var(--mj-brand-primary); color: var(--mj-brand-primary); }\n.ps-step .ln { width: 26px; height: 2px; background: var(--mj-border-strong); }\n\n/* ---- DAG flow canvas ---- */\n.ps-flow {\n position: relative;\n background:\n linear-gradient(var(--mj-border-subtle) 1px, transparent 1px) 0 0 / 22px 22px,\n linear-gradient(90deg, var(--mj-border-subtle) 1px, transparent 1px) 0 0 / 22px 22px,\n var(--mj-bg-surface-card);\n border: 1px solid var(--mj-border-default); border-radius: 10px;\n min-height: 620px; overflow: hidden;\n}\n.ps-node {\n position: absolute; width: 190px; background: var(--mj-bg-surface);\n border: 1px solid var(--mj-border-strong); border-left-width: 3px; border-radius: 8px;\n cursor: pointer;\n}\n.ps-node .nh {\n display: flex; align-items: center; gap: 8px; padding: 9px 11px;\n border-bottom: 1px solid var(--mj-border-subtle); font-weight: 650; font-size: 12.5px; color: var(--mj-text-primary);\n}\n.ps-node .nh i.tile {\n width: 24px; height: 24px; border-radius: 6px; display: grid; place-items: center;\n color: var(--mj-text-inverse); font-size: 12px; flex: 0 0 auto;\n}\n.ps-node .nh .ps-tag { margin-left: auto; }\n.ps-node .nb { padding: 9px 11px; font-size: 12px; color: var(--mj-text-secondary); }\n.ps-node .nb-row { display: flex; justify-content: space-between; gap: 8px; padding: 2px 0; }\n.ps-node .nb-row .k { color: var(--mj-text-muted); }\n.ps-node.src { border-left-color: var(--ps-node-source); }\n.ps-node.src .nh i.tile { background: var(--ps-node-source); }\n.ps-node.feat { border-left-color: var(--ps-node-feature); }\n.ps-node.feat .nh i.tile { background: var(--ps-node-feature); }\n.ps-node.emb { border-left-color: var(--ps-node-embedding); }\n.ps-node.emb .nh i.tile { background: var(--ps-node-embedding); }\n.ps-node.target { border-left-color: var(--ps-node-target); }\n.ps-node.target .nh i.tile { background: var(--ps-node-target); }\n.ps-node.algo { border-left-color: var(--ps-node-algorithm); }\n.ps-node.algo .nh i.tile { background: var(--ps-node-algorithm); }\n.ps-node.output { border-left-color: var(--mj-status-success); }\n.ps-node.output .nh i.tile { background: var(--mj-status-success); }\n.ps-node.selected {\n border-color: var(--mj-brand-primary);\n box-shadow: 0 0 0 3px color-mix(in srgb, var(--mj-brand-primary) 18%, transparent);\n}\n.ps-node .port {\n position: absolute; width: 11px; height: 11px; border-radius: 50%;\n background: var(--mj-bg-surface); border: 2px solid var(--mj-brand-primary);\n top: 50%; transform: translateY(-50%);\n}\n.ps-node .port.in { left: -6px; }\n.ps-node .port.out { right: -6px; }\nsvg.ps-edges { position: absolute; inset: 0; width: 100%; height: 100%; pointer-events: none; }\nsvg.ps-edges path { stroke: color-mix(in srgb, var(--mj-brand-primary) 45%, transparent); stroke-width: 2; fill: none; }\n\n/* palette chips */\n.ps-pchip {\n display: inline-flex; align-items: center; gap: 7px; padding: 6px 11px; border-radius: 8px;\n border: 1px dashed var(--mj-border-strong); background: var(--mj-bg-surface);\n font-size: 12.5px; font-weight: 600; color: var(--mj-text-secondary); cursor: grab;\n}\n.ps-pchip i { font-size: 12px; color: var(--mj-text-inverse); width: 20px; height: 20px; border-radius: 5px; display: grid; place-items: center; }\n.ps-pchip.s i { background: var(--ps-node-source); }\n.ps-pchip.f i { background: var(--ps-node-feature); }\n.ps-pchip.e i { background: var(--ps-node-embedding); }\n.ps-pchip.t i { background: var(--ps-node-target); }\n.ps-pchip.a i { background: var(--ps-node-algorithm); }\n\n/* segmented control */\n.ps-seg { display: inline-flex; border: 1px solid var(--mj-border-default); border-radius: 8px; overflow: hidden; }\n.ps-seg button {\n border: none; background: var(--mj-bg-surface); color: var(--mj-text-secondary);\n padding: 6px 12px; font-size: 12.5px; font-weight: 600; cursor: pointer;\n border-right: 1px solid var(--mj-border-default);\n}\n.ps-seg button:last-child { border-right: none; }\n.ps-seg button.on { background: var(--mj-brand-primary); color: var(--mj-text-inverse); }\n\n/* field control */\n.ps-field { margin-bottom: 14px; }\n.ps-field > label {\n display: block; font-size: 11px; text-transform: uppercase; letter-spacing: .04em;\n color: var(--mj-text-muted); font-weight: 700; margin-bottom: 6px;\n}\n.ps-input {\n width: 100%; padding: 8px 10px; border: 1px solid var(--mj-border-default);\n border-radius: 6px; font-size: 13px; background: var(--mj-bg-surface); color: var(--mj-text-primary);\n box-sizing: border-box;\n}\n.ps-input:focus { outline: none; border-color: var(--mj-brand-primary); box-shadow: 0 0 0 3px color-mix(in srgb, var(--mj-brand-primary) 12%, transparent); }\n\n/* loading container */\n.ps-loading { display: flex; align-items: center; justify-content: center; padding: 60px 0; }\n\n/* ---- empty / prompt states ---- */\n.ps-empty {\n display: flex; flex-direction: column; align-items: center; justify-content: center;\n text-align: center; gap: 10px; padding: 56px 24px; color: var(--mj-text-muted);\n}\n.ps-empty .ps-empty-ico {\n width: 60px; height: 60px; border-radius: 16px; display: grid; place-items: center;\n background: var(--mj-bg-surface-sunken); color: var(--mj-text-disabled); font-size: 24px;\n}\n.ps-empty h3 { margin: 4px 0 0; font-size: 16px; font-weight: 650; color: var(--mj-text-secondary); }\n.ps-empty p { margin: 0; max-width: 420px; font-size: 13px; line-height: 1.5; }\n\n/* ---- confirm modal ---- */\n.ps-modal-backdrop {\n position: fixed; inset: 0; z-index: 1000;\n background: var(--mj-bg-overlay);\n display: flex; align-items: center; justify-content: center; padding: 24px;\n}\n.ps-modal {\n width: 100%; max-width: 480px; background: var(--mj-bg-surface);\n border: 1px solid var(--mj-border-default); border-radius: 14px;\n box-shadow: var(--mj-shadow-lg, var(--mj-shadow-sm)); overflow: hidden;\n}\n.ps-modal-head {\n display: flex; align-items: center; gap: 12px; padding: 18px 20px;\n border-bottom: 1px solid var(--mj-border-subtle);\n}\n.ps-modal-head .ico {\n width: 38px; height: 38px; border-radius: 10px; display: grid; place-items: center;\n font-size: 16px; flex: 0 0 auto;\n background: var(--mj-status-info-bg); color: var(--mj-status-info-text);\n}\n.ps-modal-head.warn .ico { background: var(--mj-status-warning-bg); color: var(--mj-status-warning-text); }\n.ps-modal-head.danger .ico { background: var(--mj-status-error-bg); color: var(--mj-status-error-text); }\n.ps-modal-head h3 { margin: 0; font-size: 15.5px; font-weight: 650; color: var(--mj-text-primary); }\n.ps-modal-body { padding: 18px 20px; font-size: 13.5px; line-height: 1.55; color: var(--mj-text-secondary); }\n.ps-modal-body .ps-field { margin-top: 14px; margin-bottom: 0; }\n.ps-modal-foot {\n display: flex; align-items: center; gap: 10px; padding: 14px 20px;\n border-top: 1px solid var(--mj-border-subtle); background: var(--mj-bg-surface-card);\n}\n.ps-modal-foot .ps-spacer { flex: 1; }\n", ".ps-catalog { display: flex; flex-direction: column; gap: 16px; }\n\n.ps-catalog .guide-card .guide-head { display: flex; gap: 12px; align-items: flex-start; }\n.ps-catalog .guide-head i { font-size: 18px; margin-top: 2px; }\n.ps-catalog .guide-head h3 { font-size: 14.5px; color: var(--mj-text-primary); margin-bottom: 4px; }\n.ps-catalog .guide-head .ps-muted { max-width: 560px; }\n\n.ps-catalog .chips-row { display: flex; align-items: center; gap: 12px; margin-top: 14px; flex-wrap: wrap; }\n.ps-catalog .chips { display: flex; gap: 8px; flex-wrap: wrap; }\n.ps-catalog .chip {\n display: inline-flex; align-items: center; gap: 7px; padding: 7px 13px; border-radius: 999px;\n border: 1px solid var(--mj-border-default); background: var(--mj-bg-surface);\n color: var(--mj-text-secondary); font-size: 12.5px; font-weight: 600; cursor: pointer; transition: all .12s;\n}\n.ps-catalog .chip:hover { background: var(--mj-bg-surface-hover); }\n.ps-catalog .chip.on { background: var(--mj-brand-primary); border-color: var(--mj-brand-primary); color: var(--mj-text-inverse); }\n.ps-catalog .chip i { font-size: 12px; }\n.ps-catalog .clearx {\n display: inline-flex; align-items: center; gap: 6px; background: transparent; border: none;\n color: var(--mj-text-muted); font-size: 12.5px; font-weight: 600; cursor: pointer;\n}\n.ps-catalog .clearx:hover { color: var(--mj-status-error-text); }\n.ps-catalog .reco-banner { margin-top: 14px; }\n\n/* gallery */\n.ps-catalog .gallery { display: grid; grid-template-columns: repeat(auto-fill, minmax(340px, 1fr)); gap: 16px; }\n.ps-catalog .acard {\n position: relative; display: flex; flex-direction: column;\n background: var(--mj-bg-surface); border: 1px solid var(--mj-border-default);\n border-radius: 8px; overflow: hidden; transition: opacity .15s, border-color .12s, background .12s;\n}\n.ps-catalog .acard:hover { border-color: var(--mj-brand-primary); background: color-mix(in srgb, var(--mj-brand-primary) 4%, var(--mj-bg-surface)); }\n.ps-catalog .acard.hit { border-color: var(--mj-brand-primary); box-shadow: 0 0 0 3px color-mix(in srgb, var(--mj-brand-primary) 14%, transparent); }\n.ps-catalog .acard.dim { opacity: .46; }\n.ps-catalog .ribbon { position: absolute; top: 12px; right: 12px; z-index: 1; }\n\n.ps-catalog .ah { display: flex; align-items: center; gap: 12px; padding: 16px 16px 0; }\n.ps-catalog .ah .ic {\n width: 46px; height: 46px; border-radius: 12px; display: grid; place-items: center; font-size: 20px; flex: 0 0 auto;\n background: color-mix(in srgb, var(--mj-brand-primary) 12%, var(--mj-bg-surface)); color: var(--mj-brand-primary);\n}\n.ps-catalog .ah .nm { font-size: 15.5px; font-weight: 650; color: var(--mj-text-primary); }\n.ps-catalog .ah .key { color: var(--mj-text-muted); }\n\n.ps-catalog .ab { padding: 14px 16px; display: flex; flex-direction: column; gap: 11px; flex: 1; }\n.ps-catalog .kv-title { font-size: 11px; text-transform: uppercase; letter-spacing: .04em; color: var(--mj-text-muted); font-weight: 700; }\n.ps-catalog .meta-row { display: flex; flex-wrap: wrap; gap: 6px; }\n\n.ps-catalog .af {\n display: flex; align-items: center; gap: 8px; padding: 12px 16px;\n border-top: 1px solid var(--mj-border-subtle); background: var(--mj-bg-surface-card);\n}\n\n/* Inline algorithm detail panel (toggled by the Details button) */\n.ps-catalog .adetail {\n padding: 14px 16px; border-top: 1px solid var(--mj-border-subtle);\n background: var(--mj-bg-surface-sunken); display: flex; flex-direction: column; gap: 8px;\n}\n.ps-catalog .adetail-desc { margin: 0; line-height: 1.45; color: var(--mj-text-secondary); }\n.ps-catalog .dkv { display: flex; justify-content: space-between; gap: 12px; font-size: 12.5px; }\n.ps-catalog .dk { color: var(--mj-text-muted); font-weight: 650; }\n.ps-catalog .dv { color: var(--mj-text-primary); text-align: right; }\n.ps-catalog .hp-list { display: flex; flex-direction: column; gap: 3px; margin-top: 4px; }\n.ps-catalog .hp { display: flex; justify-content: space-between; gap: 12px; font-size: 12px; }\n.ps-catalog .adetail-foot { display: flex; justify-content: flex-start; margin-top: 6px; }\n"] }]
|
|
573
573
|
}], null, { engine: [{
|
|
574
574
|
type: Input
|
|
575
575
|
}], askAgent: [{
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ps-catalog.component.js","sourceRoot":"","sources":["../../../src/PredictiveStudio/components/ps-catalog.component.ts"],"names":[],"mappings":"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","sourcesContent":["import { Component, EventEmitter, Input, OnInit, Output, ViewEncapsulation } from '@angular/core';\nimport { CommonModule } from '@angular/common';\nimport { MJButtonDirective } from '@memberjunction/ng-ui-components';\nimport { UUIDsEqual } from '@memberjunction/global';\nimport { MJMLAlgorithmEntity, MJMLAlgorithmUseCaseEntity } from '@memberjunction/core-entities';\nimport { PredictiveStudioEngine, RecommendationLevel, RECOMMENDATION_RANK } from '../engine/predictive-studio.engine';\n\ninterface AlgoCardVM {\n algo: MJMLAlgorithmEntity;\n icon: string;\n problemTypes: string[];\n bestLevel: RecommendationLevel | null;\n}\n\nconst ALGO_ICONS: Record<string, string> = {\n xgboost: 'fa-solid fa-bolt',\n lightgbm: 'fa-solid fa-feather',\n logistic_regression: 'fa-solid fa-wave-square',\n random_forest: 'fa-solid fa-tree',\n ridge: 'fa-solid fa-ruler',\n linear_regression: 'fa-solid fa-ruler',\n mlp: 'fa-solid fa-network-wired',\n};\n\nconst USE_CASE_ICONS: Record<string, string> = {\n binary: 'fa-solid fa-circle-half-stroke',\n regression: 'fa-solid fa-arrow-trend-up',\n interpret: 'fa-solid fa-magnifying-glass-chart',\n tuning: 'fa-solid fa-sliders',\n large: 'fa-solid fa-gauge-high',\n embedding: 'fa-solid fa-vector-square',\n small: 'fa-solid fa-compress',\n};\n\n/**\n * Algorithm Catalog panel (mockup algorithms-2): a card gallery of the fixed algorithm catalog plus\n * a \"Guide me\" scenario picker. Selecting use-case chips highlights and re-sorts the algorithm cards\n * by each algorithm's best RecommendationLevel across the chosen scenarios — driven entirely by the\n * cached MJ: ML Algorithm Use Case Rankings matrix.\n */\n@Component({\n standalone: true,\n selector: 'ps-catalog',\n imports: [CommonModule, MJButtonDirective],\n encapsulation: ViewEncapsulation.None,\n styleUrls: ['../predictive-studio.shared.scss', './ps-catalog.component.scss'],\n template: `\n <div class=\"ps-panel ps-catalog\" data-testid=\"ps-catalog-panel\">\n <!-- Guide me scenario picker -->\n <div class=\"ps-card guide-card\">\n <div class=\"ps-card-body\">\n <div class=\"guide-head\">\n <i class=\"fa-solid fa-compass\" style=\"color: var(--mj-brand-primary)\"></i>\n <div>\n <h3>Guide me — what does your problem look like?</h3>\n <div class=\"ps-muted ps-small\">\n Tap the scenarios that describe your data and goals. We'll highlight the algorithms that fit\n and pull the best matches to the front. Pick one or several.\n </div>\n </div>\n </div>\n <div class=\"chips-row\">\n <div class=\"chips\" data-testid=\"ps-catalog-scenarios\">\n @for (uc of useCases; track uc.ID) {\n <button class=\"chip\" [class.on]=\"isSelected(uc.ID)\" data-testid=\"ps-catalog-scenario-chip\" (click)=\"toggleUseCase(uc.ID)\">\n <i [class]=\"useCaseIcon(uc)\"></i> {{ uc.Name }}\n </button>\n }\n </div>\n @if (selectedUseCaseIds.length > 0) {\n <button class=\"clearx\" (click)=\"clearScenarios()\"><i class=\"fa-solid fa-xmark\"></i> Clear</button>\n }\n </div>\n @if (selectedUseCaseIds.length > 0) {\n <div class=\"ps-callout info reco-banner\" data-testid=\"ps-catalog-reco-banner\">\n <i class=\"fa-solid fa-wand-magic-sparkles\"></i>\n @if (recommendation.primaries.length > 0) {\n <div>For <strong>{{ recommendation.scenarioLabel }}</strong>, your top pick(s):\n <strong>{{ recommendation.primaries.join(' & ') }}</strong>. Stronger fits are pulled to the front.</div>\n } @else {\n <div>For <strong>{{ recommendation.scenarioLabel }}</strong>, no single primary stands out —\n review the strong fits at the top.</div>\n }\n </div>\n }\n </div>\n </div>\n\n <!-- Card gallery -->\n @if (cards.length === 0) {\n <div class=\"ps-card\"><div class=\"ps-card-body ps-muted\">No algorithms in the catalog yet.</div></div>\n } @else {\n <div class=\"gallery\" data-testid=\"ps-catalog-gallery\">\n @for (card of cards; track card.algo.ID) {\n <div class=\"acard\" data-testid=\"ps-catalog-card\"\n [class.hit]=\"card.bestLevel === 'Primary'\"\n [class.dim]=\"card.bestLevel && rank(card.bestLevel) <= 1\">\n @if (card.bestLevel === 'Primary') {\n <div class=\"ribbon\"><span class=\"ps-lvl ps-lvl-Primary\">Primary</span></div>\n }\n <div class=\"ah\">\n <div class=\"ic\"><i [class]=\"card.icon\"></i></div>\n <div class=\"title\">\n <div class=\"nm\">{{ card.algo.Name }}</div>\n <div class=\"ps-mono key\">{{ card.algo.DriverClass }}</div>\n </div>\n </div>\n <div class=\"ab\">\n @if (card.algo.Description) {\n <div class=\"ps-muted ps-small\">{{ card.algo.Description }}</div>\n }\n <div class=\"kv-title\">Problem types</div>\n <div class=\"meta-row\">\n @for (pt of card.problemTypes; track pt) {\n <span class=\"ps-tag\">{{ pt }}</span>\n }\n </div>\n <div class=\"meta-row\">\n @if (card.algo.SupportsFeatureImportance) {\n <span class=\"ps-badge green\"><i class=\"fa-solid fa-chart-column\"></i> Feature importance</span>\n } @else {\n <span class=\"ps-badge red\"><i class=\"fa-solid fa-ban\"></i> No importance</span>\n }\n </div>\n </div>\n <div class=\"af\">\n @if (card.bestLevel) {\n <span class=\"ps-lvl\" [class]=\"'ps-lvl-' + card.bestLevel\">{{ levelLabel(card.bestLevel) }}</span>\n }\n <span class=\"ps-spacer\"></span>\n <button mjButton variant=\"secondary\" size=\"sm\" data-testid=\"ps-catalog-details\"\n [class.on]=\"detailId === card.algo.ID\" (click)=\"toggleDetail(card.algo.ID)\">\n <i class=\"fa-solid fa-circle-info\"></i> Details\n </button>\n <button mjButton variant=\"primary\" size=\"sm\" data-testid=\"ps-catalog-use\"\n (click)=\"useAlgorithm(card.algo)\">\n <i class=\"fa-solid fa-diagram-project\"></i> Use\n </button>\n </div>\n @if (detailId === card.algo.ID) {\n <div class=\"adetail\" data-testid=\"ps-catalog-detail\">\n @if (card.algo.Description) {\n <p class=\"ps-small adetail-desc\">{{ card.algo.Description }}</p>\n }\n <div class=\"dkv\"><span class=\"dk\">Problem types</span><span class=\"dv\">{{ card.problemTypes.join(', ') || '—' }}</span></div>\n <div class=\"dkv\"><span class=\"dk\">Driver class</span><span class=\"dv ps-mono\">{{ card.algo.DriverClass }}</span></div>\n @if (hyperparams(card.algo).length > 0) {\n <div class=\"dk\" style=\"margin-top:8px\">Default hyperparameters</div>\n <div class=\"hp-list\">\n @for (hp of hyperparams(card.algo); track hp.k) {\n <div class=\"hp\"><span class=\"ps-mono\">{{ hp.k }}</span><span class=\"ps-mono ps-muted\">{{ hp.v }}</span></div>\n }\n </div>\n } @else {\n <div class=\"ps-small ps-muted\" style=\"margin-top:6px\">Sensible defaults are applied at training time.</div>\n }\n <div class=\"adetail-foot\">\n <button mjButton variant=\"primary\" size=\"sm\" (click)=\"useAlgorithm(card.algo)\">\n <i class=\"fa-solid fa-diagram-project\"></i> Use this algorithm\n </button>\n </div>\n </div>\n }\n </div>\n }\n </div>\n }\n </div>\n `,\n})\nexport class PSCatalogComponent implements OnInit {\n @Input() engine!: PredictiveStudioEngine;\n\n /** Emitted with a starter prompt to open + seed the Model Development Agent chat. */\n @Output() askAgent = new EventEmitter<string>();\n\n public selectedUseCaseIds: string[] = [];\n public cards: AlgoCardVM[] = [];\n /** The algorithm whose inline detail panel is expanded (null = none). */\n public detailId: string | null = null;\n\n ngOnInit(): void {\n this.rebuildCards();\n }\n\n public get useCases(): MJMLAlgorithmUseCaseEntity[] {\n return this.engine?.UseCases ?? [];\n }\n\n public useCaseIcon(uc: MJMLAlgorithmUseCaseEntity): string {\n const name = (uc.Name || '').toLowerCase();\n if (name.includes('binary')) return USE_CASE_ICONS['binary'];\n if (name.includes('regression')) return USE_CASE_ICONS['regression'];\n if (name.includes('interpret')) return USE_CASE_ICONS['interpret'];\n if (name.includes('tuning') || name.includes('minimal')) return USE_CASE_ICONS['tuning'];\n if (name.includes('large') || name.includes('wide') || name.includes('speed')) return USE_CASE_ICONS['large'];\n if (name.includes('embedding') || name.includes('llm')) return USE_CASE_ICONS['embedding'];\n if (name.includes('small')) return USE_CASE_ICONS['small'];\n return 'fa-solid fa-circle-dot';\n }\n\n public isSelected(id: string): boolean {\n return this.selectedUseCaseIds.some((s) => UUIDsEqual(s, id));\n }\n\n public toggleUseCase(id: string): void {\n if (this.isSelected(id)) {\n this.selectedUseCaseIds = this.selectedUseCaseIds.filter((s) => !UUIDsEqual(s, id));\n } else {\n this.selectedUseCaseIds = [...this.selectedUseCaseIds, id];\n }\n this.rebuildCards();\n }\n\n public clearScenarios(): void {\n this.selectedUseCaseIds = [];\n this.rebuildCards();\n }\n\n // ---- algorithm card actions ----\n\n /** Toggle the inline detail panel for an algorithm card. */\n public toggleDetail(id: string): void {\n this.detailId = this.detailId === id ? null : id;\n }\n\n /** Seed the Model Development Agent to build a model with the chosen algorithm. */\n public useAlgorithm(algo: MJMLAlgorithmEntity): void {\n this.askAgent.emit(\n `Help me build a predictive model using the ${algo.Name} algorithm. ` +\n `Walk me through choosing the entity, the outcome to predict, and the features to assemble.`,\n );\n }\n\n /** Parse an algorithm's `DefaultHyperparameters` JSON into a small key/value list (≤8 rows). */\n public hyperparams(algo: MJMLAlgorithmEntity): { k: string; v: string }[] {\n const raw = algo.DefaultHyperparameters;\n if (!raw) {\n return [];\n }\n try {\n const parsed: unknown = JSON.parse(raw);\n if (!parsed || typeof parsed !== 'object') {\n return [];\n }\n return Object.entries(parsed as Record<string, unknown>)\n .map(([k, v]) => ({ k, v: String(v) }))\n .slice(0, 8);\n } catch {\n return [];\n }\n }\n\n public rank(level: RecommendationLevel): number {\n return RECOMMENDATION_RANK[level];\n }\n\n public levelLabel(level: RecommendationLevel): string {\n switch (level) {\n case 'Primary': return 'Primary fit';\n case 'Strong': return 'Strong fit';\n case 'Viable': return 'Viable';\n case 'Weak': return 'Weak fit';\n case 'NotRecommended': return 'N/A';\n }\n }\n\n /**\n * Structured recommendation data for the \"Guide me\" banner — rendered with plain template\n * interpolation + `<strong>` markup (no `[innerHTML]`). `scenarioLabel` is the human-readable\n * scenario name(s); `primaries` is the list of Primary-fit algorithm names.\n */\n public get recommendation(): { scenarioLabel: string; primaries: string[] } {\n const levels = this.engine.BestLevelsForScenarios(this.selectedUseCaseIds);\n const primaries = this.engine.Algorithms.filter((a) => levels.get(a.ID) === 'Primary').map((a) => a.Name);\n const scenarioLabel =\n this.selectedUseCaseIds.length === 1\n ? this.useCases.find((u) => UUIDsEqual(u.ID, this.selectedUseCaseIds[0]))?.Name ?? 'your scenario'\n : `${this.selectedUseCaseIds.length} scenarios`;\n return { scenarioLabel, primaries };\n }\n\n private rebuildCards(): void {\n const levels = this.engine.BestLevelsForScenarios(this.selectedUseCaseIds);\n const cards: AlgoCardVM[] = this.engine.Algorithms.map((algo) => ({\n algo,\n icon: ALGO_ICONS[algo.DriverClass] ?? 'fa-solid fa-shapes',\n problemTypes: (algo.ProblemTypes || '').split(',').map((p) => p.trim()).filter((p) => p.length > 0),\n bestLevel: levels.get(algo.ID) ?? null,\n }));\n if (this.selectedUseCaseIds.length > 0) {\n cards.sort((a, b) => {\n const ra = a.bestLevel ? RECOMMENDATION_RANK[a.bestLevel] : -1;\n const rb = b.bestLevel ? RECOMMENDATION_RANK[b.bestLevel] : -1;\n return rb - ra;\n });\n }\n this.cards = cards;\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"ps-catalog.component.js","sourceRoot":"","sources":["../../../src/PredictiveStudio/components/ps-catalog.component.ts"],"names":[],"mappings":"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","sourcesContent":["import { Component, EventEmitter, Input, OnInit, Output, ViewEncapsulation } from '@angular/core';\nimport { CommonModule } from '@angular/common';\nimport { MJButtonDirective } from '@memberjunction/ng-ui-components';\nimport { UUIDsEqual } from '@memberjunction/global';\nimport { MJMLAlgorithmEntity, MJMLAlgorithmUseCaseEntity } from '@memberjunction/core-entities';\nimport { PredictiveStudioEngine, RecommendationLevel, RECOMMENDATION_RANK } from '../engine/predictive-studio.engine';\n\ninterface AlgoCardVM {\n algo: MJMLAlgorithmEntity;\n icon: string;\n problemTypes: string[];\n bestLevel: RecommendationLevel | null;\n}\n\nconst ALGO_ICONS: Record<string, string> = {\n xgboost: 'fa-solid fa-bolt',\n lightgbm: 'fa-solid fa-feather',\n logistic_regression: 'fa-solid fa-wave-square',\n random_forest: 'fa-solid fa-tree',\n ridge: 'fa-solid fa-ruler',\n linear_regression: 'fa-solid fa-ruler',\n mlp: 'fa-solid fa-network-wired',\n};\n\nconst USE_CASE_ICONS: Record<string, string> = {\n binary: 'fa-solid fa-circle-half-stroke',\n regression: 'fa-solid fa-arrow-trend-up',\n interpret: 'fa-solid fa-magnifying-glass-chart',\n tuning: 'fa-solid fa-sliders',\n large: 'fa-solid fa-gauge-high',\n embedding: 'fa-solid fa-vector-square',\n small: 'fa-solid fa-compress',\n};\n\n/**\n * Algorithm Catalog panel (mockup algorithms-2): a card gallery of the fixed algorithm catalog plus\n * a \"Guide me\" scenario picker. Selecting use-case chips highlights and re-sorts the algorithm cards\n * by each algorithm's best RecommendationLevel across the chosen scenarios — driven entirely by the\n * cached MJ: ML Algorithm Use Case Rankings matrix.\n */\n@Component({\n standalone: true,\n selector: 'ps-catalog',\n imports: [CommonModule, MJButtonDirective],\n encapsulation: ViewEncapsulation.None,\n styleUrls: ['../predictive-studio.shared.css', './ps-catalog.component.css'],\n template: `\n <div class=\"ps-panel ps-catalog\" data-testid=\"ps-catalog-panel\">\n <!-- Guide me scenario picker -->\n <div class=\"ps-card guide-card\">\n <div class=\"ps-card-body\">\n <div class=\"guide-head\">\n <i class=\"fa-solid fa-compass\" style=\"color: var(--mj-brand-primary)\"></i>\n <div>\n <h3>Guide me — what does your problem look like?</h3>\n <div class=\"ps-muted ps-small\">\n Tap the scenarios that describe your data and goals. We'll highlight the algorithms that fit\n and pull the best matches to the front. Pick one or several.\n </div>\n </div>\n </div>\n <div class=\"chips-row\">\n <div class=\"chips\" data-testid=\"ps-catalog-scenarios\">\n @for (uc of useCases; track uc.ID) {\n <button class=\"chip\" [class.on]=\"isSelected(uc.ID)\" data-testid=\"ps-catalog-scenario-chip\" (click)=\"toggleUseCase(uc.ID)\">\n <i [class]=\"useCaseIcon(uc)\"></i> {{ uc.Name }}\n </button>\n }\n </div>\n @if (selectedUseCaseIds.length > 0) {\n <button class=\"clearx\" (click)=\"clearScenarios()\"><i class=\"fa-solid fa-xmark\"></i> Clear</button>\n }\n </div>\n @if (selectedUseCaseIds.length > 0) {\n <div class=\"ps-callout info reco-banner\" data-testid=\"ps-catalog-reco-banner\">\n <i class=\"fa-solid fa-wand-magic-sparkles\"></i>\n @if (recommendation.primaries.length > 0) {\n <div>For <strong>{{ recommendation.scenarioLabel }}</strong>, your top pick(s):\n <strong>{{ recommendation.primaries.join(' & ') }}</strong>. Stronger fits are pulled to the front.</div>\n } @else {\n <div>For <strong>{{ recommendation.scenarioLabel }}</strong>, no single primary stands out —\n review the strong fits at the top.</div>\n }\n </div>\n }\n </div>\n </div>\n\n <!-- Card gallery -->\n @if (cards.length === 0) {\n <div class=\"ps-card\"><div class=\"ps-card-body ps-muted\">No algorithms in the catalog yet.</div></div>\n } @else {\n <div class=\"gallery\" data-testid=\"ps-catalog-gallery\">\n @for (card of cards; track card.algo.ID) {\n <div class=\"acard\" data-testid=\"ps-catalog-card\"\n [class.hit]=\"card.bestLevel === 'Primary'\"\n [class.dim]=\"card.bestLevel && rank(card.bestLevel) <= 1\">\n @if (card.bestLevel === 'Primary') {\n <div class=\"ribbon\"><span class=\"ps-lvl ps-lvl-Primary\">Primary</span></div>\n }\n <div class=\"ah\">\n <div class=\"ic\"><i [class]=\"card.icon\"></i></div>\n <div class=\"title\">\n <div class=\"nm\">{{ card.algo.Name }}</div>\n <div class=\"ps-mono key\">{{ card.algo.DriverClass }}</div>\n </div>\n </div>\n <div class=\"ab\">\n @if (card.algo.Description) {\n <div class=\"ps-muted ps-small\">{{ card.algo.Description }}</div>\n }\n <div class=\"kv-title\">Problem types</div>\n <div class=\"meta-row\">\n @for (pt of card.problemTypes; track pt) {\n <span class=\"ps-tag\">{{ pt }}</span>\n }\n </div>\n <div class=\"meta-row\">\n @if (card.algo.SupportsFeatureImportance) {\n <span class=\"ps-badge green\"><i class=\"fa-solid fa-chart-column\"></i> Feature importance</span>\n } @else {\n <span class=\"ps-badge red\"><i class=\"fa-solid fa-ban\"></i> No importance</span>\n }\n </div>\n </div>\n <div class=\"af\">\n @if (card.bestLevel) {\n <span class=\"ps-lvl\" [class]=\"'ps-lvl-' + card.bestLevel\">{{ levelLabel(card.bestLevel) }}</span>\n }\n <span class=\"ps-spacer\"></span>\n <button mjButton variant=\"secondary\" size=\"sm\" data-testid=\"ps-catalog-details\"\n [class.on]=\"detailId === card.algo.ID\" (click)=\"toggleDetail(card.algo.ID)\">\n <i class=\"fa-solid fa-circle-info\"></i> Details\n </button>\n <button mjButton variant=\"primary\" size=\"sm\" data-testid=\"ps-catalog-use\"\n (click)=\"useAlgorithm(card.algo)\">\n <i class=\"fa-solid fa-diagram-project\"></i> Use\n </button>\n </div>\n @if (detailId === card.algo.ID) {\n <div class=\"adetail\" data-testid=\"ps-catalog-detail\">\n @if (card.algo.Description) {\n <p class=\"ps-small adetail-desc\">{{ card.algo.Description }}</p>\n }\n <div class=\"dkv\"><span class=\"dk\">Problem types</span><span class=\"dv\">{{ card.problemTypes.join(', ') || '—' }}</span></div>\n <div class=\"dkv\"><span class=\"dk\">Driver class</span><span class=\"dv ps-mono\">{{ card.algo.DriverClass }}</span></div>\n @if (hyperparams(card.algo).length > 0) {\n <div class=\"dk\" style=\"margin-top:8px\">Default hyperparameters</div>\n <div class=\"hp-list\">\n @for (hp of hyperparams(card.algo); track hp.k) {\n <div class=\"hp\"><span class=\"ps-mono\">{{ hp.k }}</span><span class=\"ps-mono ps-muted\">{{ hp.v }}</span></div>\n }\n </div>\n } @else {\n <div class=\"ps-small ps-muted\" style=\"margin-top:6px\">Sensible defaults are applied at training time.</div>\n }\n <div class=\"adetail-foot\">\n <button mjButton variant=\"primary\" size=\"sm\" (click)=\"useAlgorithm(card.algo)\">\n <i class=\"fa-solid fa-diagram-project\"></i> Use this algorithm\n </button>\n </div>\n </div>\n }\n </div>\n }\n </div>\n }\n </div>\n `,\n})\nexport class PSCatalogComponent implements OnInit {\n @Input() engine!: PredictiveStudioEngine;\n\n /** Emitted with a starter prompt to open + seed the Model Development Agent chat. */\n @Output() askAgent = new EventEmitter<string>();\n\n public selectedUseCaseIds: string[] = [];\n public cards: AlgoCardVM[] = [];\n /** The algorithm whose inline detail panel is expanded (null = none). */\n public detailId: string | null = null;\n\n ngOnInit(): void {\n this.rebuildCards();\n }\n\n public get useCases(): MJMLAlgorithmUseCaseEntity[] {\n return this.engine?.UseCases ?? [];\n }\n\n public useCaseIcon(uc: MJMLAlgorithmUseCaseEntity): string {\n const name = (uc.Name || '').toLowerCase();\n if (name.includes('binary')) return USE_CASE_ICONS['binary'];\n if (name.includes('regression')) return USE_CASE_ICONS['regression'];\n if (name.includes('interpret')) return USE_CASE_ICONS['interpret'];\n if (name.includes('tuning') || name.includes('minimal')) return USE_CASE_ICONS['tuning'];\n if (name.includes('large') || name.includes('wide') || name.includes('speed')) return USE_CASE_ICONS['large'];\n if (name.includes('embedding') || name.includes('llm')) return USE_CASE_ICONS['embedding'];\n if (name.includes('small')) return USE_CASE_ICONS['small'];\n return 'fa-solid fa-circle-dot';\n }\n\n public isSelected(id: string): boolean {\n return this.selectedUseCaseIds.some((s) => UUIDsEqual(s, id));\n }\n\n public toggleUseCase(id: string): void {\n if (this.isSelected(id)) {\n this.selectedUseCaseIds = this.selectedUseCaseIds.filter((s) => !UUIDsEqual(s, id));\n } else {\n this.selectedUseCaseIds = [...this.selectedUseCaseIds, id];\n }\n this.rebuildCards();\n }\n\n public clearScenarios(): void {\n this.selectedUseCaseIds = [];\n this.rebuildCards();\n }\n\n // ---- algorithm card actions ----\n\n /** Toggle the inline detail panel for an algorithm card. */\n public toggleDetail(id: string): void {\n this.detailId = this.detailId === id ? null : id;\n }\n\n /** Seed the Model Development Agent to build a model with the chosen algorithm. */\n public useAlgorithm(algo: MJMLAlgorithmEntity): void {\n this.askAgent.emit(\n `Help me build a predictive model using the ${algo.Name} algorithm. ` +\n `Walk me through choosing the entity, the outcome to predict, and the features to assemble.`,\n );\n }\n\n /** Parse an algorithm's `DefaultHyperparameters` JSON into a small key/value list (≤8 rows). */\n public hyperparams(algo: MJMLAlgorithmEntity): { k: string; v: string }[] {\n const raw = algo.DefaultHyperparameters;\n if (!raw) {\n return [];\n }\n try {\n const parsed: unknown = JSON.parse(raw);\n if (!parsed || typeof parsed !== 'object') {\n return [];\n }\n return Object.entries(parsed as Record<string, unknown>)\n .map(([k, v]) => ({ k, v: String(v) }))\n .slice(0, 8);\n } catch {\n return [];\n }\n }\n\n public rank(level: RecommendationLevel): number {\n return RECOMMENDATION_RANK[level];\n }\n\n public levelLabel(level: RecommendationLevel): string {\n switch (level) {\n case 'Primary': return 'Primary fit';\n case 'Strong': return 'Strong fit';\n case 'Viable': return 'Viable';\n case 'Weak': return 'Weak fit';\n case 'NotRecommended': return 'N/A';\n }\n }\n\n /**\n * Structured recommendation data for the \"Guide me\" banner — rendered with plain template\n * interpolation + `<strong>` markup (no `[innerHTML]`). `scenarioLabel` is the human-readable\n * scenario name(s); `primaries` is the list of Primary-fit algorithm names.\n */\n public get recommendation(): { scenarioLabel: string; primaries: string[] } {\n const levels = this.engine.BestLevelsForScenarios(this.selectedUseCaseIds);\n const primaries = this.engine.Algorithms.filter((a) => levels.get(a.ID) === 'Primary').map((a) => a.Name);\n const scenarioLabel =\n this.selectedUseCaseIds.length === 1\n ? this.useCases.find((u) => UUIDsEqual(u.ID, this.selectedUseCaseIds[0]))?.Name ?? 'your scenario'\n : `${this.selectedUseCaseIds.length} scenarios`;\n return { scenarioLabel, primaries };\n }\n\n private rebuildCards(): void {\n const levels = this.engine.BestLevelsForScenarios(this.selectedUseCaseIds);\n const cards: AlgoCardVM[] = this.engine.Algorithms.map((algo) => ({\n algo,\n icon: ALGO_ICONS[algo.DriverClass] ?? 'fa-solid fa-shapes',\n problemTypes: (algo.ProblemTypes || '').split(',').map((p) => p.trim()).filter((p) => p.length > 0),\n bestLevel: levels.get(algo.ID) ?? null,\n }));\n if (this.selectedUseCaseIds.length > 0) {\n cards.sort((a, b) => {\n const ra = a.bestLevel ? RECOMMENDATION_RANK[a.bestLevel] : -1;\n const rb = b.bestLevel ? RECOMMENDATION_RANK[b.bestLevel] : -1;\n return rb - ra;\n });\n }\n this.cards = cards;\n }\n}\n"]}
|