@duckcodeailabs/dql-cli 1.6.18 → 1.6.19
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/apps-api.d.ts +41 -1
- package/dist/apps-api.d.ts.map +1 -1
- package/dist/apps-api.js +297 -18
- package/dist/apps-api.js.map +1 -1
- package/dist/args.d.ts +4 -0
- package/dist/args.d.ts.map +1 -1
- package/dist/args.js +10 -1
- package/dist/args.js.map +1 -1
- package/dist/assets/dql-notebook/assets/index-CcpAv4lQ.js +3864 -0
- package/dist/assets/dql-notebook/assets/index-rJXLSheJ.js +3910 -0
- package/dist/assets/dql-notebook/index.html +1 -1
- package/dist/block-studio-import.d.ts +99 -3
- package/dist/block-studio-import.d.ts.map +1 -1
- package/dist/block-studio-import.js +772 -27
- package/dist/block-studio-import.js.map +1 -1
- package/dist/commands/agent.d.ts.map +1 -1
- package/dist/commands/agent.js +12 -1
- package/dist/commands/agent.js.map +1 -1
- package/dist/commands/app.d.ts +2 -1
- package/dist/commands/app.d.ts.map +1 -1
- package/dist/commands/app.js +22 -4
- package/dist/commands/app.js.map +1 -1
- package/dist/commands/certify.d.ts.map +1 -1
- package/dist/commands/certify.js +104 -10
- package/dist/commands/certify.js.map +1 -1
- package/dist/commands/compile.d.ts +1 -0
- package/dist/commands/compile.d.ts.map +1 -1
- package/dist/commands/compile.js +26 -4
- package/dist/commands/compile.js.map +1 -1
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/doctor.js +345 -6
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/import.d.ts.map +1 -1
- package/dist/commands/import.js +45 -70
- package/dist/commands/import.js.map +1 -1
- package/dist/commands/lineage.d.ts +2 -0
- package/dist/commands/lineage.d.ts.map +1 -1
- package/dist/commands/lineage.js +111 -0
- package/dist/commands/lineage.js.map +1 -1
- package/dist/commands/migrate.d.ts +1 -0
- package/dist/commands/migrate.d.ts.map +1 -1
- package/dist/commands/migrate.js +106 -2
- package/dist/commands/migrate.js.map +1 -1
- package/dist/commands/new.d.ts.map +1 -1
- package/dist/commands/new.js +246 -9
- package/dist/commands/new.js.map +1 -1
- package/dist/commands/promote.d.ts +11 -0
- package/dist/commands/promote.d.ts.map +1 -0
- package/dist/commands/promote.js +236 -0
- package/dist/commands/promote.js.map +1 -0
- package/dist/commands/semantic.js +2 -2
- package/dist/commands/semantic.js.map +1 -1
- package/dist/index.js +48 -8
- package/dist/index.js.map +1 -1
- package/dist/llm/providers/claude-agent-sdk.js +1 -1
- package/dist/llm/providers/claude-agent-sdk.js.map +1 -1
- package/dist/llm/tools.js +1 -1
- package/dist/llm/tools.js.map +1 -1
- package/dist/local-runtime.d.ts +176 -5
- package/dist/local-runtime.d.ts.map +1 -1
- package/dist/local-runtime.js +3681 -146
- package/dist/local-runtime.js.map +1 -1
- package/dist/package.json +10 -10
- package/dist/promote-from-draft.d.ts +11 -10
- package/dist/promote-from-draft.d.ts.map +1 -1
- package/dist/promote-from-draft.js +53 -30
- package/dist/promote-from-draft.js.map +1 -1
- package/dist/schedule/runner.js +2 -2
- package/dist/schedule/runner.js.map +1 -1
- package/dist/semantic-import.d.ts.map +1 -1
- package/dist/semantic-import.js +28 -12
- package/dist/semantic-import.js.map +1 -1
- package/package.json +11 -11
- package/dist/assets/dql-notebook/assets/index-BbGJ8gZk.js +0 -3639
- package/dist/assets/dql-notebook/assets/index-C4oZK5xm.js +0 -3612
- package/dist/assets/dql-notebook/assets/index-CEMXuPm0.js +0 -3802
- package/dist/assets/dql-notebook/assets/index-CIMLd3Cb.js +0 -3289
- package/dist/assets/dql-notebook/assets/index-DISMWw19.js +0 -3780
- package/dist/assets/dql-notebook/assets/index-DPcxWQuS.js +0 -3859
- package/dist/assets/dql-notebook/assets/index-McpLeeN0.js +0 -3802
package/dist/apps-api.d.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* dispatcher — returns `true` if the request was handled, `false` otherwise.
|
|
5
5
|
*/
|
|
6
6
|
import type { IncomingMessage, ServerResponse } from 'node:http';
|
|
7
|
-
import { type AppDocument } from '@duckcodeailabs/dql-core';
|
|
7
|
+
import { type AppDocument, type DashboardDisplayMetadata, type DashboardDocument } from '@duckcodeailabs/dql-core';
|
|
8
8
|
import { type LocalAppInvestigationIntent } from '@duckcodeailabs/dql-project';
|
|
9
9
|
interface Ctx {
|
|
10
10
|
req: IncomingMessage;
|
|
@@ -20,6 +20,7 @@ export declare function handleAppsApi(ctx: Ctx): Promise<boolean>;
|
|
|
20
20
|
type AppListEntry = {
|
|
21
21
|
id: string;
|
|
22
22
|
name: string;
|
|
23
|
+
filePath: string;
|
|
23
24
|
domain: string;
|
|
24
25
|
subdomain?: string;
|
|
25
26
|
groups: string[];
|
|
@@ -63,6 +64,19 @@ interface AppRecommendationRequest {
|
|
|
63
64
|
audience?: string;
|
|
64
65
|
certifiedOnly?: boolean;
|
|
65
66
|
}
|
|
67
|
+
interface VisualizationRecommendationRequest {
|
|
68
|
+
blockRef?: string;
|
|
69
|
+
resultSchema?: unknown;
|
|
70
|
+
rowSample?: Array<Record<string, unknown>>;
|
|
71
|
+
rows?: Array<Record<string, unknown>>;
|
|
72
|
+
appAudience?: string;
|
|
73
|
+
audience?: string;
|
|
74
|
+
prompt?: string;
|
|
75
|
+
filters?: unknown;
|
|
76
|
+
allowedVisualizations?: string[];
|
|
77
|
+
component?: DashboardDisplayMetadata['component'];
|
|
78
|
+
defaultVisualization?: string;
|
|
79
|
+
}
|
|
66
80
|
interface AppCreateRequest {
|
|
67
81
|
name?: string;
|
|
68
82
|
domain?: string;
|
|
@@ -122,6 +136,18 @@ interface BlockCandidate {
|
|
|
122
136
|
reasons: string[];
|
|
123
137
|
}
|
|
124
138
|
export declare function recommendBlocks(projectRoot: string, input: AppRecommendationRequest): BlockCandidate[];
|
|
139
|
+
export declare function recommendVisualization(projectRoot: string, input: VisualizationRecommendationRequest): {
|
|
140
|
+
ok: true;
|
|
141
|
+
display: DashboardDisplayMetadata;
|
|
142
|
+
evidence: Array<{
|
|
143
|
+
source: string;
|
|
144
|
+
reason: string;
|
|
145
|
+
}>;
|
|
146
|
+
warnings: string[];
|
|
147
|
+
} | {
|
|
148
|
+
ok: false;
|
|
149
|
+
error: string;
|
|
150
|
+
};
|
|
125
151
|
export declare function generateAppPackage(projectRoot: string, input: AppGenerateRequest): Promise<{
|
|
126
152
|
ok: true;
|
|
127
153
|
plan: unknown;
|
|
@@ -157,8 +183,22 @@ declare function buildDeterministicInvestigationSql(projectRoot: string, input:
|
|
|
157
183
|
sourceBlockPath: string;
|
|
158
184
|
sourceBlockName: string;
|
|
159
185
|
} | undefined;
|
|
186
|
+
export declare function createDashboardForApp(projectRoot: string, appId: string, input: {
|
|
187
|
+
id?: string;
|
|
188
|
+
title?: string;
|
|
189
|
+
description?: string;
|
|
190
|
+
}): {
|
|
191
|
+
ok: true;
|
|
192
|
+
dashboard: DashboardDocument;
|
|
193
|
+
path: string;
|
|
194
|
+
} | {
|
|
195
|
+
ok: false;
|
|
196
|
+
error: string;
|
|
197
|
+
};
|
|
160
198
|
declare function loadAppById(projectRoot: string, id: string): {
|
|
161
199
|
app: AppDocument;
|
|
200
|
+
appDir: string;
|
|
201
|
+
appPath: string;
|
|
162
202
|
dashboards: Array<{
|
|
163
203
|
id: string;
|
|
164
204
|
title: string;
|
package/dist/apps-api.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"apps-api.d.ts","sourceRoot":"","sources":["../src/apps-api.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAIH,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AACjE,OAAO,EAQL,KAAK,WAAW,
|
|
1
|
+
{"version":3,"file":"apps-api.d.ts","sourceRoot":"","sources":["../src/apps-api.ts"],"names":[],"mappings":"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"}
|
package/dist/apps-api.js
CHANGED
|
@@ -9,6 +9,21 @@ import { loadAppDocument, findAppDocuments, loadDashboardDocument, findDashboard
|
|
|
9
9
|
import { defaultPersonaRegistry, defaultLocalAppsDbPath, LocalAppStorage, personaFromMember, } from '@duckcodeailabs/dql-project';
|
|
10
10
|
export async function handleAppsApi(ctx) {
|
|
11
11
|
const { req, res, path, projectRoot } = ctx;
|
|
12
|
+
if (req.method === 'POST' && path === '/api/visualizations/recommend') {
|
|
13
|
+
try {
|
|
14
|
+
const body = await readJson(req);
|
|
15
|
+
const result = recommendVisualization(projectRoot, body);
|
|
16
|
+
if (!result.ok) {
|
|
17
|
+
sendJson(res, 400, result);
|
|
18
|
+
return true;
|
|
19
|
+
}
|
|
20
|
+
sendJson(res, 200, result);
|
|
21
|
+
}
|
|
22
|
+
catch (err) {
|
|
23
|
+
sendJson(res, 500, { ok: false, error: err.message });
|
|
24
|
+
}
|
|
25
|
+
return true;
|
|
26
|
+
}
|
|
12
27
|
// ── Apps ────────────────────────────────────────────────────────────────
|
|
13
28
|
if (req.method === 'GET' && path === '/api/apps') {
|
|
14
29
|
const apps = collectAppsList(projectRoot);
|
|
@@ -107,7 +122,7 @@ export async function handleAppsApi(ctx) {
|
|
|
107
122
|
sendJson(res, 404, { error: `App "${appId}" not found` });
|
|
108
123
|
return true;
|
|
109
124
|
}
|
|
110
|
-
sendJson(res, 200, { notebooks: listNotebookCandidates(projectRoot, loaded.app,
|
|
125
|
+
sendJson(res, 200, { notebooks: listNotebookCandidates(projectRoot, loaded.app, loaded.appDir) });
|
|
111
126
|
return true;
|
|
112
127
|
}
|
|
113
128
|
m = path.match(/^\/api\/apps\/([^/]+)\/notebooks\/create$/);
|
|
@@ -612,6 +627,7 @@ function collectAppsList(projectRoot) {
|
|
|
612
627
|
out.push({
|
|
613
628
|
id: document.id,
|
|
614
629
|
name: document.name,
|
|
630
|
+
filePath: relative(projectRoot, appDir),
|
|
615
631
|
domain: document.domain,
|
|
616
632
|
subdomain: document.subdomain,
|
|
617
633
|
groups: document.groups ?? [],
|
|
@@ -710,6 +726,79 @@ export function recommendBlocks(projectRoot, input) {
|
|
|
710
726
|
.sort((a, b) => b.score - a.score || new Date(b.lastModified).getTime() - new Date(a.lastModified).getTime())
|
|
711
727
|
.slice(0, 50);
|
|
712
728
|
}
|
|
729
|
+
export function recommendVisualization(projectRoot, input) {
|
|
730
|
+
const blockRef = cleanString(input.blockRef);
|
|
731
|
+
const prompt = cleanString(input.prompt).toLowerCase();
|
|
732
|
+
const audience = cleanString(input.appAudience) || cleanString(input.audience);
|
|
733
|
+
const rows = Array.isArray(input.rowSample) ? input.rowSample : Array.isArray(input.rows) ? input.rows : [];
|
|
734
|
+
const columns = extractRecommendationColumns(input.resultSchema, rows);
|
|
735
|
+
const block = blockRef
|
|
736
|
+
? collectBlockCandidates(projectRoot).find((candidate) => candidate.id === blockRef ||
|
|
737
|
+
candidate.name === blockRef ||
|
|
738
|
+
candidate.path === blockRef ||
|
|
739
|
+
candidate.path.endsWith(`/${blockRef}`))
|
|
740
|
+
: undefined;
|
|
741
|
+
const evidence = [];
|
|
742
|
+
const warnings = [];
|
|
743
|
+
if (block) {
|
|
744
|
+
evidence.push({ source: block.path, reason: `Block hint: ${block.chartType ?? 'table'}; status: ${block.status}; domain: ${block.domain}` });
|
|
745
|
+
}
|
|
746
|
+
else if (blockRef) {
|
|
747
|
+
warnings.push(`Block reference was not found locally: ${blockRef}`);
|
|
748
|
+
}
|
|
749
|
+
if (columns.length > 0) {
|
|
750
|
+
evidence.push({ source: 'result_schema', reason: `${columns.length} result column(s) were inspected.` });
|
|
751
|
+
}
|
|
752
|
+
else {
|
|
753
|
+
warnings.push('No result schema or row sample was provided, so recommendation used block metadata and prompt only.');
|
|
754
|
+
}
|
|
755
|
+
if (audience) {
|
|
756
|
+
evidence.push({ source: 'audience', reason: `Audience: ${audience}` });
|
|
757
|
+
}
|
|
758
|
+
const requestedViz = cleanString(input.defaultVisualization);
|
|
759
|
+
if (requestedViz && !isSupportedVizType(requestedViz)) {
|
|
760
|
+
return { ok: false, error: `Unsupported visualization: ${requestedViz}` };
|
|
761
|
+
}
|
|
762
|
+
const defaultVisualization = requestedViz
|
|
763
|
+
? normalizeVizType(requestedViz)
|
|
764
|
+
: recommendVizType({ columns, rows, prompt, blockChartType: block?.chartType });
|
|
765
|
+
const component = input.component ?? componentForViz(defaultVisualization, prompt);
|
|
766
|
+
if (!componentVizCompatible(component, defaultVisualization)) {
|
|
767
|
+
return {
|
|
768
|
+
ok: false,
|
|
769
|
+
error: `${component} cannot use ${defaultVisualization}. Choose one of ${allowedVisualizationsForComponent(component, defaultVisualization).join(', ')}.`,
|
|
770
|
+
};
|
|
771
|
+
}
|
|
772
|
+
const unsupportedAllowed = (input.allowedVisualizations ?? []).filter((value) => !isSupportedVizType(value));
|
|
773
|
+
if (unsupportedAllowed.length > 0) {
|
|
774
|
+
return { ok: false, error: `Unsupported allowed visualization(s): ${unsupportedAllowed.join(', ')}` };
|
|
775
|
+
}
|
|
776
|
+
const supportedAllowed = (input.allowedVisualizations ?? [])
|
|
777
|
+
.map((value) => normalizeVizType(value))
|
|
778
|
+
.filter((value, index, arr) => arr.indexOf(value) === index);
|
|
779
|
+
if (supportedAllowed.length > 0 && !supportedAllowed.includes(defaultVisualization)) {
|
|
780
|
+
return {
|
|
781
|
+
ok: false,
|
|
782
|
+
error: `Requested visualization ${defaultVisualization} is outside allowed visualizations: ${supportedAllowed.join(', ')}.`,
|
|
783
|
+
};
|
|
784
|
+
}
|
|
785
|
+
const allowedVisualizations = supportedAllowed.length > 0
|
|
786
|
+
? supportedAllowed
|
|
787
|
+
: allowedVisualizationsForComponent(component, defaultVisualization);
|
|
788
|
+
const fieldHints = fieldHintsForColumns(columns, rows, prompt);
|
|
789
|
+
const display = {
|
|
790
|
+
mode: block ? 'block_hint' : 'ai_generated',
|
|
791
|
+
component,
|
|
792
|
+
defaultVisualization,
|
|
793
|
+
allowedVisualizations,
|
|
794
|
+
...(Object.keys(fieldHints).length > 0 ? { fieldHints } : {}),
|
|
795
|
+
layoutIntent: layoutIntentForComponent(component),
|
|
796
|
+
rationale: recommendationRationale(component, defaultVisualization, block, columns, prompt),
|
|
797
|
+
trustState: block?.status === 'certified' ? 'certified' : 'review_required',
|
|
798
|
+
reviewStatus: block?.status === 'certified' ? 'certified' : 'review_required',
|
|
799
|
+
};
|
|
800
|
+
return { ok: true, display, evidence, warnings };
|
|
801
|
+
}
|
|
713
802
|
export async function generateAppPackage(projectRoot, input) {
|
|
714
803
|
const prompt = cleanString(input.prompt);
|
|
715
804
|
if (!prompt)
|
|
@@ -758,7 +847,7 @@ export function createAppPackage(projectRoot, input) {
|
|
|
758
847
|
if (!domain)
|
|
759
848
|
return { ok: false, error: 'domain is required' };
|
|
760
849
|
const id = suggestAppId(name);
|
|
761
|
-
const appDir =
|
|
850
|
+
const appDir = resolveAppPackageDir(projectRoot, domain, id);
|
|
762
851
|
if (existsSync(appDir))
|
|
763
852
|
return { ok: false, error: `App already exists: ${id}` };
|
|
764
853
|
const dashboardTitle = cleanString(input.dashboardTitle) || 'Overview';
|
|
@@ -901,6 +990,7 @@ function dashboardItemForBlock(block, chartType, x, y, size, index) {
|
|
|
901
990
|
h: size.h,
|
|
902
991
|
block: { blockId: block.name },
|
|
903
992
|
viz: { type: chartType },
|
|
993
|
+
display: displayForBlockHint(block, chartType),
|
|
904
994
|
title: block.name,
|
|
905
995
|
};
|
|
906
996
|
}
|
|
@@ -957,8 +1047,183 @@ function normalizeVizType(chartType) {
|
|
|
957
1047
|
return 'map';
|
|
958
1048
|
if (normalized === 'funnel')
|
|
959
1049
|
return 'funnel';
|
|
1050
|
+
if (normalized === 'text')
|
|
1051
|
+
return 'text';
|
|
1052
|
+
if (normalized === 'heading')
|
|
1053
|
+
return 'heading';
|
|
960
1054
|
return 'table';
|
|
961
1055
|
}
|
|
1056
|
+
function isSupportedVizType(chartType) {
|
|
1057
|
+
const normalized = (chartType ?? '').toLowerCase().replace(/-/g, '_');
|
|
1058
|
+
return [
|
|
1059
|
+
'single', 'single_value', 'kpi', 'line', 'bar', 'grouped_bar', 'stacked_bar',
|
|
1060
|
+
'area', 'pie', 'donut', 'scatter', 'heatmap', 'histogram', 'waterfall',
|
|
1061
|
+
'gauge', 'pivot', 'map', 'funnel', 'table', 'text', 'heading',
|
|
1062
|
+
].includes(normalized);
|
|
1063
|
+
}
|
|
1064
|
+
function displayForBlockHint(block, chartType) {
|
|
1065
|
+
const component = componentForViz(chartType, `${block.name} ${block.description} ${block.tags.join(' ')}`.toLowerCase());
|
|
1066
|
+
return {
|
|
1067
|
+
mode: 'block_hint',
|
|
1068
|
+
component,
|
|
1069
|
+
defaultVisualization: chartType,
|
|
1070
|
+
allowedVisualizations: allowedVisualizationsForComponent(component, chartType),
|
|
1071
|
+
layoutIntent: layoutIntentForComponent(component),
|
|
1072
|
+
rationale: `Block-level visualization is treated as a display hint for ${block.name}; apps and notebooks may override it.`,
|
|
1073
|
+
trustState: block.status === 'certified' ? 'certified' : 'draft_ready',
|
|
1074
|
+
reviewStatus: block.status === 'certified' ? 'certified' : 'draft_ready',
|
|
1075
|
+
};
|
|
1076
|
+
}
|
|
1077
|
+
function displayForAiPin(title, chartType, input) {
|
|
1078
|
+
const text = `${title} ${cleanString(input.question)} ${cleanString(input.answer)}`.toLowerCase();
|
|
1079
|
+
const component = componentForViz(chartType, text);
|
|
1080
|
+
const certified = input.certification === 'certified' || input.reviewStatus === 'certified';
|
|
1081
|
+
return {
|
|
1082
|
+
mode: 'ai_generated',
|
|
1083
|
+
component,
|
|
1084
|
+
defaultVisualization: chartType,
|
|
1085
|
+
allowedVisualizations: allowedVisualizationsForComponent(component, chartType),
|
|
1086
|
+
layoutIntent: layoutIntentForComponent(component),
|
|
1087
|
+
rationale: 'AI-generated research pin saved as governed presentation metadata; promote the SQL to a draft block before treating it as reusable logic.',
|
|
1088
|
+
trustState: certified ? 'certified' : 'review_required',
|
|
1089
|
+
reviewStatus: certified ? 'certified' : 'review_required',
|
|
1090
|
+
};
|
|
1091
|
+
}
|
|
1092
|
+
function extractRecommendationColumns(schema, rows) {
|
|
1093
|
+
const rawColumns = Array.isArray(schema)
|
|
1094
|
+
? schema
|
|
1095
|
+
: schema && typeof schema === 'object' && Array.isArray(schema.columns)
|
|
1096
|
+
? schema.columns
|
|
1097
|
+
: [];
|
|
1098
|
+
const columns = rawColumns.flatMap((column) => {
|
|
1099
|
+
if (typeof column === 'string')
|
|
1100
|
+
return [{ name: column }];
|
|
1101
|
+
if (column && typeof column === 'object') {
|
|
1102
|
+
const record = column;
|
|
1103
|
+
const name = cleanString(record.name) || cleanString(record.field) || cleanString(record.id);
|
|
1104
|
+
if (!name)
|
|
1105
|
+
return [];
|
|
1106
|
+
const type = cleanString(record.type) || cleanString(record.dataType);
|
|
1107
|
+
return [{ name, ...(type ? { type } : {}) }];
|
|
1108
|
+
}
|
|
1109
|
+
return [];
|
|
1110
|
+
});
|
|
1111
|
+
if (columns.length > 0)
|
|
1112
|
+
return columns;
|
|
1113
|
+
const first = rows.find((row) => row && typeof row === 'object');
|
|
1114
|
+
return first ? Object.keys(first).map((name) => ({ name, type: typeof first[name] })) : [];
|
|
1115
|
+
}
|
|
1116
|
+
function recommendVizType(input) {
|
|
1117
|
+
const names = input.columns.map((column) => column.name.toLowerCase());
|
|
1118
|
+
const hasTime = names.some((name) => /\b(date|time|week|month|quarter|year|season|period)\b/.test(name));
|
|
1119
|
+
const measures = input.columns.filter((column) => isMeasureColumn(column, input.rows));
|
|
1120
|
+
const dimensions = input.columns.filter((column) => !isMeasureColumn(column, input.rows));
|
|
1121
|
+
if (/\bpivot|matrix|cross.?tab\b/.test(input.prompt))
|
|
1122
|
+
return 'pivot';
|
|
1123
|
+
if (/\btrend|over time|weekly|monthly|daily|quarterly\b/.test(input.prompt) || (hasTime && measures.length > 0))
|
|
1124
|
+
return 'line';
|
|
1125
|
+
if (/\btop|bottom|rank|ranking|leader|leaderboard|players?|scorers?\b/.test(input.prompt))
|
|
1126
|
+
return 'bar';
|
|
1127
|
+
if (measures.length === 1 && dimensions.length === 0)
|
|
1128
|
+
return 'single_value';
|
|
1129
|
+
if (dimensions.length > 0 && measures.length > 0)
|
|
1130
|
+
return 'bar';
|
|
1131
|
+
return normalizeVizType(input.blockChartType);
|
|
1132
|
+
}
|
|
1133
|
+
function isMeasureColumn(column, rows) {
|
|
1134
|
+
const type = (column.type ?? '').toLowerCase();
|
|
1135
|
+
if (/\b(number|numeric|decimal|double|float|integer|int|bigint|real)\b/.test(type))
|
|
1136
|
+
return true;
|
|
1137
|
+
const name = column.name.toLowerCase();
|
|
1138
|
+
if (/\b(count|sum|avg|average|total|revenue|arr|amount|rate|score|points?|goals?|rank|value)\b/.test(name))
|
|
1139
|
+
return true;
|
|
1140
|
+
const sample = rows.map((row) => row[column.name]).find((value) => value !== null && value !== undefined);
|
|
1141
|
+
return typeof sample === 'number';
|
|
1142
|
+
}
|
|
1143
|
+
function fieldHintsForColumns(columns, rows, prompt) {
|
|
1144
|
+
const hints = {};
|
|
1145
|
+
const measure = columns.find((column) => isMeasureColumn(column, rows));
|
|
1146
|
+
const time = columns.find((column) => /\b(date|time|week|month|quarter|year|season|period)\b/i.test(column.name));
|
|
1147
|
+
const rank = columns.find((column) => /\b(rank|position)\b/i.test(column.name));
|
|
1148
|
+
const label = columns.find((column) => !isMeasureColumn(column, rows) && /\b(name|player|customer|account|team|segment|region|category|label)\b/i.test(column.name)) ?? columns.find((column) => !isMeasureColumn(column, rows));
|
|
1149
|
+
if (label)
|
|
1150
|
+
hints.label = label.name;
|
|
1151
|
+
if (measure)
|
|
1152
|
+
hints.value = measure.name;
|
|
1153
|
+
if (time)
|
|
1154
|
+
hints.time = time.name;
|
|
1155
|
+
if (rank || /\brank|top|bottom\b/i.test(prompt))
|
|
1156
|
+
hints.rank = rank?.name ?? 'rank';
|
|
1157
|
+
if (time && measure) {
|
|
1158
|
+
hints.x = time.name;
|
|
1159
|
+
hints.y = measure.name;
|
|
1160
|
+
}
|
|
1161
|
+
else if (label && measure) {
|
|
1162
|
+
hints.x = label.name;
|
|
1163
|
+
hints.y = measure.name;
|
|
1164
|
+
}
|
|
1165
|
+
return hints;
|
|
1166
|
+
}
|
|
1167
|
+
function componentForViz(viz, text) {
|
|
1168
|
+
if (viz === 'single_value' || viz === 'kpi' || viz === 'gauge')
|
|
1169
|
+
return 'KpiMetric';
|
|
1170
|
+
if (viz === 'line' || viz === 'area')
|
|
1171
|
+
return 'TrendPanel';
|
|
1172
|
+
if (viz === 'pivot')
|
|
1173
|
+
return 'PivotTable';
|
|
1174
|
+
if (viz === 'text' || viz === 'heading')
|
|
1175
|
+
return /\btrust|evidence|caveat|quality\b/.test(text) ? 'TrustCallout' : 'NarrativePanel';
|
|
1176
|
+
if (/\btop|bottom|rank|ranking|leader|leaderboard|scorer|player\b/.test(text))
|
|
1177
|
+
return 'RankingPanel';
|
|
1178
|
+
if (viz === 'bar' || viz === 'grouped_bar' || viz === 'stacked_bar' || viz === 'pie' || viz === 'donut')
|
|
1179
|
+
return 'RankingPanel';
|
|
1180
|
+
return 'EvidenceTable';
|
|
1181
|
+
}
|
|
1182
|
+
function allowedVisualizationsForComponent(component, primary) {
|
|
1183
|
+
const base = new Set([primary]);
|
|
1184
|
+
for (const viz of compatibleVisualizationsForComponent(component))
|
|
1185
|
+
base.add(viz);
|
|
1186
|
+
return Array.from(base);
|
|
1187
|
+
}
|
|
1188
|
+
function compatibleVisualizationsForComponent(component) {
|
|
1189
|
+
if (component === 'KpiMetric') {
|
|
1190
|
+
return ['single_value', 'kpi', 'gauge', 'table'];
|
|
1191
|
+
}
|
|
1192
|
+
if (component === 'TrendPanel')
|
|
1193
|
+
return ['line', 'area', 'bar', 'table'];
|
|
1194
|
+
if (component === 'RankingPanel')
|
|
1195
|
+
return ['bar', 'grouped_bar', 'stacked_bar', 'pie', 'donut', 'table'];
|
|
1196
|
+
if (component === 'PivotTable')
|
|
1197
|
+
return ['pivot', 'table', 'bar'];
|
|
1198
|
+
if (component === 'EvidenceTable')
|
|
1199
|
+
return ['table', 'bar', 'scatter', 'heatmap', 'histogram', 'waterfall', 'map', 'funnel'];
|
|
1200
|
+
return ['text', 'heading'];
|
|
1201
|
+
}
|
|
1202
|
+
function componentVizCompatible(component, viz) {
|
|
1203
|
+
return compatibleVisualizationsForComponent(component).includes(viz);
|
|
1204
|
+
}
|
|
1205
|
+
function layoutIntentForComponent(component) {
|
|
1206
|
+
if (component === 'KpiMetric' || component === 'TrustCallout' || component === 'ResearchActions')
|
|
1207
|
+
return 'compact';
|
|
1208
|
+
if (component === 'TrendPanel' || component === 'RankingPanel')
|
|
1209
|
+
return 'wide';
|
|
1210
|
+
if (component === 'PivotTable' || component === 'EvidenceTable')
|
|
1211
|
+
return 'standard';
|
|
1212
|
+
if (component === 'BusinessBrief')
|
|
1213
|
+
return 'wide';
|
|
1214
|
+
return 'standard';
|
|
1215
|
+
}
|
|
1216
|
+
function recommendationRationale(component, viz, block, columns, prompt) {
|
|
1217
|
+
if (block) {
|
|
1218
|
+
return `Recommended ${component} with ${viz} from the block display hint and result shape. The certified block remains the business logic source.`;
|
|
1219
|
+
}
|
|
1220
|
+
if (columns.length > 0) {
|
|
1221
|
+
return `Recommended ${component} with ${viz} from ${columns.length} output column(s) and the app prompt. This is consumer-level metadata pending review.`;
|
|
1222
|
+
}
|
|
1223
|
+
return prompt
|
|
1224
|
+
? `Recommended ${component} with ${viz} from the app prompt. Review against previewed fields before sharing.`
|
|
1225
|
+
: `Recommended ${component} with ${viz} as a safe table-first presentation fallback.`;
|
|
1226
|
+
}
|
|
962
1227
|
function appReadme(app, audience, blocks, dashboardId = 'overview') {
|
|
963
1228
|
return [
|
|
964
1229
|
`# ${app.name}`,
|
|
@@ -989,18 +1254,19 @@ function appReadme(app, audience, blocks, dashboardId = 'overview') {
|
|
|
989
1254
|
].join('\n');
|
|
990
1255
|
}
|
|
991
1256
|
function collectBlockCandidates(projectRoot) {
|
|
992
|
-
const blocksDir = join(projectRoot, 'blocks');
|
|
993
1257
|
const blocks = [];
|
|
994
|
-
|
|
995
|
-
return blocks;
|
|
1258
|
+
const seen = new Set();
|
|
996
1259
|
const scanDir = (dir) => {
|
|
1260
|
+
if (!existsSync(dir))
|
|
1261
|
+
return;
|
|
997
1262
|
for (const entry of readdirSyncSafe(dir)) {
|
|
998
1263
|
const filePath = join(dir, entry.name);
|
|
999
1264
|
if (entry.isDirectory()) {
|
|
1000
1265
|
scanDir(filePath);
|
|
1001
1266
|
}
|
|
1002
|
-
else if (entry.isFile() && entry.name.endsWith('.dql')) {
|
|
1267
|
+
else if (entry.isFile() && entry.name.endsWith('.dql') && !seen.has(filePath)) {
|
|
1003
1268
|
try {
|
|
1269
|
+
seen.add(filePath);
|
|
1004
1270
|
const source = readFileSync(filePath, 'utf-8');
|
|
1005
1271
|
const stat = statSyncSafe(filePath);
|
|
1006
1272
|
const name = matchString(source, /block\s+"([^"]+)"/) ?? entry.name.replace(/\.dql$/, '');
|
|
@@ -1012,7 +1278,7 @@ function collectBlockCandidates(projectRoot) {
|
|
|
1012
1278
|
status: matchString(source, /status\s*=\s*"([^"]+)"/) ?? 'draft',
|
|
1013
1279
|
owner: matchString(source, /owner\s*=\s*"([^"]+)"/),
|
|
1014
1280
|
tags,
|
|
1015
|
-
path: filePath.
|
|
1281
|
+
path: relative(projectRoot, filePath).replaceAll('\\', '/'),
|
|
1016
1282
|
lastModified: stat?.mtime.toISOString() ?? new Date(0).toISOString(),
|
|
1017
1283
|
description: matchString(source, /description\s*=\s*"((?:[^"\\]|\\.)*)"/) ?? '',
|
|
1018
1284
|
llmContext: matchString(source, /llmContext\s*=\s*"((?:[^"\\]|\\.)*)"/),
|
|
@@ -1027,7 +1293,8 @@ function collectBlockCandidates(projectRoot) {
|
|
|
1027
1293
|
}
|
|
1028
1294
|
}
|
|
1029
1295
|
};
|
|
1030
|
-
scanDir(
|
|
1296
|
+
scanDir(join(projectRoot, 'blocks'));
|
|
1297
|
+
scanDir(join(projectRoot, 'domains'));
|
|
1031
1298
|
return blocks;
|
|
1032
1299
|
}
|
|
1033
1300
|
function readdirSyncSafe(dir) {
|
|
@@ -1902,6 +2169,14 @@ function slugify(value) {
|
|
|
1902
2169
|
.replace(/[^a-z0-9]+/g, '-')
|
|
1903
2170
|
.replace(/^-+|-+$/g, '');
|
|
1904
2171
|
}
|
|
2172
|
+
function resolveAppPackageDir(projectRoot, domain, id) {
|
|
2173
|
+
const domainSlug = slugify(domain);
|
|
2174
|
+
const domainDir = domainSlug ? join(projectRoot, 'domains', domainSlug) : '';
|
|
2175
|
+
if (domainDir && existsSync(domainDir)) {
|
|
2176
|
+
return join(domainDir, 'apps', id);
|
|
2177
|
+
}
|
|
2178
|
+
return join(projectRoot, 'apps', id);
|
|
2179
|
+
}
|
|
1905
2180
|
function titleFromPath(path) {
|
|
1906
2181
|
return basename(path)
|
|
1907
2182
|
.replace(/\.(dqlnb|dql)$/i, '')
|
|
@@ -1925,7 +2200,7 @@ function appAllowsExecute(app, domain) {
|
|
|
1925
2200
|
return policy.accessLevel === 'execute' || policy.accessLevel === 'admin';
|
|
1926
2201
|
});
|
|
1927
2202
|
}
|
|
1928
|
-
function createDashboardForApp(projectRoot, appId, input) {
|
|
2203
|
+
export function createDashboardForApp(projectRoot, appId, input) {
|
|
1929
2204
|
const loaded = loadAppById(projectRoot, appId);
|
|
1930
2205
|
if (!loaded)
|
|
1931
2206
|
return { ok: false, error: `App "${appId}" not found` };
|
|
@@ -1933,7 +2208,7 @@ function createDashboardForApp(projectRoot, appId, input) {
|
|
|
1933
2208
|
const id = slugify(cleanString(input.id) || title) || `page-${Date.now()}`;
|
|
1934
2209
|
if (!/^[a-z0-9][a-z0-9_-]*$/i.test(id))
|
|
1935
2210
|
return { ok: false, error: 'dashboard id must be folder-safe' };
|
|
1936
|
-
const appDir =
|
|
2211
|
+
const appDir = loaded.appDir;
|
|
1937
2212
|
const dashboardPath = join(appDir, 'dashboards', `${id}.dqld`);
|
|
1938
2213
|
if (existsSync(dashboardPath))
|
|
1939
2214
|
return { ok: false, error: `Dashboard already exists: ${id}` };
|
|
@@ -2010,11 +2285,13 @@ function createAiPinTile(projectRoot, appId, input) {
|
|
|
2010
2285
|
evidence: input.evidence,
|
|
2011
2286
|
followUps: Array.isArray(input.followUps) ? input.followUps.filter((item) => typeof item === 'string') : [],
|
|
2012
2287
|
});
|
|
2288
|
+
const vizType = normalizeVizTypeFromChart(input.chartConfig);
|
|
2013
2289
|
const tile = {
|
|
2014
2290
|
i: tileId,
|
|
2015
2291
|
...nextTilePosition(loaded.dashboard),
|
|
2016
2292
|
aiPin: { id: pin.id },
|
|
2017
|
-
viz: { type:
|
|
2293
|
+
viz: { type: vizType },
|
|
2294
|
+
display: displayForAiPin(title, vizType, input),
|
|
2018
2295
|
title,
|
|
2019
2296
|
};
|
|
2020
2297
|
const dashboard = {
|
|
@@ -2057,7 +2334,7 @@ function promoteAiPinToDraftBlock(projectRoot, appId, pinId) {
|
|
|
2057
2334
|
analysisPlan?.dimensions?.length ? `Dimensions: ${analysisPlan.dimensions.join(', ')}` : '',
|
|
2058
2335
|
analysisPlan?.measures?.length ? `Measures: ${analysisPlan.measures.join(', ')}` : '',
|
|
2059
2336
|
].filter(Boolean).join(' | ');
|
|
2060
|
-
const draftDir = join(
|
|
2337
|
+
const draftDir = join(loaded.appDir, 'drafts');
|
|
2061
2338
|
const blockPath = join(draftDir, `${blockName}.dql`);
|
|
2062
2339
|
mkdirSync(draftDir, { recursive: true });
|
|
2063
2340
|
const source = [
|
|
@@ -2138,6 +2415,8 @@ function loadAppById(projectRoot, id) {
|
|
|
2138
2415
|
}
|
|
2139
2416
|
return {
|
|
2140
2417
|
app: document,
|
|
2418
|
+
appDir,
|
|
2419
|
+
appPath: relative(projectRoot, appDir),
|
|
2141
2420
|
dashboards,
|
|
2142
2421
|
notebooks: listAppNotebookRefs(projectRoot, document, appDir),
|
|
2143
2422
|
drafts: listAppDrafts(projectRoot, appDir),
|
|
@@ -2182,8 +2461,8 @@ export function createNotebookForApp(projectRoot, appId, input) {
|
|
|
2182
2461
|
return { ok: false, error: `App "${appId}" not found` };
|
|
2183
2462
|
const title = cleanString(input.title) || cleanString(input.name) || 'App analysis';
|
|
2184
2463
|
const slug = slugify(cleanString(input.name) || title) || `notebook-${Date.now()}`;
|
|
2185
|
-
const appDir =
|
|
2186
|
-
const relPath =
|
|
2464
|
+
const appDir = loaded.appDir;
|
|
2465
|
+
const relPath = relative(projectRoot, join(appDir, 'notebooks', `${slug}.dqlnb`)).replaceAll('\\', '/');
|
|
2187
2466
|
const absPath = join(projectRoot, relPath);
|
|
2188
2467
|
if (existsSync(absPath))
|
|
2189
2468
|
return { ok: false, error: `Notebook already exists: ${relPath}` };
|
|
@@ -2488,10 +2767,10 @@ function writeDashboard(projectRoot, appId, dashboardId, payload) {
|
|
|
2488
2767
|
return { ok: false, error: `dashboard.id (${document.id}) does not match URL :did (${dashboardId})` };
|
|
2489
2768
|
}
|
|
2490
2769
|
// Confirm the App exists.
|
|
2491
|
-
const
|
|
2492
|
-
if (!
|
|
2493
|
-
return { ok: false, error: `App "${appId}" not found
|
|
2494
|
-
|
|
2770
|
+
const loaded = loadAppById(projectRoot, appId);
|
|
2771
|
+
if (!loaded)
|
|
2772
|
+
return { ok: false, error: `App "${appId}" not found` };
|
|
2773
|
+
const appDir = loaded.appDir;
|
|
2495
2774
|
const dashboardPath = join(appDir, 'dashboards', `${dashboardId}.dqld`);
|
|
2496
2775
|
mkdirSync(dirname(dashboardPath), { recursive: true });
|
|
2497
2776
|
writeFileSync(dashboardPath, JSON.stringify(document, null, 2) + '\n', 'utf-8');
|