@miao-vision/cli 0.1.8 → 0.1.9
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/cli.cjs +1123 -98
- package/dist/types.ts +10 -1
- package/package.json +1 -1
package/dist/cli.cjs
CHANGED
|
@@ -34716,7 +34716,7 @@ var require_stringify = __commonJS({
|
|
|
34716
34716
|
props.push(doc.directives.tagString(tag));
|
|
34717
34717
|
return props.join(" ");
|
|
34718
34718
|
}
|
|
34719
|
-
function
|
|
34719
|
+
function stringify3(item, ctx, onComment, onChompKeep) {
|
|
34720
34720
|
if (identity.isPair(item))
|
|
34721
34721
|
return item.toString(ctx, onComment, onChompKeep);
|
|
34722
34722
|
if (identity.isAlias(item)) {
|
|
@@ -34745,7 +34745,7 @@ var require_stringify = __commonJS({
|
|
|
34745
34745
|
${ctx.indent}${str}`;
|
|
34746
34746
|
}
|
|
34747
34747
|
exports2.createStringifyContext = createStringifyContext;
|
|
34748
|
-
exports2.stringify =
|
|
34748
|
+
exports2.stringify = stringify3;
|
|
34749
34749
|
}
|
|
34750
34750
|
});
|
|
34751
34751
|
|
|
@@ -34755,7 +34755,7 @@ var require_stringifyPair = __commonJS({
|
|
|
34755
34755
|
"use strict";
|
|
34756
34756
|
var identity = require_identity();
|
|
34757
34757
|
var Scalar = require_Scalar();
|
|
34758
|
-
var
|
|
34758
|
+
var stringify3 = require_stringify();
|
|
34759
34759
|
var stringifyComment = require_stringifyComment();
|
|
34760
34760
|
function stringifyPair({ key, value }, ctx, onComment, onChompKeep) {
|
|
34761
34761
|
const { allNullValues, doc, indent, indentStep, options: { commentString, indentSeq, simpleKeys } } = ctx;
|
|
@@ -34777,7 +34777,7 @@ var require_stringifyPair = __commonJS({
|
|
|
34777
34777
|
});
|
|
34778
34778
|
let keyCommentDone = false;
|
|
34779
34779
|
let chompKeep = false;
|
|
34780
|
-
let str =
|
|
34780
|
+
let str = stringify3.stringify(key, ctx, () => keyCommentDone = true, () => chompKeep = true);
|
|
34781
34781
|
if (!explicitKey && !ctx.inFlow && str.length > 1024) {
|
|
34782
34782
|
if (simpleKeys)
|
|
34783
34783
|
throw new Error("With simple keys, single line scalar must not span more than 1024 characters");
|
|
@@ -34829,7 +34829,7 @@ ${indent}:`;
|
|
|
34829
34829
|
ctx.indent = ctx.indent.substring(2);
|
|
34830
34830
|
}
|
|
34831
34831
|
let valueCommentDone = false;
|
|
34832
|
-
const valueStr =
|
|
34832
|
+
const valueStr = stringify3.stringify(value, ctx, () => valueCommentDone = true, () => chompKeep = true);
|
|
34833
34833
|
let ws = " ";
|
|
34834
34834
|
if (keyComment || vsb || vcb) {
|
|
34835
34835
|
ws = vsb ? "\n" : "";
|
|
@@ -34967,7 +34967,7 @@ var require_addPairToJSMap = __commonJS({
|
|
|
34967
34967
|
"use strict";
|
|
34968
34968
|
var log = require_log();
|
|
34969
34969
|
var merge2 = require_merge();
|
|
34970
|
-
var
|
|
34970
|
+
var stringify3 = require_stringify();
|
|
34971
34971
|
var identity = require_identity();
|
|
34972
34972
|
var toJS = require_toJS();
|
|
34973
34973
|
function addPairToJSMap(ctx, map2, { key, value }) {
|
|
@@ -35003,7 +35003,7 @@ var require_addPairToJSMap = __commonJS({
|
|
|
35003
35003
|
if (typeof jsKey !== "object")
|
|
35004
35004
|
return String(jsKey);
|
|
35005
35005
|
if (identity.isNode(key) && ctx?.doc) {
|
|
35006
|
-
const strCtx =
|
|
35006
|
+
const strCtx = stringify3.createStringifyContext(ctx.doc, {});
|
|
35007
35007
|
strCtx.anchors = /* @__PURE__ */ new Set();
|
|
35008
35008
|
for (const node of ctx.anchors.keys())
|
|
35009
35009
|
strCtx.anchors.add(node.anchor);
|
|
@@ -35070,12 +35070,12 @@ var require_stringifyCollection = __commonJS({
|
|
|
35070
35070
|
"../../node_modules/yaml/dist/stringify/stringifyCollection.js"(exports2) {
|
|
35071
35071
|
"use strict";
|
|
35072
35072
|
var identity = require_identity();
|
|
35073
|
-
var
|
|
35073
|
+
var stringify3 = require_stringify();
|
|
35074
35074
|
var stringifyComment = require_stringifyComment();
|
|
35075
35075
|
function stringifyCollection(collection, ctx, options) {
|
|
35076
35076
|
const flow = ctx.inFlow ?? collection.flow;
|
|
35077
|
-
const
|
|
35078
|
-
return
|
|
35077
|
+
const stringify4 = flow ? stringifyFlowCollection : stringifyBlockCollection;
|
|
35078
|
+
return stringify4(collection, ctx, options);
|
|
35079
35079
|
}
|
|
35080
35080
|
function stringifyBlockCollection({ comment, items }, ctx, { blockItemPrefix, flowChars, itemIndent, onChompKeep, onComment }) {
|
|
35081
35081
|
const { indent, options: { commentString } } = ctx;
|
|
@@ -35100,7 +35100,7 @@ var require_stringifyCollection = __commonJS({
|
|
|
35100
35100
|
}
|
|
35101
35101
|
}
|
|
35102
35102
|
chompKeep = false;
|
|
35103
|
-
let str2 =
|
|
35103
|
+
let str2 = stringify3.stringify(item, itemCtx, () => comment2 = null, () => chompKeep = true);
|
|
35104
35104
|
if (comment2)
|
|
35105
35105
|
str2 += stringifyComment.lineComment(str2, itemIndent, commentString(comment2));
|
|
35106
35106
|
if (chompKeep && comment2)
|
|
@@ -35167,7 +35167,7 @@ ${indent}${line}` : "\n";
|
|
|
35167
35167
|
}
|
|
35168
35168
|
if (comment)
|
|
35169
35169
|
reqNewline = true;
|
|
35170
|
-
let str =
|
|
35170
|
+
let str = stringify3.stringify(item, itemCtx, () => comment = null);
|
|
35171
35171
|
if (i < items.length - 1)
|
|
35172
35172
|
str += ",";
|
|
35173
35173
|
if (comment)
|
|
@@ -36521,7 +36521,7 @@ var require_stringifyDocument = __commonJS({
|
|
|
36521
36521
|
"../../node_modules/yaml/dist/stringify/stringifyDocument.js"(exports2) {
|
|
36522
36522
|
"use strict";
|
|
36523
36523
|
var identity = require_identity();
|
|
36524
|
-
var
|
|
36524
|
+
var stringify3 = require_stringify();
|
|
36525
36525
|
var stringifyComment = require_stringifyComment();
|
|
36526
36526
|
function stringifyDocument(doc, options) {
|
|
36527
36527
|
const lines = [];
|
|
@@ -36536,7 +36536,7 @@ var require_stringifyDocument = __commonJS({
|
|
|
36536
36536
|
}
|
|
36537
36537
|
if (hasDirectives)
|
|
36538
36538
|
lines.push("---");
|
|
36539
|
-
const ctx =
|
|
36539
|
+
const ctx = stringify3.createStringifyContext(doc, options);
|
|
36540
36540
|
const { commentString } = ctx.options;
|
|
36541
36541
|
if (doc.commentBefore) {
|
|
36542
36542
|
if (lines.length !== 1)
|
|
@@ -36558,7 +36558,7 @@ var require_stringifyDocument = __commonJS({
|
|
|
36558
36558
|
contentComment = doc.contents.comment;
|
|
36559
36559
|
}
|
|
36560
36560
|
const onChompKeep = contentComment ? void 0 : () => chompKeep = true;
|
|
36561
|
-
let body =
|
|
36561
|
+
let body = stringify3.stringify(doc.contents, ctx, () => contentComment = null, onChompKeep);
|
|
36562
36562
|
if (contentComment)
|
|
36563
36563
|
body += stringifyComment.lineComment(body, "", commentString(contentComment));
|
|
36564
36564
|
if ((body[0] === "|" || body[0] === ">") && lines[lines.length - 1] === "---") {
|
|
@@ -36566,7 +36566,7 @@ var require_stringifyDocument = __commonJS({
|
|
|
36566
36566
|
} else
|
|
36567
36567
|
lines.push(body);
|
|
36568
36568
|
} else {
|
|
36569
|
-
lines.push(
|
|
36569
|
+
lines.push(stringify3.stringify(doc.contents, ctx));
|
|
36570
36570
|
}
|
|
36571
36571
|
if (doc.directives?.docEnd) {
|
|
36572
36572
|
if (doc.comment) {
|
|
@@ -38693,7 +38693,7 @@ var require_cst_scalar = __commonJS({
|
|
|
38693
38693
|
var require_cst_stringify = __commonJS({
|
|
38694
38694
|
"../../node_modules/yaml/dist/parse/cst-stringify.js"(exports2) {
|
|
38695
38695
|
"use strict";
|
|
38696
|
-
var
|
|
38696
|
+
var stringify3 = (cst) => "type" in cst ? stringifyToken(cst) : stringifyItem(cst);
|
|
38697
38697
|
function stringifyToken(token) {
|
|
38698
38698
|
switch (token.type) {
|
|
38699
38699
|
case "block-scalar": {
|
|
@@ -38746,7 +38746,7 @@ var require_cst_stringify = __commonJS({
|
|
|
38746
38746
|
res += stringifyToken(value);
|
|
38747
38747
|
return res;
|
|
38748
38748
|
}
|
|
38749
|
-
exports2.stringify =
|
|
38749
|
+
exports2.stringify = stringify3;
|
|
38750
38750
|
}
|
|
38751
38751
|
});
|
|
38752
38752
|
|
|
@@ -40459,7 +40459,7 @@ var require_public_api = __commonJS({
|
|
|
40459
40459
|
}
|
|
40460
40460
|
return doc.toJS(Object.assign({ reviver: _reviver }, options));
|
|
40461
40461
|
}
|
|
40462
|
-
function
|
|
40462
|
+
function stringify3(value, replacer, options) {
|
|
40463
40463
|
let _replacer = null;
|
|
40464
40464
|
if (typeof replacer === "function" || Array.isArray(replacer)) {
|
|
40465
40465
|
_replacer = replacer;
|
|
@@ -40484,7 +40484,7 @@ var require_public_api = __commonJS({
|
|
|
40484
40484
|
exports2.parse = parse4;
|
|
40485
40485
|
exports2.parseAllDocuments = parseAllDocuments;
|
|
40486
40486
|
exports2.parseDocument = parseDocument;
|
|
40487
|
-
exports2.stringify =
|
|
40487
|
+
exports2.stringify = stringify3;
|
|
40488
40488
|
}
|
|
40489
40489
|
});
|
|
40490
40490
|
|
|
@@ -41204,8 +41204,79 @@ function prepareChartData(rows, chart) {
|
|
|
41204
41204
|
for (const transform2 of chart.data?.transform ?? []) {
|
|
41205
41205
|
current = applyTransform(current, transform2);
|
|
41206
41206
|
}
|
|
41207
|
+
current = applyEncodingAggregates(current, chart);
|
|
41207
41208
|
return current;
|
|
41208
41209
|
}
|
|
41210
|
+
function inspectChartTransforms(rows, chart) {
|
|
41211
|
+
let current = [...rows];
|
|
41212
|
+
const transforms = [];
|
|
41213
|
+
let step = 1;
|
|
41214
|
+
for (const transform2 of chart.data?.transform ?? []) {
|
|
41215
|
+
const inputRows = current.length;
|
|
41216
|
+
current = applyTransform(current, transform2);
|
|
41217
|
+
transforms.push({
|
|
41218
|
+
step: step++,
|
|
41219
|
+
type: transform2.type,
|
|
41220
|
+
inputRows,
|
|
41221
|
+
outputRows: current.length,
|
|
41222
|
+
preview: current.slice(0, 3)
|
|
41223
|
+
});
|
|
41224
|
+
}
|
|
41225
|
+
const beforeEncoding = current;
|
|
41226
|
+
current = applyEncodingAggregates(current, chart);
|
|
41227
|
+
if (current !== beforeEncoding) {
|
|
41228
|
+
transforms.push({
|
|
41229
|
+
step,
|
|
41230
|
+
type: "encoding-aggregate",
|
|
41231
|
+
inputRows: beforeEncoding.length,
|
|
41232
|
+
outputRows: current.length,
|
|
41233
|
+
preview: current.slice(0, 3)
|
|
41234
|
+
});
|
|
41235
|
+
}
|
|
41236
|
+
return { rows: current, transforms };
|
|
41237
|
+
}
|
|
41238
|
+
function applyEncodingAggregates(rows, chart) {
|
|
41239
|
+
const enc = chart.encoding;
|
|
41240
|
+
if (!enc) return rows;
|
|
41241
|
+
if (chart.type === "bigvalue") {
|
|
41242
|
+
const valueEnc = enc.value;
|
|
41243
|
+
if (valueEnc?.field && valueEnc.aggregate) {
|
|
41244
|
+
const result = aggregateMeasure(rows, valueEnc.field, valueEnc.aggregate);
|
|
41245
|
+
return [{ [valueEnc.field]: result }];
|
|
41246
|
+
}
|
|
41247
|
+
return rows;
|
|
41248
|
+
}
|
|
41249
|
+
if (chart.type === "bar" || chart.type === "line" || chart.type === "area") {
|
|
41250
|
+
const xField = enc.x?.field;
|
|
41251
|
+
const yEnc = enc.y;
|
|
41252
|
+
if (xField && yEnc?.field && yEnc.aggregate) {
|
|
41253
|
+
return groupByAndAggregate(rows, xField, yEnc.field, yEnc.aggregate);
|
|
41254
|
+
}
|
|
41255
|
+
return rows;
|
|
41256
|
+
}
|
|
41257
|
+
if (chart.type === "pie") {
|
|
41258
|
+
const labelField = enc.label?.field;
|
|
41259
|
+
const valueEnc = enc.value;
|
|
41260
|
+
if (labelField && valueEnc?.field && valueEnc.aggregate) {
|
|
41261
|
+
return groupByAndAggregate(rows, labelField, valueEnc.field, valueEnc.aggregate);
|
|
41262
|
+
}
|
|
41263
|
+
return rows;
|
|
41264
|
+
}
|
|
41265
|
+
return rows;
|
|
41266
|
+
}
|
|
41267
|
+
function groupByAndAggregate(rows, groupField, valueField, op) {
|
|
41268
|
+
const groups = /* @__PURE__ */ new Map();
|
|
41269
|
+
for (const row of rows) {
|
|
41270
|
+
const key = row[groupField];
|
|
41271
|
+
const group = groups.get(key) ?? [];
|
|
41272
|
+
group.push(row);
|
|
41273
|
+
groups.set(key, group);
|
|
41274
|
+
}
|
|
41275
|
+
return [...groups.entries()].map(([key, groupRows]) => ({
|
|
41276
|
+
[groupField]: key,
|
|
41277
|
+
[valueField]: aggregateMeasure(groupRows, valueField, op)
|
|
41278
|
+
}));
|
|
41279
|
+
}
|
|
41209
41280
|
function applyTransform(rows, transform2) {
|
|
41210
41281
|
if (transform2.type === "derive-month" && transform2.field && transform2.as) {
|
|
41211
41282
|
return rows.map((row) => ({
|
|
@@ -41292,11 +41363,14 @@ function renderChartSvg(chart, rows, svgTheme, options = {}) {
|
|
|
41292
41363
|
if (chart.type === "pie") return renderPieChart(chart, data, theme, options);
|
|
41293
41364
|
if (chart.type === "table") return renderTable(chart, data, options);
|
|
41294
41365
|
if (chart.type === "bigvalue") return renderBigValue(chart, data);
|
|
41366
|
+
if (chart.type === "histogram") return renderHistogramChart(chart, data, theme, options);
|
|
41367
|
+
if (chart.type === "scatter") return renderScatterChart(chart, data, theme, options);
|
|
41368
|
+
if (chart.type === "heatmap") return renderHeatmapChart(chart, data, theme, options);
|
|
41295
41369
|
return renderUnsupported(chart);
|
|
41296
41370
|
}
|
|
41297
41371
|
function renderBarChart(chart, rows, theme, options) {
|
|
41298
|
-
const xField = chart.encoding
|
|
41299
|
-
const yField = chart.encoding
|
|
41372
|
+
const xField = chart.encoding?.x?.field ?? "";
|
|
41373
|
+
const yField = chart.encoding?.y?.field ?? "";
|
|
41300
41374
|
const width = numberStyle(chart, "width", 720);
|
|
41301
41375
|
const height = numberStyle(chart, "height", 420);
|
|
41302
41376
|
const margin = { top: 24, right: 24, bottom: 48, left: 72 };
|
|
@@ -41326,8 +41400,8 @@ function renderBarChart(chart, rows, theme, options) {
|
|
|
41326
41400
|
`);
|
|
41327
41401
|
}
|
|
41328
41402
|
function renderLineChart(chart, rows, theme, options) {
|
|
41329
|
-
const xField = chart.encoding
|
|
41330
|
-
const yField = chart.encoding
|
|
41403
|
+
const xField = chart.encoding?.x?.field ?? "";
|
|
41404
|
+
const yField = chart.encoding?.y?.field ?? "";
|
|
41331
41405
|
const width = numberStyle(chart, "width", 720);
|
|
41332
41406
|
const height = numberStyle(chart, "height", 420);
|
|
41333
41407
|
const margin = { top: 24, right: 24, bottom: 64, left: 72 };
|
|
@@ -41359,8 +41433,8 @@ function renderLineChart(chart, rows, theme, options) {
|
|
|
41359
41433
|
`);
|
|
41360
41434
|
}
|
|
41361
41435
|
function renderPieChart(chart, rows, theme, options) {
|
|
41362
|
-
const labelField = chart.encoding
|
|
41363
|
-
const valueField = chart.encoding
|
|
41436
|
+
const labelField = chart.encoding?.label?.field ?? "";
|
|
41437
|
+
const valueField = chart.encoding?.value?.field ?? "";
|
|
41364
41438
|
const width = numberStyle(chart, "width", 720);
|
|
41365
41439
|
const height = numberStyle(chart, "height", 420);
|
|
41366
41440
|
const cx = width / 2 - 80;
|
|
@@ -41389,19 +41463,182 @@ function renderPieChart(chart, rows, theme, options) {
|
|
|
41389
41463
|
return svgFrame(width, height, theme.background, `${slices}${legend}`);
|
|
41390
41464
|
}
|
|
41391
41465
|
function renderTable(chart, rows, options) {
|
|
41392
|
-
const columns = Object.keys(rows[0] ?? {})
|
|
41466
|
+
const columns = Object.keys(rows[0] ?? {});
|
|
41393
41467
|
const header = columns.map((c) => `<th>${escapeHtml(c)}</th>`).join("");
|
|
41394
41468
|
const markField = chart.encoding?.label?.field ?? chart.encoding?.x?.field ?? columns[0] ?? "";
|
|
41395
41469
|
const body = rows.slice(0, 20).map(
|
|
41396
|
-
(row) => `<tr ${markAttrs(options.chartId, markField, row[markField],
|
|
41470
|
+
(row, i) => `<tr ${markAttrs(options.chartId, markField, row[markField], i, String(row[markField] ?? "Row"))}>${columns.map((c) => `<td>${escapeHtml(String(row[c] ?? ""))}</td>`).join("")}</tr>`
|
|
41397
41471
|
).join("");
|
|
41398
41472
|
return `<div class="miao-table-wrap"><table class="miao-table"><caption>${escapeHtml(chart.title ?? "Table")}</caption><thead><tr>${header}</tr></thead><tbody>${body}</tbody></table></div>`;
|
|
41399
41473
|
}
|
|
41400
41474
|
function renderBigValue(chart, rows) {
|
|
41401
|
-
const valueField = chart.encoding
|
|
41475
|
+
const valueField = chart.encoding?.value?.field ?? "";
|
|
41402
41476
|
const value = rows[0]?.[valueField] ?? "";
|
|
41403
41477
|
return `<div class="miao-bigvalue"><div class="miao-bigvalue-label">${escapeHtml(chart.title ?? valueField)}</div><div class="miao-bigvalue-number">${escapeHtml(String(value))}</div></div>`;
|
|
41404
41478
|
}
|
|
41479
|
+
function renderHistogramChart(chart, rows, theme, options) {
|
|
41480
|
+
const xField = chart.encoding?.x?.field ?? "";
|
|
41481
|
+
const width = numberStyle(chart, "width", 720);
|
|
41482
|
+
const height = numberStyle(chart, "height", 420);
|
|
41483
|
+
const margin = { top: 24, right: 24, bottom: 56, left: 72 };
|
|
41484
|
+
const chartWidth = width - margin.left - margin.right;
|
|
41485
|
+
const chartHeight = height - margin.top - margin.bottom;
|
|
41486
|
+
const values = rows.map((row) => Number(row[xField])).filter(Number.isFinite);
|
|
41487
|
+
if (values.length === 0) return renderUnsupported(chart);
|
|
41488
|
+
const bucketCount = 8;
|
|
41489
|
+
const xMin = Math.min(...values);
|
|
41490
|
+
const xMax = Math.max(...values);
|
|
41491
|
+
const bucketSpan = xMax - xMin || 1;
|
|
41492
|
+
const bucketWidth = bucketSpan / bucketCount;
|
|
41493
|
+
const counts = Array(bucketCount).fill(0);
|
|
41494
|
+
for (const v of values) {
|
|
41495
|
+
counts[Math.min(Math.floor((v - xMin) / bucketWidth), bucketCount - 1)]++;
|
|
41496
|
+
}
|
|
41497
|
+
const yMax = Math.max(...counts, 1);
|
|
41498
|
+
const barGap = 2;
|
|
41499
|
+
const barW = (chartWidth - barGap * (bucketCount - 1)) / bucketCount;
|
|
41500
|
+
const color = theme.palette[0];
|
|
41501
|
+
const x0 = margin.left;
|
|
41502
|
+
const y0 = margin.top + chartHeight;
|
|
41503
|
+
const bars = counts.map((count, i) => {
|
|
41504
|
+
const barH = count / yMax * chartHeight;
|
|
41505
|
+
const x = margin.left + i * (barW + barGap);
|
|
41506
|
+
const y = margin.top + chartHeight - barH;
|
|
41507
|
+
const lo = xMin + i * bucketWidth;
|
|
41508
|
+
const tooltip = `${formatTick(lo)}\u2013${formatTick(lo + bucketWidth)}: ${count}`;
|
|
41509
|
+
return `<rect ${markAttrs(options.chartId, xField, lo, i, tooltip)} x="${x.toFixed(1)}" y="${y.toFixed(1)}" width="${barW.toFixed(1)}" height="${barH.toFixed(1)}" fill="${color}" />`;
|
|
41510
|
+
}).join("");
|
|
41511
|
+
const xTickPositions = [0, 2, 4, 6, 8];
|
|
41512
|
+
const xTicks = xTickPositions.map((i) => {
|
|
41513
|
+
const val = xMin + i * bucketWidth;
|
|
41514
|
+
const x = margin.left + i * (barW + barGap);
|
|
41515
|
+
return `<text x="${x.toFixed(1)}" y="${(y0 + 18).toFixed(1)}" text-anchor="middle" fill="${theme.labelColor}" font-size="10">${escapeHtml(formatTick(val))}</text>`;
|
|
41516
|
+
}).join("");
|
|
41517
|
+
const tickCount = 4;
|
|
41518
|
+
const yTicks = Array.from({ length: tickCount + 1 }, (_, i) => ({
|
|
41519
|
+
count: Math.round(i / tickCount * yMax),
|
|
41520
|
+
y: margin.top + chartHeight - i / tickCount * chartHeight
|
|
41521
|
+
}));
|
|
41522
|
+
const gridLines = yTicks.filter((_, i) => i > 0).map(
|
|
41523
|
+
(t) => `<line x1="${x0}" y1="${t.y.toFixed(1)}" x2="${x0 + chartWidth}" y2="${t.y.toFixed(1)}" stroke="${theme.axisColor}" stroke-opacity="0.4" stroke-dasharray="4 3" />`
|
|
41524
|
+
).join("");
|
|
41525
|
+
const yTickLabels = yTicks.map(
|
|
41526
|
+
(t) => `<text x="${(x0 - 6).toFixed(1)}" y="${(t.y + 4).toFixed(1)}" text-anchor="end" fill="${theme.labelColor}" font-size="11">${t.count}</text>`
|
|
41527
|
+
).join("");
|
|
41528
|
+
return svgFrame(width, height, theme.background, `
|
|
41529
|
+
${gridLines}
|
|
41530
|
+
<line x1="${x0}" y1="${y0}" x2="${(x0 + chartWidth).toFixed(1)}" y2="${y0}" stroke="${theme.axisColor}" />
|
|
41531
|
+
<line x1="${x0}" y1="${margin.top}" x2="${x0}" y2="${y0}" stroke="${theme.axisColor}" />
|
|
41532
|
+
${yTickLabels}
|
|
41533
|
+
<text x="${(x0 + chartWidth / 2).toFixed(1)}" y="${(y0 + 50).toFixed(1)}" text-anchor="middle" fill="${theme.labelColor}" font-size="12">${escapeHtml(xField)}</text>
|
|
41534
|
+
<text x="14" y="${(margin.top + chartHeight / 2).toFixed(1)}" text-anchor="middle" transform="rotate(-90 14 ${(margin.top + chartHeight / 2).toFixed(1)})" fill="${theme.labelColor}" font-size="12">count</text>
|
|
41535
|
+
${bars}
|
|
41536
|
+
${xTicks}
|
|
41537
|
+
`);
|
|
41538
|
+
}
|
|
41539
|
+
function renderScatterChart(chart, rows, theme, options) {
|
|
41540
|
+
const xField = chart.encoding?.x?.field ?? "";
|
|
41541
|
+
const yField = chart.encoding?.y?.field ?? "";
|
|
41542
|
+
const labelField = chart.encoding?.label?.field ?? "";
|
|
41543
|
+
const width = numberStyle(chart, "width", 720);
|
|
41544
|
+
const height = numberStyle(chart, "height", 420);
|
|
41545
|
+
const margin = { top: 24, right: 24, bottom: 56, left: 72 };
|
|
41546
|
+
const chartWidth = width - margin.left - margin.right;
|
|
41547
|
+
const chartHeight = height - margin.top - margin.bottom;
|
|
41548
|
+
const MAX_POINTS = 400;
|
|
41549
|
+
const sample = rows.length > MAX_POINTS ? rows.filter((_, i) => i % Math.ceil(rows.length / MAX_POINTS) === 0) : rows;
|
|
41550
|
+
const xValues = sample.map((row) => Number(row[xField])).filter(Number.isFinite);
|
|
41551
|
+
const yValues = sample.map((row) => Number(row[yField])).filter(Number.isFinite);
|
|
41552
|
+
if (xValues.length === 0 || yValues.length === 0) return renderUnsupported(chart);
|
|
41553
|
+
const xMin = Math.min(...xValues);
|
|
41554
|
+
const xMax = Math.max(...xValues);
|
|
41555
|
+
const yMin = Math.min(...yValues);
|
|
41556
|
+
const yMax = Math.max(...yValues);
|
|
41557
|
+
const xSpan = xMax - xMin || 1;
|
|
41558
|
+
const ySpan = yMax - yMin || 1;
|
|
41559
|
+
const color = theme.palette[0];
|
|
41560
|
+
const x0 = margin.left;
|
|
41561
|
+
const y0 = margin.top + chartHeight;
|
|
41562
|
+
const dots = sample.map((row, i) => {
|
|
41563
|
+
const xVal = Number(row[xField]);
|
|
41564
|
+
const yVal = Number(row[yField]);
|
|
41565
|
+
if (!Number.isFinite(xVal) || !Number.isFinite(yVal)) return "";
|
|
41566
|
+
const cx = margin.left + (xVal - xMin) / xSpan * chartWidth;
|
|
41567
|
+
const cy = margin.top + chartHeight - (yVal - yMin) / ySpan * chartHeight;
|
|
41568
|
+
const label = labelField ? String(row[labelField] ?? "") : `${formatTick(xVal)}, ${formatTick(yVal)}`;
|
|
41569
|
+
const tooltip = labelField ? `${label}: (${formatTick(xVal)}, ${formatTick(yVal)})` : `(${formatTick(xVal)}, ${formatTick(yVal)})`;
|
|
41570
|
+
return `<circle ${markAttrs(options.chartId, xField, xVal, i, tooltip)} cx="${cx.toFixed(1)}" cy="${cy.toFixed(1)}" r="4" fill="${color}" fill-opacity="0.55" />`;
|
|
41571
|
+
}).join("");
|
|
41572
|
+
const sampledNote = rows.length > MAX_POINTS ? `<text x="${(x0 + chartWidth).toFixed(1)}" y="${margin.top - 6}" text-anchor="end" fill="${theme.labelColor}" font-size="10">sampled ${MAX_POINTS} of ${rows.length} rows</text>` : "";
|
|
41573
|
+
const xTickCount = 5;
|
|
41574
|
+
const xTicks = Array.from({ length: xTickCount }, (_, i) => {
|
|
41575
|
+
const val = xMin + i / (xTickCount - 1) * xSpan;
|
|
41576
|
+
const x = margin.left + i / (xTickCount - 1) * chartWidth;
|
|
41577
|
+
return `<text x="${x.toFixed(1)}" y="${(y0 + 18).toFixed(1)}" text-anchor="middle" fill="${theme.labelColor}" font-size="10">${escapeHtml(formatTick(val))}</text>`;
|
|
41578
|
+
}).join("");
|
|
41579
|
+
return svgFrame(width, height, theme.background, `
|
|
41580
|
+
${buildAxis(margin, chartWidth, chartHeight, xField, yField, yMin, yMax, theme)}
|
|
41581
|
+
${dots}
|
|
41582
|
+
${xTicks}
|
|
41583
|
+
${sampledNote}
|
|
41584
|
+
`);
|
|
41585
|
+
}
|
|
41586
|
+
function renderHeatmapChart(chart, rows, theme, options) {
|
|
41587
|
+
const xField = chart.encoding?.x?.field ?? "";
|
|
41588
|
+
const yField = chart.encoding?.y?.field ?? "";
|
|
41589
|
+
const valueField = chart.encoding?.value?.field ?? "";
|
|
41590
|
+
const width = numberStyle(chart, "width", 720);
|
|
41591
|
+
const height = numberStyle(chart, "height", 420);
|
|
41592
|
+
const margin = { top: 24, right: 24, bottom: 64, left: 80 };
|
|
41593
|
+
const chartWidth = width - margin.left - margin.right;
|
|
41594
|
+
const chartHeight = height - margin.top - margin.bottom;
|
|
41595
|
+
const xValues = [...new Set(rows.map((row) => String(row[xField] ?? "")))];
|
|
41596
|
+
const yValues = [...new Set(rows.map((row) => String(row[yField] ?? "")))];
|
|
41597
|
+
if (xValues.length === 0 || yValues.length === 0) return renderUnsupported(chart);
|
|
41598
|
+
const cellMap = /* @__PURE__ */ new Map();
|
|
41599
|
+
for (const row of rows) {
|
|
41600
|
+
cellMap.set(`${row[xField]}|${row[yField]}`, Number(row[valueField]) || 0);
|
|
41601
|
+
}
|
|
41602
|
+
const allValues = [...cellMap.values()];
|
|
41603
|
+
const minVal = Math.min(...allValues);
|
|
41604
|
+
const maxVal = Math.max(...allValues, minVal + 1);
|
|
41605
|
+
const span = maxVal - minVal;
|
|
41606
|
+
const cellW = chartWidth / xValues.length;
|
|
41607
|
+
const cellH = chartHeight / yValues.length;
|
|
41608
|
+
const color = theme.palette[0];
|
|
41609
|
+
const x0 = margin.left;
|
|
41610
|
+
const y0 = margin.top + chartHeight;
|
|
41611
|
+
const cells = yValues.flatMap(
|
|
41612
|
+
(yVal, yi) => xValues.map((xVal, xi) => {
|
|
41613
|
+
const val = cellMap.get(`${xVal}|${yVal}`) ?? 0;
|
|
41614
|
+
const opacity = (0.1 + (val - minVal) / span * 0.85).toFixed(2);
|
|
41615
|
+
const x = margin.left + xi * cellW;
|
|
41616
|
+
const y = margin.top + yi * cellH;
|
|
41617
|
+
const tooltip = `${xVal}, ${yVal}: ${formatTick(val)}`;
|
|
41618
|
+
return `<rect ${markAttrs(options.chartId, xField, xVal, xi + yi * xValues.length, tooltip)} x="${x.toFixed(1)}" y="${y.toFixed(1)}" width="${cellW.toFixed(1)}" height="${cellH.toFixed(1)}" fill="${color}" fill-opacity="${opacity}" stroke="${theme.background}" stroke-width="1" />`;
|
|
41619
|
+
})
|
|
41620
|
+
).join("");
|
|
41621
|
+
const MAX_LABELS = 12;
|
|
41622
|
+
const xStep = Math.max(1, Math.ceil(xValues.length / MAX_LABELS));
|
|
41623
|
+
const xLabels = xValues.filter((_, i) => i % xStep === 0).map((val, i) => {
|
|
41624
|
+
const x = margin.left + (i * xStep + 0.5) * cellW;
|
|
41625
|
+
return `<text x="${x.toFixed(1)}" y="${(y0 + 16).toFixed(1)}" text-anchor="middle" fill="${theme.labelColor}" font-size="10">${escapeHtml(val)}</text>`;
|
|
41626
|
+
}).join("");
|
|
41627
|
+
const yStep = Math.max(1, Math.ceil(yValues.length / MAX_LABELS));
|
|
41628
|
+
const yLabels = yValues.filter((_, i) => i % yStep === 0).map((val, i) => {
|
|
41629
|
+
const y = margin.top + (i * yStep + 0.5) * cellH;
|
|
41630
|
+
return `<text x="${(x0 - 6).toFixed(1)}" y="${(y + 4).toFixed(1)}" text-anchor="end" fill="${theme.labelColor}" font-size="10">${escapeHtml(val)}</text>`;
|
|
41631
|
+
}).join("");
|
|
41632
|
+
return svgFrame(width, height, theme.background, `
|
|
41633
|
+
<line x1="${x0}" y1="${y0}" x2="${(x0 + chartWidth).toFixed(1)}" y2="${y0}" stroke="${theme.axisColor}" />
|
|
41634
|
+
<line x1="${x0}" y1="${margin.top}" x2="${x0}" y2="${y0}" stroke="${theme.axisColor}" />
|
|
41635
|
+
${cells}
|
|
41636
|
+
${xLabels}
|
|
41637
|
+
${yLabels}
|
|
41638
|
+
<text x="${(x0 + chartWidth / 2).toFixed(1)}" y="${(y0 + 52).toFixed(1)}" text-anchor="middle" fill="${theme.labelColor}" font-size="12">${escapeHtml(xField)}</text>
|
|
41639
|
+
<text x="14" y="${(margin.top + chartHeight / 2).toFixed(1)}" text-anchor="middle" transform="rotate(-90 14 ${(margin.top + chartHeight / 2).toFixed(1)})" fill="${theme.labelColor}" font-size="12">${escapeHtml(yField)}</text>
|
|
41640
|
+
`);
|
|
41641
|
+
}
|
|
41405
41642
|
function renderUnsupported(chart) {
|
|
41406
41643
|
return `<div class="miao-unsupported">Static HTML rendering for ${escapeHtml(chart.type)} is not implemented yet.</div>`;
|
|
41407
41644
|
}
|
|
@@ -42117,12 +42354,38 @@ function escapeScriptJson(value) {
|
|
|
42117
42354
|
return JSON.stringify(value).replace(/</g, "\\u003c");
|
|
42118
42355
|
}
|
|
42119
42356
|
|
|
42357
|
+
// src/insight-utils.ts
|
|
42358
|
+
function normalizeInsight(insight) {
|
|
42359
|
+
if (typeof insight === "string") {
|
|
42360
|
+
return { text: insight, evidence: [], original: insight };
|
|
42361
|
+
}
|
|
42362
|
+
return {
|
|
42363
|
+
text: insight.text,
|
|
42364
|
+
evidence: insight.evidence ?? [],
|
|
42365
|
+
caveat: insight.caveat,
|
|
42366
|
+
severity: insight.severity,
|
|
42367
|
+
original: insight
|
|
42368
|
+
};
|
|
42369
|
+
}
|
|
42370
|
+
function normalizeInsights(insights) {
|
|
42371
|
+
return (insights ?? []).map(normalizeInsight);
|
|
42372
|
+
}
|
|
42373
|
+
function mapInsightText(insight, mapText) {
|
|
42374
|
+
if (typeof insight === "string") return mapText(insight);
|
|
42375
|
+
return { ...insight, text: mapText(insight.text) };
|
|
42376
|
+
}
|
|
42377
|
+
function insightPreview(text) {
|
|
42378
|
+
return `"${text.slice(0, 80)}${text.length > 80 ? "..." : ""}"`;
|
|
42379
|
+
}
|
|
42380
|
+
|
|
42120
42381
|
// src/html-export.ts
|
|
42121
42382
|
var INSIGHTS_CSS = `
|
|
42122
42383
|
.report-insights { margin: 0 0 32px; padding: 16px 20px 14px; border-radius: 4px; border: 1px solid rgba(128,128,128,0.18); background: rgba(128,128,128,0.04); }
|
|
42123
42384
|
.insights-label { font-size: 10px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.1em; opacity: 0.45; margin: 0 0 8px; }
|
|
42124
42385
|
.insights-list { margin: 0; padding: 0 0 0 18px; }
|
|
42125
42386
|
.insights-list li { margin: 5px 0; font-size: 13px; line-height: 1.55; opacity: 0.75; }
|
|
42387
|
+
.insight-warning { color: #8a4b00; }
|
|
42388
|
+
.insight-caveat { display: block; margin-top: 2px; font-size: 11px; opacity: 0.58; }
|
|
42126
42389
|
`;
|
|
42127
42390
|
function renderStaticHtml(spec, profile, rows, themeOverride, interactiveOptions = {}) {
|
|
42128
42391
|
const theme = getTheme(themeOverride ?? spec.theme);
|
|
@@ -42229,7 +42492,11 @@ function chartIdFor(chart, index) {
|
|
|
42229
42492
|
return chart.id ?? `chart-${index + 1}`;
|
|
42230
42493
|
}
|
|
42231
42494
|
function renderInsights(insights) {
|
|
42232
|
-
const items = insights.map((
|
|
42495
|
+
const items = normalizeInsights(insights).map((insight) => {
|
|
42496
|
+
const className = insight.severity === "warning" ? ' class="insight-warning"' : "";
|
|
42497
|
+
const caveat = insight.caveat ? `<span class="insight-caveat">${escapeHtml(insight.caveat)}</span>` : "";
|
|
42498
|
+
return `<li${className}>${escapeHtml(insight.text)}${caveat}</li>`;
|
|
42499
|
+
}).join("\n ");
|
|
42233
42500
|
return `<section class="report-insights">
|
|
42234
42501
|
<p class="insights-label">Key Observations</p>
|
|
42235
42502
|
<ul class="insights-list">
|
|
@@ -42239,9 +42506,9 @@ function renderInsights(insights) {
|
|
|
42239
42506
|
}
|
|
42240
42507
|
function buildCaption(chart) {
|
|
42241
42508
|
const parts = [];
|
|
42242
|
-
if (chart.encoding
|
|
42243
|
-
if (chart.encoding
|
|
42244
|
-
if (chart.encoding
|
|
42509
|
+
if (chart.encoding?.x?.field) parts.push(`x: ${chart.encoding.x.field}`);
|
|
42510
|
+
if (chart.encoding?.y?.field) parts.push(`y: ${chart.encoding.y.field}`);
|
|
42511
|
+
if (chart.encoding?.value?.field) parts.push(`value: ${chart.encoding.value.field}`);
|
|
42245
42512
|
const transforms = chart.data?.transform ?? [];
|
|
42246
42513
|
const agg = transforms.find((t) => t.type === "aggregate");
|
|
42247
42514
|
if (agg?.groupBy?.length) parts.push(`grouped by ${agg.groupBy.join(", ")}`);
|
|
@@ -56806,6 +57073,15 @@ var chartInteractionSchema = external_exports.object({
|
|
|
56806
57073
|
tooltip: external_exports.boolean().optional(),
|
|
56807
57074
|
select: external_exports.enum(["filter", "detail"]).optional()
|
|
56808
57075
|
});
|
|
57076
|
+
var insightSchema = external_exports.union([
|
|
57077
|
+
external_exports.string(),
|
|
57078
|
+
external_exports.object({
|
|
57079
|
+
text: external_exports.string().min(1),
|
|
57080
|
+
evidence: external_exports.array(external_exports.string().min(1)).optional(),
|
|
57081
|
+
caveat: external_exports.string().optional(),
|
|
57082
|
+
severity: external_exports.enum(["info", "warning"]).optional()
|
|
57083
|
+
})
|
|
57084
|
+
]);
|
|
56809
57085
|
var chartSpecSchema = external_exports.object({
|
|
56810
57086
|
id: external_exports.string().min(1).optional(),
|
|
56811
57087
|
type: external_exports.enum(MVP_CHART_TYPES),
|
|
@@ -56833,7 +57109,7 @@ var reportSpecSchema = external_exports.object({
|
|
|
56833
57109
|
interactions: external_exports.object({
|
|
56834
57110
|
globalFilters: external_exports.array(globalFilterSchema).optional()
|
|
56835
57111
|
}).optional(),
|
|
56836
|
-
insights: external_exports.array(
|
|
57112
|
+
insights: external_exports.array(insightSchema).optional(),
|
|
56837
57113
|
charts: external_exports.array(chartSpecSchema).min(1)
|
|
56838
57114
|
});
|
|
56839
57115
|
var singleOrReportSpecSchema = external_exports.union([
|
|
@@ -56889,12 +57165,23 @@ function resolveEvidencePath(evidence, id, path) {
|
|
|
56889
57165
|
}
|
|
56890
57166
|
return { found: false, value: void 0 };
|
|
56891
57167
|
}
|
|
57168
|
+
function resolveDirectives(text, evidence) {
|
|
57169
|
+
return text.replace(new RegExp(EVIDENCE_RE.source, "g"), (_, id, path) => {
|
|
57170
|
+
const { found, value } = resolveEvidencePath(evidence, id, path);
|
|
57171
|
+
return found ? String(value) : `[?${id}.${path}]`;
|
|
57172
|
+
});
|
|
57173
|
+
}
|
|
56892
57174
|
|
|
56893
57175
|
// src/chart-catalog.ts
|
|
56894
57176
|
var CHART_CATALOG = [
|
|
56895
57177
|
{
|
|
56896
57178
|
id: "bar",
|
|
56897
57179
|
displayName: "Bar Chart",
|
|
57180
|
+
compactFor: "rank,compare",
|
|
57181
|
+
requires: "dim(2-30)+measure",
|
|
57182
|
+
transformRecipe: "agg>sort(desc)>limit(10)",
|
|
57183
|
+
avoid: "dim>30,time>=3",
|
|
57184
|
+
insightPattern: "top {dimension} by {measure}",
|
|
56898
57185
|
requiredEncodings: ["x", "y"],
|
|
56899
57186
|
allowedTransforms: ["aggregate", "sort", "limit", "derive-month"],
|
|
56900
57187
|
bestFor: ["ranking by category", "comparison across dimensions", "top-N with limit"],
|
|
@@ -56917,6 +57204,25 @@ var CHART_CATALOG = [
|
|
|
56917
57204
|
return null;
|
|
56918
57205
|
}
|
|
56919
57206
|
},
|
|
57207
|
+
{
|
|
57208
|
+
code: "BAR_NO_AGGREGATE",
|
|
57209
|
+
severity: "warning",
|
|
57210
|
+
expression: "encoding.y.aggregate not set and no aggregate transform",
|
|
57211
|
+
message: "bar will plot one bar per raw row \u2014 unsorted and unaggregated. Add encoding.y.aggregate or data.transform aggregate + sort + limit.",
|
|
57212
|
+
validate: (chart) => {
|
|
57213
|
+
const hasAggTransform = (chart.data?.transform ?? []).some((t) => t.type === "aggregate");
|
|
57214
|
+
const hasEncodingAgg = !!chart.encoding?.y?.aggregate;
|
|
57215
|
+
if (!hasAggTransform && !hasEncodingAgg) {
|
|
57216
|
+
return {
|
|
57217
|
+
code: "BAR_NO_AGGREGATE",
|
|
57218
|
+
severity: "warning",
|
|
57219
|
+
message: `bar${chart.id ? ` '${chart.id}'` : ""}: no aggregation \u2014 will plot one bar per raw row (unsorted, unaggregated). Add encoding.y.aggregate (sum/avg/count) or data.transform: aggregate + sort + limit.`,
|
|
57220
|
+
chartId: chart.id
|
|
57221
|
+
};
|
|
57222
|
+
}
|
|
57223
|
+
return null;
|
|
57224
|
+
}
|
|
57225
|
+
},
|
|
56920
57226
|
{
|
|
56921
57227
|
code: "TOO_MANY_CATEGORIES",
|
|
56922
57228
|
severity: "warning",
|
|
@@ -56943,6 +57249,11 @@ var CHART_CATALOG = [
|
|
|
56943
57249
|
{
|
|
56944
57250
|
id: "line",
|
|
56945
57251
|
displayName: "Line Chart",
|
|
57252
|
+
compactFor: "trend",
|
|
57253
|
+
requires: "time(>=3)+measure",
|
|
57254
|
+
transformRecipe: "agg(time)>sort(asc)",
|
|
57255
|
+
avoid: "time<3,nominal_x",
|
|
57256
|
+
insightPattern: "{measure} over {time}",
|
|
56946
57257
|
requiredEncodings: ["x", "y"],
|
|
56947
57258
|
allowedTransforms: ["aggregate", "sort", "derive-month"],
|
|
56948
57259
|
bestFor: ["time series trends", "continuous data over ordered axis"],
|
|
@@ -56992,6 +57303,10 @@ var CHART_CATALOG = [
|
|
|
56992
57303
|
{
|
|
56993
57304
|
id: "area",
|
|
56994
57305
|
displayName: "Area Chart",
|
|
57306
|
+
compactFor: "trend,magnitude",
|
|
57307
|
+
requires: "time(>=3)+measure",
|
|
57308
|
+
transformRecipe: "agg(time)>sort(asc)",
|
|
57309
|
+
avoid: "time<3,negative_values,nominal_x",
|
|
56995
57310
|
requiredEncodings: ["x", "y"],
|
|
56996
57311
|
allowedTransforms: ["aggregate", "sort", "derive-month"],
|
|
56997
57312
|
bestFor: ["cumulative trends", "filled time series with visual mass"],
|
|
@@ -57041,11 +57356,35 @@ var CHART_CATALOG = [
|
|
|
57041
57356
|
{
|
|
57042
57357
|
id: "pie",
|
|
57043
57358
|
displayName: "Pie Chart",
|
|
57359
|
+
compactFor: "share,composition",
|
|
57360
|
+
requires: "dim(2-7)+measure",
|
|
57361
|
+
transformRecipe: "agg>sort(desc)>limit(7)",
|
|
57362
|
+
avoid: "dim>7,non_whole_values",
|
|
57363
|
+
insightPattern: "{dimension} share of {measure}",
|
|
57044
57364
|
requiredEncodings: ["label", "value"],
|
|
57045
57365
|
allowedTransforms: ["aggregate", "sort", "limit"],
|
|
57046
57366
|
bestFor: ["part-to-whole proportions", "share distribution with \u22647 categories"],
|
|
57047
57367
|
antiPatterns: ["more than 7 slices (use bar)", "values that do not sum to a meaningful whole"],
|
|
57048
57368
|
rules: [
|
|
57369
|
+
{
|
|
57370
|
+
code: "PIE_NO_AGGREGATE",
|
|
57371
|
+
severity: "warning",
|
|
57372
|
+
expression: "encoding.value.aggregate not set and no aggregate transform",
|
|
57373
|
+
message: "pie will show one slice per raw row. Add encoding.value.aggregate or data.transform aggregate + sort + limit.",
|
|
57374
|
+
validate: (chart) => {
|
|
57375
|
+
const hasAggTransform = (chart.data?.transform ?? []).some((t) => t.type === "aggregate");
|
|
57376
|
+
const hasEncodingAgg = !!chart.encoding?.value?.aggregate;
|
|
57377
|
+
if (!hasAggTransform && !hasEncodingAgg) {
|
|
57378
|
+
return {
|
|
57379
|
+
code: "PIE_NO_AGGREGATE",
|
|
57380
|
+
severity: "warning",
|
|
57381
|
+
message: `pie${chart.id ? ` '${chart.id}'` : ""}: no aggregation \u2014 will show one slice per raw row (too many slices, wrong values). Add encoding.value.aggregate (sum/avg/count) or data.transform: aggregate + sort + limit.`,
|
|
57382
|
+
chartId: chart.id
|
|
57383
|
+
};
|
|
57384
|
+
}
|
|
57385
|
+
return null;
|
|
57386
|
+
}
|
|
57387
|
+
},
|
|
57049
57388
|
{
|
|
57050
57389
|
code: "TOO_MANY_SLICES",
|
|
57051
57390
|
severity: "warning",
|
|
@@ -57072,6 +57411,10 @@ var CHART_CATALOG = [
|
|
|
57072
57411
|
{
|
|
57073
57412
|
id: "scatter",
|
|
57074
57413
|
displayName: "Scatter Chart",
|
|
57414
|
+
compactFor: "relationship,correlation",
|
|
57415
|
+
requires: "measure+measure",
|
|
57416
|
+
transformRecipe: "raw_or_limit",
|
|
57417
|
+
avoid: "single_measure,categorical_axis",
|
|
57075
57418
|
requiredEncodings: ["x", "y"],
|
|
57076
57419
|
allowedTransforms: ["sort", "limit"],
|
|
57077
57420
|
bestFor: ["correlation between two measures", "distribution of two quantitative variables"],
|
|
@@ -57081,6 +57424,11 @@ var CHART_CATALOG = [
|
|
|
57081
57424
|
{
|
|
57082
57425
|
id: "histogram",
|
|
57083
57426
|
displayName: "Histogram",
|
|
57427
|
+
compactFor: "distribution",
|
|
57428
|
+
requires: "measure+rows(>=20)",
|
|
57429
|
+
transformRecipe: "bin(numeric)>count",
|
|
57430
|
+
avoid: "rows<20,categorical_field",
|
|
57431
|
+
insightPattern: "{measure} distribution",
|
|
57084
57432
|
requiredEncodings: ["x"],
|
|
57085
57433
|
allowedTransforms: ["derive-month"],
|
|
57086
57434
|
bestFor: ["distribution of a single numeric field", "frequency across value bins"],
|
|
@@ -57091,6 +57439,11 @@ var CHART_CATALOG = [
|
|
|
57091
57439
|
{
|
|
57092
57440
|
id: "heatmap",
|
|
57093
57441
|
displayName: "Heatmap",
|
|
57442
|
+
compactFor: "matrix,density",
|
|
57443
|
+
requires: "dim+dim+measure",
|
|
57444
|
+
transformRecipe: "agg(dim,dim)>encode(value)",
|
|
57445
|
+
avoid: "single_dimension,sparse_matrix",
|
|
57446
|
+
insightPattern: "{measure} by {x_dimension} and {y_dimension}",
|
|
57094
57447
|
requiredEncodings: ["x", "y", "value"],
|
|
57095
57448
|
allowedTransforms: ["aggregate"],
|
|
57096
57449
|
bestFor: ["matrix of two dimensions vs one measure", "calendar-style density maps"],
|
|
@@ -57100,6 +57453,10 @@ var CHART_CATALOG = [
|
|
|
57100
57453
|
{
|
|
57101
57454
|
id: "table",
|
|
57102
57455
|
displayName: "Data Table",
|
|
57456
|
+
compactFor: "detail,high-cardinality,export",
|
|
57457
|
+
requires: "any_fields",
|
|
57458
|
+
transformRecipe: "sort_or_limit_optional",
|
|
57459
|
+
avoid: "too_many_columns_without_selection",
|
|
57103
57460
|
requiredEncodings: [],
|
|
57104
57461
|
allowedTransforms: ["aggregate", "sort", "limit", "derive-month"],
|
|
57105
57462
|
bestFor: ["high-cardinality dimension detail", "multi-measure comparison", "data export"],
|
|
@@ -57109,17 +57466,60 @@ var CHART_CATALOG = [
|
|
|
57109
57466
|
{
|
|
57110
57467
|
id: "bigvalue",
|
|
57111
57468
|
displayName: "Big Value (KPI Card)",
|
|
57469
|
+
compactFor: "kpi,summary",
|
|
57470
|
+
requires: "measure",
|
|
57471
|
+
transformRecipe: "agg(measure)>single_value",
|
|
57472
|
+
avoid: "raw_row_value,too_many_cards",
|
|
57473
|
+
insightPattern: "total {measure}",
|
|
57112
57474
|
requiredEncodings: ["value"],
|
|
57113
57475
|
allowedTransforms: ["aggregate"],
|
|
57114
57476
|
bestFor: ["single top-level KPI", "summary metric with optional delta"],
|
|
57115
57477
|
antiPatterns: ["more than 4 bigvalue cards per report (use kpigrid)", "showing a dimension value without a measure"],
|
|
57116
|
-
rules: [
|
|
57478
|
+
rules: [
|
|
57479
|
+
{
|
|
57480
|
+
code: "BIGVALUE_NO_REDUCTION",
|
|
57481
|
+
severity: "warning",
|
|
57482
|
+
expression: "no aggregate transform and encoding.value.aggregate not set",
|
|
57483
|
+
message: "bigvalue will show rows[0] raw value. Add encoding.value.aggregate or data.transform aggregate + limit: 1.",
|
|
57484
|
+
validate: (chart) => {
|
|
57485
|
+
const hasAggTransform = (chart.data?.transform ?? []).some((t) => t.type === "aggregate");
|
|
57486
|
+
const hasEncodingAgg = !!chart.encoding?.value?.aggregate;
|
|
57487
|
+
if (!hasAggTransform && !hasEncodingAgg) {
|
|
57488
|
+
const field = chart.encoding?.value?.field ?? "value";
|
|
57489
|
+
return {
|
|
57490
|
+
code: "BIGVALUE_NO_REDUCTION",
|
|
57491
|
+
severity: "warning",
|
|
57492
|
+
message: `bigvalue${chart.id ? ` '${chart.id}'` : ""}: no aggregation \u2014 will display rows[0].${field} (a raw row value, almost always wrong). Add encoding.value.aggregate (max/sum/avg/min/count) or data.transform: aggregate + limit: 1.`,
|
|
57493
|
+
chartId: chart.id
|
|
57494
|
+
};
|
|
57495
|
+
}
|
|
57496
|
+
return null;
|
|
57497
|
+
}
|
|
57498
|
+
}
|
|
57499
|
+
]
|
|
57117
57500
|
}
|
|
57118
57501
|
];
|
|
57119
57502
|
function getCatalogItem(chartType) {
|
|
57120
57503
|
return CHART_CATALOG.find((c) => c.id === chartType);
|
|
57121
57504
|
}
|
|
57122
57505
|
|
|
57506
|
+
// src/spec-utils.ts
|
|
57507
|
+
function findChartIndex(spec, chartId) {
|
|
57508
|
+
if (!chartId) return spec.charts.length === 1 ? 0 : -1;
|
|
57509
|
+
const byId = spec.charts.findIndex((c) => c.id === chartId);
|
|
57510
|
+
if (byId >= 0) return byId;
|
|
57511
|
+
return spec.charts.findIndex((c) => c.type === chartId);
|
|
57512
|
+
}
|
|
57513
|
+
function findLastChartIndexById(spec, id) {
|
|
57514
|
+
for (let i = spec.charts.length - 1; i >= 0; i--) {
|
|
57515
|
+
if (spec.charts[i].id === id) return i;
|
|
57516
|
+
}
|
|
57517
|
+
return -1;
|
|
57518
|
+
}
|
|
57519
|
+
function countChartsByType(spec, type) {
|
|
57520
|
+
return spec.charts.filter((c) => c.type === type).length;
|
|
57521
|
+
}
|
|
57522
|
+
|
|
57123
57523
|
// src/spec-validator.ts
|
|
57124
57524
|
var FORBIDDEN_WORDS = [
|
|
57125
57525
|
{ pattern: /\b(trend|趋势)\b/i, word: "trend/\u8D8B\u52BF" },
|
|
@@ -57174,6 +57574,8 @@ function validateReportSpec(spec, profile, formats = ["html"], context) {
|
|
|
57174
57574
|
});
|
|
57175
57575
|
}
|
|
57176
57576
|
}
|
|
57577
|
+
const finalSchemaResult = validateEncodingFieldsInFinalSchema(chart);
|
|
57578
|
+
if (isAgentError(finalSchemaResult)) return finalSchemaResult;
|
|
57177
57579
|
}
|
|
57178
57580
|
return ok(parsed.data);
|
|
57179
57581
|
}
|
|
@@ -57195,7 +57597,7 @@ function collectValidationWarnings(spec, profile, context) {
|
|
|
57195
57597
|
`Report has ${spec.charts.length} charts (>6). Consider splitting into multiple sections or removing low-value charts.`
|
|
57196
57598
|
);
|
|
57197
57599
|
}
|
|
57198
|
-
const bigvalueCount = spec
|
|
57600
|
+
const bigvalueCount = countChartsByType(spec, "bigvalue");
|
|
57199
57601
|
if (bigvalueCount > 4) {
|
|
57200
57602
|
warnings.push(
|
|
57201
57603
|
`Report has ${bigvalueCount} bigvalue cards (>4). Use kpigrid for 5+ KPI cards to avoid visual clutter.`
|
|
@@ -57235,8 +57637,20 @@ function collectValidationWarnings(spec, profile, context) {
|
|
|
57235
57637
|
return warnings;
|
|
57236
57638
|
}
|
|
57237
57639
|
function validateEvidencePaths(spec, context) {
|
|
57640
|
+
const availableIds = context.evidence.map((e) => e.id);
|
|
57641
|
+
for (const insight of normalizeInsights(spec.insights)) {
|
|
57642
|
+
for (const evidenceId of insight.evidence) {
|
|
57643
|
+
if (!availableIds.includes(evidenceId)) {
|
|
57644
|
+
return agentError(
|
|
57645
|
+
"INSIGHT_EVIDENCE_NOT_FOUND",
|
|
57646
|
+
`insight.evidence id '${evidenceId}' not found in context.evidence. Available ids: ${availableIds.join(", ") || "(none)"}`,
|
|
57647
|
+
{ evidenceId, availableIds }
|
|
57648
|
+
);
|
|
57649
|
+
}
|
|
57650
|
+
}
|
|
57651
|
+
}
|
|
57238
57652
|
const texts = [
|
|
57239
|
-
...spec.insights
|
|
57653
|
+
...normalizeInsights(spec.insights).map((insight) => insight.text),
|
|
57240
57654
|
...spec.charts.map((c) => c.title).filter((t) => Boolean(t))
|
|
57241
57655
|
];
|
|
57242
57656
|
for (const text of texts) {
|
|
@@ -57255,22 +57669,29 @@ function validateEvidencePaths(spec, context) {
|
|
|
57255
57669
|
}
|
|
57256
57670
|
function collectVerifyWarnings(spec, context) {
|
|
57257
57671
|
const warnings = [];
|
|
57258
|
-
const insights = spec.insights
|
|
57259
|
-
for (const
|
|
57672
|
+
const insights = normalizeInsights(spec.insights);
|
|
57673
|
+
for (const insight of insights) {
|
|
57260
57674
|
for (const { pattern, word } of FORBIDDEN_WORDS) {
|
|
57261
|
-
if (pattern.test(text)) {
|
|
57675
|
+
if (pattern.test(insight.text)) {
|
|
57262
57676
|
warnings.push(
|
|
57263
|
-
`insight contains forbidden word '${word}':
|
|
57677
|
+
`insight contains forbidden word '${word}': ${insightPreview(insight.text)} \u2014 use only when backed by statistical evidence in context.evidence[]`
|
|
57264
57678
|
);
|
|
57265
57679
|
}
|
|
57266
57680
|
}
|
|
57681
|
+
if (typeof insight.original !== "string" && /\d/.test(insight.text) && insight.evidence.length === 0) {
|
|
57682
|
+
warnings.push(
|
|
57683
|
+
`insight contains numeric claim without structured evidence: ${insightPreview(insight.text)}`
|
|
57684
|
+
);
|
|
57685
|
+
}
|
|
57267
57686
|
}
|
|
57268
57687
|
if (context?.sampleWarnings.length) {
|
|
57269
57688
|
const CAVEAT_PATTERNS = [
|
|
57270
57689
|
/仅供参考|样本量|有限数据|based on.*rows?|N-row sample|limited data|small sample/i,
|
|
57271
57690
|
/环比变化|period.over.period/i
|
|
57272
57691
|
];
|
|
57273
|
-
const hasCaveat = insights.some(
|
|
57692
|
+
const hasCaveat = insights.some(
|
|
57693
|
+
(insight) => Boolean(insight.caveat) || CAVEAT_PATTERNS.some((p) => p.test(insight.text))
|
|
57694
|
+
);
|
|
57274
57695
|
if (insights.length > 0 && !hasCaveat) {
|
|
57275
57696
|
const codes = context.sampleWarnings.map((w) => w.code).join(", ");
|
|
57276
57697
|
warnings.push(
|
|
@@ -57280,6 +57701,24 @@ function collectVerifyWarnings(spec, context) {
|
|
|
57280
57701
|
}
|
|
57281
57702
|
return warnings;
|
|
57282
57703
|
}
|
|
57704
|
+
function strictVerifyError(warnings) {
|
|
57705
|
+
if (warnings.length === 0) return ok(void 0);
|
|
57706
|
+
const issues = warnings.map(warningToVerifyIssue);
|
|
57707
|
+
return agentError(
|
|
57708
|
+
"STRICT_VERIFY_FAILED",
|
|
57709
|
+
`Strict verify failed with ${warnings.length} warning(s).`,
|
|
57710
|
+
{ warnings, issues }
|
|
57711
|
+
);
|
|
57712
|
+
}
|
|
57713
|
+
function warningToVerifyIssue(message) {
|
|
57714
|
+
if (message.includes("forbidden word")) {
|
|
57715
|
+
return { code: "INSIGHT_FORBIDDEN_WORD_STRICT", message };
|
|
57716
|
+
}
|
|
57717
|
+
if (message.includes("numeric claim without structured evidence")) {
|
|
57718
|
+
return { code: "INSIGHT_NUMERIC_CLAIM_WITHOUT_EVIDENCE_STRICT", message };
|
|
57719
|
+
}
|
|
57720
|
+
return { code: "INSIGHT_MISSING_CAVEAT_STRICT", message };
|
|
57721
|
+
}
|
|
57283
57722
|
function validateChartType(chart) {
|
|
57284
57723
|
if (!MVP_CHART_TYPES.includes(chart.type)) {
|
|
57285
57724
|
return agentError("UNSUPPORTED_CHART_TYPE", `Chart type '${chart.type}' is not supported in the MVP.`, {
|
|
@@ -57365,6 +57804,38 @@ function validateTransforms(chart) {
|
|
|
57365
57804
|
}
|
|
57366
57805
|
return ok(chart);
|
|
57367
57806
|
}
|
|
57807
|
+
function simulateFinalSchema(chart) {
|
|
57808
|
+
let schema = null;
|
|
57809
|
+
for (const transform2 of chart.data?.transform ?? []) {
|
|
57810
|
+
if (transform2.type === "aggregate") {
|
|
57811
|
+
const next = /* @__PURE__ */ new Set();
|
|
57812
|
+
for (const field of transform2.groupBy ?? []) next.add(field);
|
|
57813
|
+
for (const measure of transform2.measures ?? []) next.add(measure.as);
|
|
57814
|
+
schema = next;
|
|
57815
|
+
} else if (transform2.type === "derive-month" && transform2.as && schema !== null) {
|
|
57816
|
+
schema.add(transform2.as);
|
|
57817
|
+
}
|
|
57818
|
+
}
|
|
57819
|
+
return schema;
|
|
57820
|
+
}
|
|
57821
|
+
function validateEncodingFieldsInFinalSchema(chart) {
|
|
57822
|
+
const finalSchema = simulateFinalSchema(chart);
|
|
57823
|
+
if (!finalSchema) return ok(chart);
|
|
57824
|
+
const chartLabel = chart.id ? `chart '${chart.id}'` : `${chart.type} chart`;
|
|
57825
|
+
const available = [...finalSchema].join(", ") || "(none)";
|
|
57826
|
+
for (const [channel, encoding] of Object.entries(chart.encoding ?? {})) {
|
|
57827
|
+
const field = encoding?.field;
|
|
57828
|
+
if (!field) continue;
|
|
57829
|
+
if (!finalSchema.has(field)) {
|
|
57830
|
+
return agentError(
|
|
57831
|
+
"ENCODING_FIELD_NOT_IN_FINAL_ROWS",
|
|
57832
|
+
`${chartLabel}: encoding.${channel}.field '${field}' does not exist in rows after transforms. Available fields after transforms: ${available}`,
|
|
57833
|
+
{ chartId: chart.id, channel, field, availableAfterTransforms: [...finalSchema] }
|
|
57834
|
+
);
|
|
57835
|
+
}
|
|
57836
|
+
}
|
|
57837
|
+
return ok(chart);
|
|
57838
|
+
}
|
|
57368
57839
|
function collectSourceFields(chart, derivedFields) {
|
|
57369
57840
|
const fields = /* @__PURE__ */ new Set();
|
|
57370
57841
|
for (const encoding of Object.values(chart.encoding ?? {})) {
|
|
@@ -57428,6 +57899,18 @@ var blockedBlockEntrySchema = external_exports.object({
|
|
|
57428
57899
|
id: external_exports.string().min(1),
|
|
57429
57900
|
reason: external_exports.string().min(1)
|
|
57430
57901
|
});
|
|
57902
|
+
var catalogTemplateEntrySchema = external_exports.object({
|
|
57903
|
+
id: external_exports.string().min(1),
|
|
57904
|
+
score: external_exports.number().min(0).max(1),
|
|
57905
|
+
bestFor: external_exports.array(external_exports.string()),
|
|
57906
|
+
requires: external_exports.array(external_exports.enum(["measure", "dimension", "time"])),
|
|
57907
|
+
blocks: external_exports.array(external_exports.string()),
|
|
57908
|
+
density: external_exports.enum(["compact", "medium", "full"])
|
|
57909
|
+
});
|
|
57910
|
+
var blockedTemplateEntrySchema = external_exports.object({
|
|
57911
|
+
id: external_exports.string().min(1),
|
|
57912
|
+
reason: external_exports.string().min(1)
|
|
57913
|
+
});
|
|
57431
57914
|
var analyzeCatalogSchema = external_exports.object({
|
|
57432
57915
|
charts: external_exports.array(external_exports.string().min(1)),
|
|
57433
57916
|
blockedCharts: external_exports.array(external_exports.object({
|
|
@@ -57439,7 +57922,9 @@ var analyzeCatalogSchema = external_exports.object({
|
|
|
57439
57922
|
note: external_exports.string().optional()
|
|
57440
57923
|
})),
|
|
57441
57924
|
blocks: external_exports.array(catalogBlockEntrySchema).optional(),
|
|
57442
|
-
blockedBlocks: external_exports.array(blockedBlockEntrySchema).optional()
|
|
57925
|
+
blockedBlocks: external_exports.array(blockedBlockEntrySchema).optional(),
|
|
57926
|
+
templates: external_exports.array(catalogTemplateEntrySchema).optional(),
|
|
57927
|
+
blockedTemplates: external_exports.array(blockedTemplateEntrySchema).optional()
|
|
57443
57928
|
});
|
|
57444
57929
|
var metricCandidateSchema = external_exports.object({
|
|
57445
57930
|
id: external_exports.string().min(1),
|
|
@@ -57450,15 +57935,35 @@ var metricCandidateSchema = external_exports.object({
|
|
|
57450
57935
|
confidence: external_exports.enum(["high", "medium"]),
|
|
57451
57936
|
caveat: external_exports.string().optional()
|
|
57452
57937
|
});
|
|
57938
|
+
var analyzeAssumptionSchema = external_exports.object({
|
|
57939
|
+
key: external_exports.enum(["primary_measure", "primary_dimension", "time_field"]),
|
|
57940
|
+
value: external_exports.string(),
|
|
57941
|
+
confidence: external_exports.number().min(0).max(1),
|
|
57942
|
+
alternatives: external_exports.array(external_exports.string()).optional(),
|
|
57943
|
+
reason: external_exports.string().optional()
|
|
57944
|
+
});
|
|
57945
|
+
var legacyAssumptionSchema = external_exports.string().transform((value) => ({
|
|
57946
|
+
key: value.includes("dimension") ? "primary_dimension" : value.includes("time") ? "time_field" : "primary_measure",
|
|
57947
|
+
value,
|
|
57948
|
+
confidence: 0.5,
|
|
57949
|
+
reason: "legacy string assumption"
|
|
57950
|
+
}));
|
|
57453
57951
|
var analyzeIntentSchema = external_exports.object({
|
|
57454
57952
|
raw: external_exports.string().min(1),
|
|
57455
57953
|
coverage: external_exports.enum(["full", "partial"]),
|
|
57456
|
-
assumptions: external_exports.array(external_exports.
|
|
57954
|
+
assumptions: external_exports.array(external_exports.union([analyzeAssumptionSchema, legacyAssumptionSchema]))
|
|
57457
57955
|
});
|
|
57458
57956
|
var analyzeSampleWarningSchema = external_exports.object({
|
|
57459
57957
|
code: external_exports.string().min(1),
|
|
57460
57958
|
message: external_exports.string().min(1)
|
|
57461
57959
|
});
|
|
57960
|
+
var clarificationQuestionSchema = external_exports.object({
|
|
57961
|
+
id: external_exports.string().min(1),
|
|
57962
|
+
question: external_exports.string().min(1),
|
|
57963
|
+
options: external_exports.array(external_exports.string()),
|
|
57964
|
+
blocking: external_exports.boolean(),
|
|
57965
|
+
appliesTo: external_exports.enum(["measure", "dimension", "time", "template"])
|
|
57966
|
+
});
|
|
57462
57967
|
var analyzeContextSchema = external_exports.object({
|
|
57463
57968
|
intent: analyzeIntentSchema,
|
|
57464
57969
|
fields: external_exports.array(analyzeFieldSchema),
|
|
@@ -57466,8 +57971,158 @@ var analyzeContextSchema = external_exports.object({
|
|
|
57466
57971
|
catalog: analyzeCatalogSchema,
|
|
57467
57972
|
sampleWarnings: external_exports.array(analyzeSampleWarningSchema),
|
|
57468
57973
|
promptRules: external_exports.array(external_exports.string()),
|
|
57469
|
-
metricCandidates: external_exports.array(metricCandidateSchema).optional()
|
|
57470
|
-
|
|
57974
|
+
metricCandidates: external_exports.array(metricCandidateSchema).optional(),
|
|
57975
|
+
clarificationQuestions: external_exports.array(clarificationQuestionSchema).optional()
|
|
57976
|
+
});
|
|
57977
|
+
var compactAnalyzeContextSchema = external_exports.object({
|
|
57978
|
+
format: external_exports.literal("compact-v1"),
|
|
57979
|
+
intent: external_exports.object({
|
|
57980
|
+
raw: external_exports.string(),
|
|
57981
|
+
coverage: external_exports.enum(["full", "partial"])
|
|
57982
|
+
}),
|
|
57983
|
+
assumptions: external_exports.array(external_exports.tuple([
|
|
57984
|
+
external_exports.enum(["primary_measure", "primary_dimension", "time_field"]),
|
|
57985
|
+
external_exports.string(),
|
|
57986
|
+
external_exports.number(),
|
|
57987
|
+
external_exports.array(external_exports.string()).nullable().optional()
|
|
57988
|
+
])),
|
|
57989
|
+
fields: external_exports.array(external_exports.tuple([
|
|
57990
|
+
external_exports.string(),
|
|
57991
|
+
external_exports.enum(["measure", "dimension", "time", "id", "status", "score", "unknown"]),
|
|
57992
|
+
external_exports.enum(["number", "string", "date", "boolean", "unknown"]),
|
|
57993
|
+
external_exports.number().nullable().optional(),
|
|
57994
|
+
external_exports.number().nullable().optional()
|
|
57995
|
+
])),
|
|
57996
|
+
evidence: external_exports.array(external_exports.tuple([external_exports.string(), external_exports.union([
|
|
57997
|
+
external_exports.record(external_exports.string(), external_exports.unknown()),
|
|
57998
|
+
external_exports.array(external_exports.record(external_exports.string(), external_exports.unknown()))
|
|
57999
|
+
])])),
|
|
58000
|
+
metricCandidates: external_exports.array(external_exports.tuple([
|
|
58001
|
+
external_exports.string(),
|
|
58002
|
+
external_exports.enum(["unit_average", "rate", "share", "period_change", "difference"]),
|
|
58003
|
+
external_exports.string(),
|
|
58004
|
+
external_exports.number().nullable().optional()
|
|
58005
|
+
])),
|
|
58006
|
+
catalog: external_exports.object({
|
|
58007
|
+
charts: external_exports.array(external_exports.string()),
|
|
58008
|
+
blockedCharts: external_exports.array(external_exports.tuple([external_exports.string(), external_exports.string()])),
|
|
58009
|
+
blocks: external_exports.array(external_exports.tuple([external_exports.string(), external_exports.number(), external_exports.enum(["compact", "medium", "full"]), external_exports.array(external_exports.string())])).optional(),
|
|
58010
|
+
blockedBlocks: external_exports.array(external_exports.tuple([external_exports.string(), external_exports.string()])).optional(),
|
|
58011
|
+
templates: external_exports.array(external_exports.tuple([external_exports.string(), external_exports.number(), external_exports.enum(["compact", "medium", "full"]), external_exports.array(external_exports.string())])).optional(),
|
|
58012
|
+
blockedTemplates: external_exports.array(external_exports.tuple([external_exports.string(), external_exports.string()])).optional()
|
|
58013
|
+
}),
|
|
58014
|
+
warnings: external_exports.array(external_exports.tuple([external_exports.string(), external_exports.string()])),
|
|
58015
|
+
clarificationQuestions: external_exports.array(external_exports.tuple([
|
|
58016
|
+
external_exports.string(),
|
|
58017
|
+
external_exports.string(),
|
|
58018
|
+
external_exports.array(external_exports.string()),
|
|
58019
|
+
external_exports.boolean(),
|
|
58020
|
+
external_exports.enum(["measure", "dimension", "time", "template"])
|
|
58021
|
+
]))
|
|
58022
|
+
});
|
|
58023
|
+
function toCompactAnalyzeContext(ctx) {
|
|
58024
|
+
return {
|
|
58025
|
+
format: "compact-v1",
|
|
58026
|
+
intent: { raw: ctx.intent.raw, coverage: ctx.intent.coverage },
|
|
58027
|
+
assumptions: ctx.intent.assumptions.map((a) => [a.key, a.value, a.confidence, a.alternatives]),
|
|
58028
|
+
fields: ctx.fields.map((f) => [f.name, f.role, f.type, f.distinctCount, f.timePeriods]),
|
|
58029
|
+
evidence: ctx.evidence.map((e) => [e.id, e.values ?? e.rows ?? {}]),
|
|
58030
|
+
metricCandidates: (ctx.metricCandidates ?? []).map((m) => [m.id, m.type, m.formula, m.value]),
|
|
58031
|
+
catalog: {
|
|
58032
|
+
charts: ctx.catalog.charts,
|
|
58033
|
+
blockedCharts: ctx.catalog.blockedCharts.map((c) => [c.type, c.reason]),
|
|
58034
|
+
blocks: ctx.catalog.blocks?.map((b) => [b.id, b.score, b.density, b.charts]),
|
|
58035
|
+
blockedBlocks: ctx.catalog.blockedBlocks?.map((b) => [b.id, b.reason]),
|
|
58036
|
+
templates: ctx.catalog.templates?.map((t) => [t.id, t.score, t.density, t.blocks]),
|
|
58037
|
+
blockedTemplates: ctx.catalog.blockedTemplates?.map((t) => [t.id, t.reason])
|
|
58038
|
+
},
|
|
58039
|
+
warnings: ctx.sampleWarnings.map((w) => [w.code, w.message]),
|
|
58040
|
+
clarificationQuestions: (ctx.clarificationQuestions ?? []).map((q) => [
|
|
58041
|
+
q.id,
|
|
58042
|
+
q.question,
|
|
58043
|
+
q.options,
|
|
58044
|
+
q.blocking,
|
|
58045
|
+
q.appliesTo
|
|
58046
|
+
])
|
|
58047
|
+
};
|
|
58048
|
+
}
|
|
58049
|
+
function fromCompactAnalyzeContext(ctx) {
|
|
58050
|
+
return {
|
|
58051
|
+
intent: {
|
|
58052
|
+
raw: ctx.intent.raw,
|
|
58053
|
+
coverage: ctx.intent.coverage,
|
|
58054
|
+
assumptions: ctx.assumptions.map(([key, value, confidence, alternatives]) => ({
|
|
58055
|
+
key,
|
|
58056
|
+
value,
|
|
58057
|
+
confidence,
|
|
58058
|
+
alternatives: alternatives ?? void 0
|
|
58059
|
+
}))
|
|
58060
|
+
},
|
|
58061
|
+
fields: ctx.fields.map(([name, role, type, distinctCount, timePeriods]) => ({
|
|
58062
|
+
name,
|
|
58063
|
+
role,
|
|
58064
|
+
type,
|
|
58065
|
+
...distinctCount !== void 0 && distinctCount !== null ? { distinctCount } : {},
|
|
58066
|
+
...timePeriods !== void 0 && timePeriods !== null ? { timePeriods } : {}
|
|
58067
|
+
})),
|
|
58068
|
+
evidence: ctx.evidence.map(([id, value]) => ({
|
|
58069
|
+
id,
|
|
58070
|
+
query: `compact evidence: ${id}`,
|
|
58071
|
+
...Array.isArray(value) ? { rows: value } : { values: value }
|
|
58072
|
+
})),
|
|
58073
|
+
catalog: {
|
|
58074
|
+
charts: ctx.catalog.charts,
|
|
58075
|
+
blockedCharts: ctx.catalog.blockedCharts.map(([type, reason]) => ({ type, reason })),
|
|
58076
|
+
recommendedPlan: [],
|
|
58077
|
+
blocks: ctx.catalog.blocks?.map(([id, score, density, charts]) => ({
|
|
58078
|
+
id,
|
|
58079
|
+
score,
|
|
58080
|
+
description: "",
|
|
58081
|
+
bestFor: [],
|
|
58082
|
+
density,
|
|
58083
|
+
examplePrompt: "",
|
|
58084
|
+
charts,
|
|
58085
|
+
variables: {},
|
|
58086
|
+
qualityChecks: []
|
|
58087
|
+
})),
|
|
58088
|
+
blockedBlocks: ctx.catalog.blockedBlocks?.map(([id, reason]) => ({ id, reason })),
|
|
58089
|
+
templates: ctx.catalog.templates?.map(([id, score, density, blocks]) => ({
|
|
58090
|
+
id,
|
|
58091
|
+
score,
|
|
58092
|
+
bestFor: [],
|
|
58093
|
+
requires: [],
|
|
58094
|
+
blocks,
|
|
58095
|
+
density
|
|
58096
|
+
})),
|
|
58097
|
+
blockedTemplates: ctx.catalog.blockedTemplates?.map(([id, reason]) => ({ id, reason }))
|
|
58098
|
+
},
|
|
58099
|
+
sampleWarnings: ctx.warnings.map(([code, message]) => ({ code, message })),
|
|
58100
|
+
promptRules: [],
|
|
58101
|
+
metricCandidates: ctx.metricCandidates.map(([id, type, formula, value]) => ({
|
|
58102
|
+
id,
|
|
58103
|
+
type,
|
|
58104
|
+
label: id,
|
|
58105
|
+
formula,
|
|
58106
|
+
...value !== void 0 && value !== null ? { value } : {},
|
|
58107
|
+
confidence: "medium"
|
|
58108
|
+
})),
|
|
58109
|
+
clarificationQuestions: ctx.clarificationQuestions.map(([id, question, options, blocking, appliesTo]) => ({
|
|
58110
|
+
id,
|
|
58111
|
+
question,
|
|
58112
|
+
options,
|
|
58113
|
+
blocking,
|
|
58114
|
+
appliesTo
|
|
58115
|
+
}))
|
|
58116
|
+
};
|
|
58117
|
+
}
|
|
58118
|
+
function parseAnalyzeContext(value) {
|
|
58119
|
+
const unwrapped = value.ok === true ? value.value : value;
|
|
58120
|
+
const full = analyzeContextSchema.safeParse(unwrapped);
|
|
58121
|
+
if (full.success) return full.data;
|
|
58122
|
+
const compact = compactAnalyzeContextSchema.safeParse(unwrapped);
|
|
58123
|
+
if (compact.success) return fromCompactAnalyzeContext(compact.data);
|
|
58124
|
+
return null;
|
|
58125
|
+
}
|
|
57471
58126
|
|
|
57472
58127
|
// src/deck-layouts.ts
|
|
57473
58128
|
function withSize(chart, width, height) {
|
|
@@ -57961,13 +58616,13 @@ function validateChartFields(chart, sourceFields, path) {
|
|
|
57961
58616
|
return deckFieldError(path, chart.type, `Chart type '${chart.type}' is not supported.`);
|
|
57962
58617
|
}
|
|
57963
58618
|
for (const encoding of REQUIRED_ENCODINGS[chart.type] ?? []) {
|
|
57964
|
-
if (!chart.encoding[encoding]?.field) {
|
|
58619
|
+
if (!chart.encoding?.[encoding]?.field) {
|
|
57965
58620
|
return deckFieldError(`${path}.encoding.${encoding}`, encoding, `Chart type '${chart.type}' requires encoding '${encoding}'.`);
|
|
57966
58621
|
}
|
|
57967
58622
|
}
|
|
57968
58623
|
const available = applyTransforms(chart.data?.transform ?? [], sourceFields, path);
|
|
57969
58624
|
if (isAgentError(available)) return available;
|
|
57970
|
-
for (const [encoding, spec] of Object.entries(chart.encoding)) {
|
|
58625
|
+
for (const [encoding, spec] of Object.entries(chart.encoding ?? {})) {
|
|
57971
58626
|
if (spec?.field && !available.value.has(spec.field)) {
|
|
57972
58627
|
return deckFieldError(`${path}.encoding.${encoding}.field`, spec.field, `Field '${spec.field}' is not available for this chart encoding.`);
|
|
57973
58628
|
}
|
|
@@ -58064,7 +58719,7 @@ var infographicSpecSchema = external_exports.object({
|
|
|
58064
58719
|
inputFile: external_exports.string().default(""),
|
|
58065
58720
|
generatedAt: external_exports.string().default(() => (/* @__PURE__ */ new Date()).toISOString()),
|
|
58066
58721
|
wordCount: external_exports.number().int().min(0).default(0)
|
|
58067
|
-
}).default({})
|
|
58722
|
+
}).default(() => ({ inputFile: "", generatedAt: (/* @__PURE__ */ new Date()).toISOString(), wordCount: 0 }))
|
|
58068
58723
|
});
|
|
58069
58724
|
function loadInfographicSpec(file2) {
|
|
58070
58725
|
let raw;
|
|
@@ -58768,6 +59423,178 @@ function toCatalogBlockEntry(resolver, decision, ctx) {
|
|
|
58768
59423
|
};
|
|
58769
59424
|
}
|
|
58770
59425
|
|
|
59426
|
+
// src/report-template-registry.ts
|
|
59427
|
+
var YAML = __toESM(require_dist(), 1);
|
|
59428
|
+
function scoreForRequirements(ctx, requires) {
|
|
59429
|
+
for (const role of requires) {
|
|
59430
|
+
if (role === "measure" && !ctx.fields.some((f) => f.role === "measure" || f.role === "score")) {
|
|
59431
|
+
return { ok: false, score: 0, reason: "missing required measure" };
|
|
59432
|
+
}
|
|
59433
|
+
if (role === "dimension" && !ctx.fields.some((f) => f.role === "dimension" || f.role === "status")) {
|
|
59434
|
+
return { ok: false, score: 0, reason: "missing required dimension" };
|
|
59435
|
+
}
|
|
59436
|
+
if (role === "time") {
|
|
59437
|
+
const time3 = ctx.fields.find((f) => f.role === "time");
|
|
59438
|
+
if (!time3) return { ok: false, score: 0, reason: "missing required time" };
|
|
59439
|
+
const periods = time3.timePeriods ?? 0;
|
|
59440
|
+
if (periods < 3) return { ok: false, score: 0, reason: `timePeriods=${periods} < 3` };
|
|
59441
|
+
}
|
|
59442
|
+
}
|
|
59443
|
+
return { ok: true, score: Math.min(0.65 + requires.length * 0.1 + (ctx.evidence.length > 0 ? 0.05 : 0), 1) };
|
|
59444
|
+
}
|
|
59445
|
+
function compileBlocks(blockIds, ctx) {
|
|
59446
|
+
const charts = [];
|
|
59447
|
+
for (const id of blockIds) {
|
|
59448
|
+
const block = getBlockById(id);
|
|
59449
|
+
if (!block) continue;
|
|
59450
|
+
const decision = block.canUse(ctx);
|
|
59451
|
+
if (!decision.ok) continue;
|
|
59452
|
+
const variables = block.defaultVariables(ctx);
|
|
59453
|
+
charts.push(...block.compile(variables, ctx).charts);
|
|
59454
|
+
}
|
|
59455
|
+
const seen = /* @__PURE__ */ new Set();
|
|
59456
|
+
return {
|
|
59457
|
+
title: "Miao Vision Report",
|
|
59458
|
+
insights: [],
|
|
59459
|
+
charts: charts.map((chart) => {
|
|
59460
|
+
if (!chart.id || !seen.has(chart.id)) {
|
|
59461
|
+
if (chart.id) seen.add(chart.id);
|
|
59462
|
+
return chart;
|
|
59463
|
+
}
|
|
59464
|
+
const next = { ...chart, id: `${chart.id}_${seen.size + 1}` };
|
|
59465
|
+
seen.add(next.id);
|
|
59466
|
+
return next;
|
|
59467
|
+
})
|
|
59468
|
+
};
|
|
59469
|
+
}
|
|
59470
|
+
var TEMPLATE_REGISTRY = [
|
|
59471
|
+
{
|
|
59472
|
+
id: "snapshot-overview",
|
|
59473
|
+
bestFor: ["static comparison", "category ranking", "no time axis"],
|
|
59474
|
+
requires: ["measure", "dimension"],
|
|
59475
|
+
blocks: ["kpi-summary", "snapshot-ranking"],
|
|
59476
|
+
density: "compact",
|
|
59477
|
+
qualityNotes: ["Use for static comparisons without requiring a trend.", "Add sample caveats when sampleWarnings are present."],
|
|
59478
|
+
canUse: (ctx) => scoreForRequirements(ctx, ["measure", "dimension"]),
|
|
59479
|
+
instantiate: (ctx) => compileBlocks(["kpi-summary", "snapshot-ranking"], ctx)
|
|
59480
|
+
},
|
|
59481
|
+
{
|
|
59482
|
+
id: "trend-ranking-overview",
|
|
59483
|
+
bestFor: ["executive trend with category ranking", "monthly review"],
|
|
59484
|
+
requires: ["measure", "dimension", "time"],
|
|
59485
|
+
blocks: ["trend-ranking"],
|
|
59486
|
+
density: "full",
|
|
59487
|
+
qualityNotes: ["Requires at least 3 time periods.", "Combines KPI, trend, and ranking views."],
|
|
59488
|
+
canUse: (ctx) => scoreForRequirements(ctx, ["measure", "dimension", "time"]),
|
|
59489
|
+
instantiate: (ctx) => compileBlocks(["trend-ranking"], ctx)
|
|
59490
|
+
},
|
|
59491
|
+
{
|
|
59492
|
+
id: "full-detail-report",
|
|
59493
|
+
bestFor: ["comprehensive business review", "trend plus ranking plus table"],
|
|
59494
|
+
requires: ["measure", "dimension", "time"],
|
|
59495
|
+
blocks: ["full-detail-report"],
|
|
59496
|
+
density: "full",
|
|
59497
|
+
qualityNotes: ["Use for comprehensive reviews where detail table is acceptable.", "Keep total chart count within report limits."],
|
|
59498
|
+
canUse: (ctx) => scoreForRequirements(ctx, ["measure", "dimension", "time"]),
|
|
59499
|
+
instantiate: (ctx) => compileBlocks(["full-detail-report"], ctx)
|
|
59500
|
+
},
|
|
59501
|
+
{
|
|
59502
|
+
id: "composition-review",
|
|
59503
|
+
bestFor: ["share analysis", "part-to-whole breakdown", "composition report"],
|
|
59504
|
+
requires: ["measure", "dimension"],
|
|
59505
|
+
blocks: ["kpi-summary", "comparison-breakdown"],
|
|
59506
|
+
density: "medium",
|
|
59507
|
+
qualityNotes: ["Use for share or composition analysis.", "Avoid when the primary dimension has too many categories for pie."],
|
|
59508
|
+
canUse: (ctx) => scoreForRequirements(ctx, ["measure", "dimension"]),
|
|
59509
|
+
instantiate: (ctx) => compileBlocks(["kpi-summary", "comparison-breakdown"], ctx)
|
|
59510
|
+
}
|
|
59511
|
+
];
|
|
59512
|
+
function getTemplateById(id) {
|
|
59513
|
+
return TEMPLATE_REGISTRY.find((template) => template.id === id);
|
|
59514
|
+
}
|
|
59515
|
+
function templateInfo(template) {
|
|
59516
|
+
return {
|
|
59517
|
+
id: template.id,
|
|
59518
|
+
bestFor: template.bestFor,
|
|
59519
|
+
requires: template.requires,
|
|
59520
|
+
blocks: template.blocks,
|
|
59521
|
+
density: template.density,
|
|
59522
|
+
qualityNotes: template.qualityNotes
|
|
59523
|
+
};
|
|
59524
|
+
}
|
|
59525
|
+
function toCatalogTemplateEntry(resolver, decision) {
|
|
59526
|
+
return {
|
|
59527
|
+
id: resolver.id,
|
|
59528
|
+
score: decision.score,
|
|
59529
|
+
bestFor: resolver.bestFor,
|
|
59530
|
+
requires: resolver.requires,
|
|
59531
|
+
blocks: resolver.blocks,
|
|
59532
|
+
density: resolver.density
|
|
59533
|
+
};
|
|
59534
|
+
}
|
|
59535
|
+
function templateSpecToYaml(spec) {
|
|
59536
|
+
return YAML.stringify(spec);
|
|
59537
|
+
}
|
|
59538
|
+
function buildTemplateCatalog(ctx) {
|
|
59539
|
+
const templates = [];
|
|
59540
|
+
const blockedTemplates = [];
|
|
59541
|
+
for (const template of TEMPLATE_REGISTRY) {
|
|
59542
|
+
const decision = template.canUse(ctx);
|
|
59543
|
+
if (decision.ok && decision.score >= 0.5) {
|
|
59544
|
+
templates.push(toCatalogTemplateEntry(template, decision));
|
|
59545
|
+
} else {
|
|
59546
|
+
blockedTemplates.push({ id: template.id, reason: decision.reason ?? `score=${decision.score.toFixed(2)} < 0.5` });
|
|
59547
|
+
}
|
|
59548
|
+
}
|
|
59549
|
+
templates.sort((a, b) => b.score - a.score);
|
|
59550
|
+
return { templates, blockedTemplates };
|
|
59551
|
+
}
|
|
59552
|
+
|
|
59553
|
+
// src/analyze-clarifications.ts
|
|
59554
|
+
function buildClarificationQuestions(fields, intent = "") {
|
|
59555
|
+
const questions = [];
|
|
59556
|
+
const measures = fields.filter((f) => f.role === "measure" || f.role === "score");
|
|
59557
|
+
const dimensions = fields.filter((f) => f.role === "dimension" || f.role === "status");
|
|
59558
|
+
const times = fields.filter((f) => f.role === "time");
|
|
59559
|
+
const precisionMode = /precise|accurate|decision|executive|老板|决策|精准/i.test(intent);
|
|
59560
|
+
if (measures.length === 0) {
|
|
59561
|
+
questions.push({
|
|
59562
|
+
id: "primary_measure",
|
|
59563
|
+
question: "Which field should be treated as the primary measure?",
|
|
59564
|
+
options: fields.map((f) => f.name),
|
|
59565
|
+
blocking: true,
|
|
59566
|
+
appliesTo: "measure"
|
|
59567
|
+
});
|
|
59568
|
+
} else if (measures.length > 1) {
|
|
59569
|
+
questions.push({
|
|
59570
|
+
id: "primary_measure",
|
|
59571
|
+
question: `This dataset has multiple measures. Analyze ${measures.map((f) => f.name).join(", ")}?`,
|
|
59572
|
+
options: measures.map((f) => f.name),
|
|
59573
|
+
blocking: precisionMode,
|
|
59574
|
+
appliesTo: "measure"
|
|
59575
|
+
});
|
|
59576
|
+
}
|
|
59577
|
+
if (dimensions.length > 1) {
|
|
59578
|
+
questions.push({
|
|
59579
|
+
id: "primary_dimension",
|
|
59580
|
+
question: `Which dimension should drive the main comparison: ${dimensions.map((f) => f.name).join(", ")}?`,
|
|
59581
|
+
options: dimensions.map((f) => f.name),
|
|
59582
|
+
blocking: false,
|
|
59583
|
+
appliesTo: "dimension"
|
|
59584
|
+
});
|
|
59585
|
+
}
|
|
59586
|
+
if (times.length > 1) {
|
|
59587
|
+
questions.push({
|
|
59588
|
+
id: "time_field",
|
|
59589
|
+
question: `Which time field should drive trend views: ${times.map((f) => f.name).join(", ")}?`,
|
|
59590
|
+
options: times.map((f) => f.name),
|
|
59591
|
+
blocking: false,
|
|
59592
|
+
appliesTo: "time"
|
|
59593
|
+
});
|
|
59594
|
+
}
|
|
59595
|
+
return questions;
|
|
59596
|
+
}
|
|
59597
|
+
|
|
58771
59598
|
// src/analyzer.ts
|
|
58772
59599
|
function analyzeDataset(dataset, options = {}) {
|
|
58773
59600
|
const profile = profileDataset(dataset);
|
|
@@ -58782,7 +59609,8 @@ function analyzeDataset(dataset, options = {}) {
|
|
|
58782
59609
|
const catalog = buildCatalog(fields, sampleWarnings, profile.rows, evidence);
|
|
58783
59610
|
const promptRules = buildPromptRules(catalog.charts, sampleWarnings);
|
|
58784
59611
|
const metricCandidates = buildMetricCandidates(fields, evidence);
|
|
58785
|
-
|
|
59612
|
+
const clarificationQuestions = buildClarificationQuestions(fields, options.intent ?? "");
|
|
59613
|
+
return { intent, fields, evidence, catalog, sampleWarnings, promptRules, metricCandidates, clarificationQuestions };
|
|
58786
59614
|
}
|
|
58787
59615
|
function buildAnalyzeFields(columns) {
|
|
58788
59616
|
return columns.map((col) => {
|
|
@@ -58816,7 +59644,6 @@ function refineRole(col) {
|
|
|
58816
59644
|
if (col.type === "string") {
|
|
58817
59645
|
const name = col.name.toLowerCase();
|
|
58818
59646
|
if (/\b(status|state|phase|stage|flag|type|category|tier)\b/.test(name) && col.distinctCount <= 10) return "status";
|
|
58819
|
-
if (col.role === "id") return "id";
|
|
58820
59647
|
return "dimension";
|
|
58821
59648
|
}
|
|
58822
59649
|
return "unknown";
|
|
@@ -58838,27 +59665,50 @@ function parseIntent(raw, fields, correctAssumption) {
|
|
|
58838
59665
|
const times = fields.filter((f) => f.role === "time");
|
|
58839
59666
|
let primaryMeasure = measures[0]?.name;
|
|
58840
59667
|
let primaryDimension = dimensions[0]?.name;
|
|
59668
|
+
let timeField = times[0]?.name;
|
|
58841
59669
|
if (correctAssumption) {
|
|
58842
|
-
const m = correctAssumption.match(/^primary_measure=(\w+)$/);
|
|
58843
|
-
const d = correctAssumption.match(/^primary_dimension=(\w+)$/);
|
|
59670
|
+
const m = correctAssumption.match(/^primary_measure=([\w-]+)$/);
|
|
59671
|
+
const d = correctAssumption.match(/^primary_dimension=([\w-]+)$/);
|
|
59672
|
+
const t = correctAssumption.match(/^time_field=([\w-]+)$/);
|
|
58844
59673
|
if (m) primaryMeasure = m[1];
|
|
58845
59674
|
if (d) primaryDimension = d[1];
|
|
59675
|
+
if (t) timeField = t[1];
|
|
58846
59676
|
}
|
|
58847
59677
|
const assumptions = [];
|
|
58848
59678
|
if (primaryMeasure) {
|
|
58849
|
-
|
|
58850
|
-
|
|
59679
|
+
assumptions.push({
|
|
59680
|
+
key: "primary_measure",
|
|
59681
|
+
value: primaryMeasure,
|
|
59682
|
+
confidence: measures.length > 1 ? 0.62 : 0.9,
|
|
59683
|
+
alternatives: measures.filter((f) => f.name !== primaryMeasure).map((f) => f.name),
|
|
59684
|
+
reason: measures.length > 1 ? "multiple numeric measures detected" : "single clear numeric measure"
|
|
59685
|
+
});
|
|
58851
59686
|
}
|
|
58852
59687
|
if (primaryDimension) {
|
|
58853
|
-
assumptions.push(
|
|
59688
|
+
assumptions.push({
|
|
59689
|
+
key: "primary_dimension",
|
|
59690
|
+
value: primaryDimension,
|
|
59691
|
+
confidence: dimensions.length > 1 ? 0.72 : 0.9,
|
|
59692
|
+
alternatives: dimensions.filter((f) => f.name !== primaryDimension).map((f) => f.name),
|
|
59693
|
+
reason: dimensions.length > 1 ? "multiple dimensions detected" : "single clear dimension"
|
|
59694
|
+
});
|
|
58854
59695
|
}
|
|
58855
59696
|
if (times.length > 0) {
|
|
58856
|
-
|
|
58857
|
-
|
|
58858
|
-
|
|
59697
|
+
assumptions.push({
|
|
59698
|
+
key: "time_field",
|
|
59699
|
+
value: timeField ?? times[0].name,
|
|
59700
|
+
confidence: times.length > 1 ? 0.7 : 0.9,
|
|
59701
|
+
alternatives: times.filter((f) => f.name !== timeField).map((f) => f.name),
|
|
59702
|
+
reason: times.length > 1 ? "multiple time fields detected" : "single clear time field"
|
|
59703
|
+
});
|
|
58859
59704
|
}
|
|
58860
59705
|
if (measures.length === 0) {
|
|
58861
|
-
assumptions.push(
|
|
59706
|
+
assumptions.push({
|
|
59707
|
+
key: "primary_measure",
|
|
59708
|
+
value: "",
|
|
59709
|
+
confidence: 0,
|
|
59710
|
+
reason: "no numeric measure detected"
|
|
59711
|
+
});
|
|
58862
59712
|
}
|
|
58863
59713
|
const rawLower = raw.toLowerCase();
|
|
58864
59714
|
const wantsTrend = /trend|over time|by month|by year/.test(rawLower);
|
|
@@ -58877,7 +59727,7 @@ function runStandardQueries(dataset, fields, sampleWarnings) {
|
|
|
58877
59727
|
if (measures.length > 0) {
|
|
58878
59728
|
const measureExpr = measures.slice(0, 4).map((m) => `sum(${m.name}) as total_${m.name}`).concat(["count(*) as row_count"]).join(", ");
|
|
58879
59729
|
const result = queryDataset(dataset.rows, { measure: measureExpr });
|
|
58880
|
-
if (result &&
|
|
59730
|
+
if (!isAgentError(result) && result.rows.length > 0) {
|
|
58881
59731
|
evidence.push({
|
|
58882
59732
|
id: "total",
|
|
58883
59733
|
query: `Total aggregates: ${measureExpr}`,
|
|
@@ -58892,7 +59742,7 @@ function runStandardQueries(dataset, fields, sampleWarnings) {
|
|
|
58892
59742
|
measure: measureExpr,
|
|
58893
59743
|
orderby: `total_${primaryMeasure.name} desc`
|
|
58894
59744
|
});
|
|
58895
|
-
if (result &&
|
|
59745
|
+
if (!isAgentError(result) && result.rows.length > 0) {
|
|
58896
59746
|
const totalVal = result.rows.reduce((s, r) => s + Number(r[`total_${primaryMeasure.name}`] ?? 0), 0);
|
|
58897
59747
|
const rows = result.rows.map((r) => {
|
|
58898
59748
|
const val = Number(r[`total_${primaryMeasure.name}`] ?? 0);
|
|
@@ -58913,7 +59763,7 @@ function runStandardQueries(dataset, fields, sampleWarnings) {
|
|
|
58913
59763
|
measure: measureExpr,
|
|
58914
59764
|
orderby: `${primaryTime.name} asc`
|
|
58915
59765
|
});
|
|
58916
|
-
if (result &&
|
|
59766
|
+
if (!isAgentError(result) && result.rows.length > 0) {
|
|
58917
59767
|
evidence.push({
|
|
58918
59768
|
id: "by_time",
|
|
58919
59769
|
query: `${primaryMeasure.name} by ${primaryTime.name} (ascending)`,
|
|
@@ -58937,7 +59787,7 @@ function runExtraQuery(dataset, extraQuery, existingCount) {
|
|
|
58937
59787
|
orderby: parts.orderby,
|
|
58938
59788
|
limit: parts.limit ? Number(parts.limit) : void 0
|
|
58939
59789
|
});
|
|
58940
|
-
if (
|
|
59790
|
+
if (isAgentError(result)) return null;
|
|
58941
59791
|
return {
|
|
58942
59792
|
id: `extra_${existingCount + 1}`,
|
|
58943
59793
|
query: `Custom query: ${extraQuery}`,
|
|
@@ -59027,7 +59877,8 @@ function buildCatalog(fields, warnings, rowCount, evidence) {
|
|
|
59027
59877
|
}
|
|
59028
59878
|
}
|
|
59029
59879
|
blocks.sort((a, b) => b.score - a.score);
|
|
59030
|
-
|
|
59880
|
+
const templateCatalog = buildTemplateCatalog(matchCtx);
|
|
59881
|
+
return { charts, blockedCharts, recommendedPlan, blocks, blockedBlocks, ...templateCatalog };
|
|
59031
59882
|
}
|
|
59032
59883
|
function buildRecommendedPlan(charts, fields) {
|
|
59033
59884
|
const plan = [];
|
|
@@ -59127,7 +59978,7 @@ function generatePatchHints(error51, spec, catalogCharts) {
|
|
|
59127
59978
|
switch (error51.code) {
|
|
59128
59979
|
case "UNSUPPORTED_TRANSFORM": {
|
|
59129
59980
|
const chartId = detail?.["chartId"];
|
|
59130
|
-
const chartIndex = spec
|
|
59981
|
+
const chartIndex = findChartIndex(spec, chartId);
|
|
59131
59982
|
if (chartIndex < 0) return void 0;
|
|
59132
59983
|
const transforms = spec.charts[chartIndex].data?.transform ?? [];
|
|
59133
59984
|
const tIdx = transforms.findIndex((t) => t.type === "filter");
|
|
@@ -59136,7 +59987,7 @@ function generatePatchHints(error51, spec, catalogCharts) {
|
|
|
59136
59987
|
}
|
|
59137
59988
|
case "BLOCKED_CHART_STRICT": {
|
|
59138
59989
|
const chartType = detail?.["chartType"];
|
|
59139
|
-
const chartIdx = spec
|
|
59990
|
+
const chartIdx = findChartIndex(spec, chartType);
|
|
59140
59991
|
if (chartIdx < 0 || !catalogCharts?.length) return void 0;
|
|
59141
59992
|
const suggestion = catalogCharts[0];
|
|
59142
59993
|
return [{ op: "replace", path: `/charts/${chartIdx}/type`, value: suggestion }];
|
|
@@ -59144,20 +59995,14 @@ function generatePatchHints(error51, spec, catalogCharts) {
|
|
|
59144
59995
|
case "DUPLICATE_CHART_ID": {
|
|
59145
59996
|
const dupId = detail?.["chartId"];
|
|
59146
59997
|
if (!dupId) return void 0;
|
|
59147
|
-
|
|
59148
|
-
for (let i = spec.charts.length - 1; i >= 0; i--) {
|
|
59149
|
-
if (spec.charts[i].id === dupId) {
|
|
59150
|
-
lastIdx = i;
|
|
59151
|
-
break;
|
|
59152
|
-
}
|
|
59153
|
-
}
|
|
59998
|
+
const lastIdx = findLastChartIndexById(spec, dupId);
|
|
59154
59999
|
if (lastIdx < 0) return void 0;
|
|
59155
60000
|
return [{ op: "replace", path: `/charts/${lastIdx}/id`, value: `${dupId}_2` }];
|
|
59156
60001
|
}
|
|
59157
60002
|
case "MISSING_ENCODING": {
|
|
59158
60003
|
const chartType = detail?.["chartType"];
|
|
59159
60004
|
const required2 = detail?.["requiredEncodings"];
|
|
59160
|
-
const chartIdx = spec
|
|
60005
|
+
const chartIdx = findChartIndex(spec, chartType);
|
|
59161
60006
|
if (chartIdx < 0 || !required2?.length) return void 0;
|
|
59162
60007
|
const existing = Object.keys(spec.charts[chartIdx].encoding ?? {});
|
|
59163
60008
|
const missing = required2.filter((enc) => !existing.includes(enc));
|
|
@@ -59203,12 +60048,6 @@ function collectWarningPatches(spec) {
|
|
|
59203
60048
|
}
|
|
59204
60049
|
return patches;
|
|
59205
60050
|
}
|
|
59206
|
-
function findChartIndex(spec, chartId) {
|
|
59207
|
-
if (!chartId) return spec.charts.length === 1 ? 0 : -1;
|
|
59208
|
-
const byId = spec.charts.findIndex((c) => c.id === chartId);
|
|
59209
|
-
if (byId >= 0) return byId;
|
|
59210
|
-
return spec.charts.findIndex((c) => c.type === chartId);
|
|
59211
|
-
}
|
|
59212
60051
|
|
|
59213
60052
|
// src/cli-help.ts
|
|
59214
60053
|
var COMMAND_HELP = {
|
|
@@ -59220,8 +60059,10 @@ Outputs context.json: intent, fields, evidence, catalog (blockedCharts), sampleW
|
|
|
59220
60059
|
Options:
|
|
59221
60060
|
--intent <text> Natural language description of the report goal
|
|
59222
60061
|
--output <file> Write context.json to this path (default: stdout)
|
|
60062
|
+
--compact Output compact context for agent consumption
|
|
60063
|
+
--verbose Keep full context output explicitly for debugging
|
|
59223
60064
|
--extra-query <expr> Custom query: "groupby=col;measure=sum(x) as y;filter=col>=val"
|
|
59224
|
-
--correct-assumption <expr> Override an assumption: "primary_measure=orders"
|
|
60065
|
+
--correct-assumption <expr> Override an assumption: "primary_measure=orders", "primary_dimension=region", or "time_field=date"
|
|
59225
60066
|
--sheet <name> Sheet name (Excel only)
|
|
59226
60067
|
--limit <n> Max rows to read
|
|
59227
60068
|
`,
|
|
@@ -59246,13 +60087,28 @@ Options:
|
|
|
59246
60087
|
--profile <file> Path to profile JSON (output of "profile")
|
|
59247
60088
|
--context <file> Path to context.json (output of "analyze") for catalog compliance and
|
|
59248
60089
|
$evidence path checks
|
|
59249
|
-
--strict
|
|
60090
|
+
--strict With --verify, treat verify warnings as hard errors; also hard-fails blockedChart violations
|
|
59250
60091
|
--verify Also check for forbidden words and missing sampleWarning caveats
|
|
59251
60092
|
--patch-hints Attach machine-readable JSON Patch hints to fixable errors
|
|
59252
60093
|
`,
|
|
59253
60094
|
catalog: `Usage: miao-viz catalog
|
|
59254
60095
|
|
|
59255
60096
|
List all available chart types and their required fields.
|
|
60097
|
+
`,
|
|
60098
|
+
block: `Usage: miao-viz block instantiate <block-id> --context <context.json> [--output <file>]
|
|
60099
|
+
|
|
60100
|
+
Instantiate a report block using full or compact analyze context.
|
|
60101
|
+
`,
|
|
60102
|
+
template: `Usage:
|
|
60103
|
+
miao-viz template list
|
|
60104
|
+
miao-viz template inspect <template-id>
|
|
60105
|
+
miao-viz template instantiate <template-id> --context <context.json> [--output <file>]
|
|
60106
|
+
|
|
60107
|
+
List, inspect, or instantiate report templates using full or compact analyze context.
|
|
60108
|
+
`,
|
|
60109
|
+
inspect: `Usage: miao-viz inspect --input <file> --spec <file> --context <context.json> --output <file>
|
|
60110
|
+
|
|
60111
|
+
Inspect chart transform pipelines and evidence usage for debugging.
|
|
59256
60112
|
`,
|
|
59257
60113
|
render: `Usage: miao-viz render --input <file> --spec <file> --output <file> [options]
|
|
59258
60114
|
|
|
@@ -59264,6 +60120,7 @@ Options:
|
|
|
59264
60120
|
--output <file> Output file path
|
|
59265
60121
|
--format <fmt> Output format: html, svg (default: html)
|
|
59266
60122
|
--theme <name> Theme: default, editorial, dark, minimal
|
|
60123
|
+
--context <file> Path to context.json (output of "analyze") \u2014 resolves $evidence: directives in insights[]
|
|
59267
60124
|
--interactive Force interactive runtime for HTML output
|
|
59268
60125
|
--no-interactive Force static HTML output
|
|
59269
60126
|
--sheet <name> Sheet name (Excel only)
|
|
@@ -59323,6 +60180,9 @@ Commands:
|
|
|
59323
60180
|
query Run an aggregation query to get real computed values
|
|
59324
60181
|
validate Validate a vizspec against a data profile
|
|
59325
60182
|
catalog List all available chart types
|
|
60183
|
+
block Instantiate report blocks from analyze context
|
|
60184
|
+
template List, inspect, or instantiate report templates
|
|
60185
|
+
inspect Inspect chart transforms and evidence usage
|
|
59326
60186
|
render Render a vizspec to HTML or SVG
|
|
59327
60187
|
deck Render a deck spec to HTML slides
|
|
59328
60188
|
article Convert a local article to an infographic artifact
|
|
@@ -59332,12 +60192,12 @@ Run "miao-viz <command> --help" for command-specific options.
|
|
|
59332
60192
|
}
|
|
59333
60193
|
|
|
59334
60194
|
// src/cli-block.ts
|
|
59335
|
-
var
|
|
60195
|
+
var YAML3 = __toESM(require_dist(), 1);
|
|
59336
60196
|
|
|
59337
60197
|
// src/cli-utils.ts
|
|
59338
60198
|
var import_node_fs3 = require("node:fs");
|
|
59339
60199
|
var import_node_path3 = require("node:path");
|
|
59340
|
-
var
|
|
60200
|
+
var YAML2 = __toESM(require_dist(), 1);
|
|
59341
60201
|
var BOOLEAN_FLAGS = /* @__PURE__ */ new Set([
|
|
59342
60202
|
"h",
|
|
59343
60203
|
"help",
|
|
@@ -59348,7 +60208,9 @@ var BOOLEAN_FLAGS = /* @__PURE__ */ new Set([
|
|
|
59348
60208
|
"strict",
|
|
59349
60209
|
"patch-hints",
|
|
59350
60210
|
"verify",
|
|
59351
|
-
"for-llm"
|
|
60211
|
+
"for-llm",
|
|
60212
|
+
"compact",
|
|
60213
|
+
"verbose"
|
|
59352
60214
|
]);
|
|
59353
60215
|
function parseArgs(argv) {
|
|
59354
60216
|
const [command, ...rest] = argv;
|
|
@@ -59410,7 +60272,7 @@ function printJson(value) {
|
|
|
59410
60272
|
function readSpec(file2) {
|
|
59411
60273
|
const text = (0, import_node_fs3.readFileSync)(file2, "utf8");
|
|
59412
60274
|
if (file2.endsWith(".json")) return JSON.parse(text);
|
|
59413
|
-
return
|
|
60275
|
+
return YAML2.parse(text);
|
|
59414
60276
|
}
|
|
59415
60277
|
function readJson(file2) {
|
|
59416
60278
|
return JSON.parse((0, import_node_fs3.readFileSync)(file2, "utf8"));
|
|
@@ -59443,6 +60305,11 @@ function runCatalog(args) {
|
|
|
59443
60305
|
const catalog = {
|
|
59444
60306
|
charts: CHART_CATALOG.map((c) => ({
|
|
59445
60307
|
id: c.id,
|
|
60308
|
+
compactFor: c.compactFor,
|
|
60309
|
+
requires: c.requires,
|
|
60310
|
+
transformRecipe: c.transformRecipe,
|
|
60311
|
+
avoid: c.avoid,
|
|
60312
|
+
insightPattern: c.insightPattern,
|
|
59446
60313
|
requiredEncodings: c.requiredEncodings,
|
|
59447
60314
|
allowedTransforms: c.allowedTransforms,
|
|
59448
60315
|
rules: c.rules.map((r) => r.expression),
|
|
@@ -59474,11 +60341,11 @@ function runBlock(args) {
|
|
|
59474
60341
|
if (isAgentError(contextPath)) return fail(contextPath);
|
|
59475
60342
|
const raw = readJson(contextPath);
|
|
59476
60343
|
const unwrapped = raw.ok === true ? raw.value : raw;
|
|
59477
|
-
const parsed =
|
|
59478
|
-
if (!parsed
|
|
59479
|
-
return fail(agentError("INVALID_CONTEXT",
|
|
60344
|
+
const parsed = parseAnalyzeContext(unwrapped);
|
|
60345
|
+
if (!parsed) {
|
|
60346
|
+
return fail(agentError("INVALID_CONTEXT", "context.json format is invalid.", { contextPath }));
|
|
59480
60347
|
}
|
|
59481
|
-
const ctx = parsed
|
|
60348
|
+
const ctx = parsed;
|
|
59482
60349
|
const resolver = getBlockById(blockId);
|
|
59483
60350
|
if (!resolver) {
|
|
59484
60351
|
return fail(agentError("UNKNOWN_BLOCK_ID", `Block id '${blockId}' not found. Available: ${BLOCK_REGISTRY.map((b) => b.id).join(", ")}`, { blockId, availableIds: BLOCK_REGISTRY.map((b) => b.id) }));
|
|
@@ -59505,7 +60372,7 @@ function runBlock(args) {
|
|
|
59505
60372
|
}
|
|
59506
60373
|
function buildBlockDraft(resolver, variables, score, compiled) {
|
|
59507
60374
|
const varSummary = Object.entries(variables).map(([k, v]) => `${k}=${v}`).join(", ");
|
|
59508
|
-
const yamlBody =
|
|
60375
|
+
const yamlBody = YAML3.stringify({ charts: compiled.charts, insights: compiled.insights ?? [] });
|
|
59509
60376
|
const checks = resolver.qualityChecks.map((c) => `# [ ] ${c}`).join("\n");
|
|
59510
60377
|
return [
|
|
59511
60378
|
`# Generated by: miao-viz block instantiate ${resolver.id}`,
|
|
@@ -59519,6 +60386,140 @@ function buildBlockDraft(resolver, variables, score, compiled) {
|
|
|
59519
60386
|
].join("\n");
|
|
59520
60387
|
}
|
|
59521
60388
|
|
|
60389
|
+
// src/cli-template.ts
|
|
60390
|
+
function runTemplate(args) {
|
|
60391
|
+
const subcommand = args.positional[0];
|
|
60392
|
+
if (subcommand === "list") {
|
|
60393
|
+
return {
|
|
60394
|
+
ok: true,
|
|
60395
|
+
value: TEMPLATE_REGISTRY.map(templateInfo)
|
|
60396
|
+
};
|
|
60397
|
+
}
|
|
60398
|
+
if (subcommand === "inspect") {
|
|
60399
|
+
const templateId = args.positional[1];
|
|
60400
|
+
if (!templateId) return fail(agentError("MISSING_INPUT", "Usage: miao-viz template inspect <template-id>"));
|
|
60401
|
+
const template = getTemplateById(templateId);
|
|
60402
|
+
if (!template) return fail(agentError("UNKNOWN_TEMPLATE_ID", `Template id '${templateId}' not found.`, { availableIds: TEMPLATE_REGISTRY.map((t) => t.id) }));
|
|
60403
|
+
return { ok: true, value: templateInfo(template) };
|
|
60404
|
+
}
|
|
60405
|
+
if (subcommand === "instantiate") {
|
|
60406
|
+
return runTemplateInstantiate(args);
|
|
60407
|
+
}
|
|
60408
|
+
return fail(agentError("UNKNOWN_SUBCOMMAND", `Unknown template subcommand '${subcommand ?? "(none)"}'. Supported: list, inspect, instantiate`, { subcommand }));
|
|
60409
|
+
}
|
|
60410
|
+
function runTemplateInstantiate(args) {
|
|
60411
|
+
const templateId = args.positional[1];
|
|
60412
|
+
if (!templateId) {
|
|
60413
|
+
return fail(agentError("MISSING_INPUT", "Usage: miao-viz template instantiate <template-id> --context <context.json> [--output <file>]"));
|
|
60414
|
+
}
|
|
60415
|
+
const contextPath = requiredFlag(args, "context");
|
|
60416
|
+
if (isAgentError(contextPath)) return fail(contextPath);
|
|
60417
|
+
const ctx = parseAnalyzeContext(readJson(contextPath));
|
|
60418
|
+
if (!ctx) return fail(agentError("INVALID_CONTEXT", "context.json format is invalid.", { contextPath }));
|
|
60419
|
+
const template = getTemplateById(templateId);
|
|
60420
|
+
if (!template) {
|
|
60421
|
+
return fail(agentError("UNKNOWN_TEMPLATE_ID", `Template id '${templateId}' not found.`, { templateId, availableIds: TEMPLATE_REGISTRY.map((t) => t.id) }));
|
|
60422
|
+
}
|
|
60423
|
+
const matchCtx = {
|
|
60424
|
+
fields: ctx.fields,
|
|
60425
|
+
evidence: ctx.evidence,
|
|
60426
|
+
catalog: ctx.catalog,
|
|
60427
|
+
sampleWarnings: ctx.sampleWarnings
|
|
60428
|
+
};
|
|
60429
|
+
const decision = template.canUse(matchCtx);
|
|
60430
|
+
if (!decision.ok) {
|
|
60431
|
+
return fail(agentError("TEMPLATE_NOT_APPLICABLE", `Template '${templateId}' cannot be used: ${decision.reason}`, { templateId, reason: decision.reason }));
|
|
60432
|
+
}
|
|
60433
|
+
const spec = template.instantiate(matchCtx);
|
|
60434
|
+
const yaml = [
|
|
60435
|
+
`# Generated by: miao-viz template instantiate ${template.id}`,
|
|
60436
|
+
`# Template: ${template.id} (score: ${decision.score.toFixed(2)})`,
|
|
60437
|
+
"# IMPORTANT: Review chart order and fill structured insights[] before validate",
|
|
60438
|
+
"",
|
|
60439
|
+
templateSpecToYaml(spec)
|
|
60440
|
+
].join("\n");
|
|
60441
|
+
const outputPath = stringFlag(args, "output");
|
|
60442
|
+
if (outputPath) {
|
|
60443
|
+
writeOutput(outputPath, yaml);
|
|
60444
|
+
return { ok: true, value: { output: outputPath, templateId, score: decision.score } };
|
|
60445
|
+
}
|
|
60446
|
+
return { ok: true, value: { yaml, templateId, score: decision.score } };
|
|
60447
|
+
}
|
|
60448
|
+
|
|
60449
|
+
// src/cli-inspect.ts
|
|
60450
|
+
function runInspect(args) {
|
|
60451
|
+
const input = requiredFlag(args, "input");
|
|
60452
|
+
const specPath = requiredFlag(args, "spec");
|
|
60453
|
+
const contextPath = requiredFlag(args, "context");
|
|
60454
|
+
const output = requiredFlag(args, "output");
|
|
60455
|
+
if (isAgentError(input)) return fail(input);
|
|
60456
|
+
if (isAgentError(specPath)) return fail(specPath);
|
|
60457
|
+
if (isAgentError(contextPath)) return fail(contextPath);
|
|
60458
|
+
if (isAgentError(output)) return fail(output);
|
|
60459
|
+
const dataset = loadDataset(input);
|
|
60460
|
+
if (isAgentError(dataset)) return fail(dataset);
|
|
60461
|
+
const spec = normalizeSpec(readSpec(specPath));
|
|
60462
|
+
if (isAgentError(spec)) return fail(spec);
|
|
60463
|
+
const context = parseAnalyzeContext(readJson(contextPath));
|
|
60464
|
+
if (!context) return fail(agentError("INVALID_CONTEXT", "context.json format is invalid.", { contextPath }));
|
|
60465
|
+
const profile = profileDataset(dataset.value);
|
|
60466
|
+
const charts = spec.charts.map((chart, index) => {
|
|
60467
|
+
const inspected = inspectChartTransforms(dataset.value.rows, chart);
|
|
60468
|
+
return {
|
|
60469
|
+
id: chart.id ?? `chart-${index + 1}`,
|
|
60470
|
+
type: chart.type,
|
|
60471
|
+
transforms: inspected.transforms,
|
|
60472
|
+
encoding: Object.fromEntries(Object.entries(chart.encoding ?? {}).map(([channel, enc]) => {
|
|
60473
|
+
const field = enc?.field;
|
|
60474
|
+
const profileField = field ? profile.columns.find((c) => c.name === field) : void 0;
|
|
60475
|
+
const finalMatch = field ? inspected.rows.some((row) => Object.prototype.hasOwnProperty.call(row, field)) : false;
|
|
60476
|
+
const sourceFieldMatch = Boolean(profileField);
|
|
60477
|
+
return [channel, {
|
|
60478
|
+
field,
|
|
60479
|
+
specType: enc?.type,
|
|
60480
|
+
resolvedType: profileField?.type ?? (finalMatch ? inferFinalFieldType(inspected.rows, field) : void 0),
|
|
60481
|
+
sourceFieldMatch,
|
|
60482
|
+
finalRowsMatch: finalMatch,
|
|
60483
|
+
match: sourceFieldMatch || finalMatch
|
|
60484
|
+
}];
|
|
60485
|
+
}))
|
|
60486
|
+
};
|
|
60487
|
+
});
|
|
60488
|
+
function inferFinalFieldType(rows, field) {
|
|
60489
|
+
if (!field) return void 0;
|
|
60490
|
+
const values = rows.map((row) => row[field]).filter((value) => value !== null && value !== void 0);
|
|
60491
|
+
if (values.length === 0) return void 0;
|
|
60492
|
+
if (values.every((value) => typeof value === "number")) return "number";
|
|
60493
|
+
if (values.every((value) => typeof value === "boolean")) return "boolean";
|
|
60494
|
+
if (values.every((value) => value instanceof Date)) return "date";
|
|
60495
|
+
return "string";
|
|
60496
|
+
}
|
|
60497
|
+
const referenced = /* @__PURE__ */ new Set();
|
|
60498
|
+
for (const insight of normalizeInsights(spec.insights)) {
|
|
60499
|
+
for (const id of insight.evidence) referenced.add(id);
|
|
60500
|
+
for (const ref of parseEvidenceRefs(insight.text)) referenced.add(ref.id);
|
|
60501
|
+
}
|
|
60502
|
+
for (const chart of spec.charts) {
|
|
60503
|
+
if (!chart.title) continue;
|
|
60504
|
+
for (const ref of parseEvidenceRefs(chart.title)) referenced.add(ref.id);
|
|
60505
|
+
}
|
|
60506
|
+
const defined = context.evidence.map((e) => e.id);
|
|
60507
|
+
const result = {
|
|
60508
|
+
ok: true,
|
|
60509
|
+
value: {
|
|
60510
|
+
charts,
|
|
60511
|
+
evidence: {
|
|
60512
|
+
defined,
|
|
60513
|
+
referenced: [...referenced],
|
|
60514
|
+
unreferenced: defined.filter((id) => !referenced.has(id))
|
|
60515
|
+
}
|
|
60516
|
+
}
|
|
60517
|
+
};
|
|
60518
|
+
writeOutput(output, `${JSON.stringify(result.value, null, 2)}
|
|
60519
|
+
`);
|
|
60520
|
+
return result;
|
|
60521
|
+
}
|
|
60522
|
+
|
|
59522
60523
|
// src/cli.ts
|
|
59523
60524
|
async function main() {
|
|
59524
60525
|
const args = parseArgs(process.argv.slice(2));
|
|
@@ -59547,6 +60548,14 @@ async function main() {
|
|
|
59547
60548
|
printJson(runBlock(args));
|
|
59548
60549
|
return;
|
|
59549
60550
|
}
|
|
60551
|
+
if (args.command === "template") {
|
|
60552
|
+
printJson(runTemplate(args));
|
|
60553
|
+
return;
|
|
60554
|
+
}
|
|
60555
|
+
if (args.command === "inspect") {
|
|
60556
|
+
printJson(runInspect(args));
|
|
60557
|
+
return;
|
|
60558
|
+
}
|
|
59550
60559
|
if (args.command === "render") {
|
|
59551
60560
|
printJson(runRender(args));
|
|
59552
60561
|
return;
|
|
@@ -59568,7 +60577,7 @@ async function main() {
|
|
|
59568
60577
|
return;
|
|
59569
60578
|
}
|
|
59570
60579
|
printJson(agentError("UNKNOWN_COMMAND", `Unknown command: ${args.command ?? "(none)"}`, {
|
|
59571
|
-
commands: ["profile", "validate", "catalog", "block", "render", "deck", "article", "query", "analyze"]
|
|
60580
|
+
commands: ["profile", "validate", "catalog", "block", "template", "inspect", "render", "deck", "article", "query", "analyze"]
|
|
59572
60581
|
}));
|
|
59573
60582
|
process.exitCode = 1;
|
|
59574
60583
|
} catch (error51) {
|
|
@@ -59613,15 +60622,15 @@ function runValidate(args) {
|
|
|
59613
60622
|
if (contextPath) {
|
|
59614
60623
|
const raw = readJson(contextPath);
|
|
59615
60624
|
const unwrapped = raw.ok === true ? raw.value : raw;
|
|
59616
|
-
const parsed =
|
|
59617
|
-
if (!parsed
|
|
60625
|
+
const parsed = parseAnalyzeContext(unwrapped);
|
|
60626
|
+
if (!parsed) {
|
|
59618
60627
|
return fail(agentError(
|
|
59619
60628
|
"INVALID_CONTEXT",
|
|
59620
|
-
|
|
60629
|
+
"context.json format is invalid.",
|
|
59621
60630
|
{ contextPath }
|
|
59622
60631
|
));
|
|
59623
60632
|
}
|
|
59624
|
-
context = parsed
|
|
60633
|
+
context = parsed;
|
|
59625
60634
|
}
|
|
59626
60635
|
const warnings = collectValidationWarnings(result.value, profile, context);
|
|
59627
60636
|
if (args.flags["strict"] === true && context) {
|
|
@@ -59653,6 +60662,10 @@ function runValidate(args) {
|
|
|
59653
60662
|
if (args.flags["verify"] === true) {
|
|
59654
60663
|
const verifyWarnings = collectVerifyWarnings(result.value, context);
|
|
59655
60664
|
warnings.push(...verifyWarnings);
|
|
60665
|
+
if (args.flags["strict"] === true) {
|
|
60666
|
+
const strictResult = strictVerifyError(verifyWarnings);
|
|
60667
|
+
if (isAgentError(strictResult)) return fail(strictResult);
|
|
60668
|
+
}
|
|
59656
60669
|
}
|
|
59657
60670
|
if (args.flags["patch-hints"] === true) {
|
|
59658
60671
|
const warningPatches = collectWarningPatches(result.value);
|
|
@@ -59682,6 +60695,17 @@ function runRender(args) {
|
|
|
59682
60695
|
if (isAgentError(normalized)) return fail(normalized);
|
|
59683
60696
|
const validation = validateReportSpec(normalized, profile, formats);
|
|
59684
60697
|
if (isAgentError(validation)) return fail(validation);
|
|
60698
|
+
const contextPath = stringFlag(args, "context");
|
|
60699
|
+
if (contextPath && validation.value.insights && validation.value.insights.length > 0) {
|
|
60700
|
+
const raw = readJson(contextPath);
|
|
60701
|
+
const unwrapped = raw.ok === true ? raw.value : raw;
|
|
60702
|
+
const parsed = parseAnalyzeContext(unwrapped);
|
|
60703
|
+
if (parsed) {
|
|
60704
|
+
validation.value.insights = validation.value.insights.map(
|
|
60705
|
+
(insight) => mapInsightText(insight, (text) => resolveDirectives(text, parsed.evidence))
|
|
60706
|
+
);
|
|
60707
|
+
}
|
|
60708
|
+
}
|
|
59685
60709
|
const themeFlag = stringFlag(args, "theme");
|
|
59686
60710
|
const interactive = args.flags["interactive"] === true ? true : args.flags["no-interactive"] === true ? false : void 0;
|
|
59687
60711
|
const written = [];
|
|
@@ -59762,7 +60786,8 @@ async function runAnalyze(args) {
|
|
|
59762
60786
|
extraQuery: stringFlag(args, "extra-query"),
|
|
59763
60787
|
correctAssumption: stringFlag(args, "correct-assumption")
|
|
59764
60788
|
});
|
|
59765
|
-
const
|
|
60789
|
+
const value = args.flags["compact"] === true ? toCompactAnalyzeContext(context) : context;
|
|
60790
|
+
const result = { ok: true, value };
|
|
59766
60791
|
const outputPath = stringFlag(args, "output");
|
|
59767
60792
|
if (outputPath) {
|
|
59768
60793
|
writeOutput(outputPath, `${JSON.stringify(result, null, 2)}
|