@miao-vision/cli 0.1.8 → 0.1.10
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 +1218 -129
- package/dist/examples/campaign-data.csv +33 -0
- package/dist/examples/campaign-report-deck.yaml +115 -0
- package/dist/examples/executive-overview-deck.yaml +137 -0
- package/dist/examples/executive-overview.csv +37 -0
- package/dist/types.ts +10 -1
- package/examples/campaign-data.csv +33 -0
- package/examples/campaign-report-deck.yaml +115 -0
- package/examples/executive-overview-deck.yaml +137 -0
- package/examples/executive-overview.csv +37 -0
- package/package.json +1 -1
- package/dist/examples/sales-dashboard.yaml +0 -54
- package/examples/sales-dashboard.yaml +0 -54
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
|
}
|
|
@@ -41482,7 +41719,19 @@ var defaultTheme = {
|
|
|
41482
41719
|
labelColor: "#475569"
|
|
41483
41720
|
},
|
|
41484
41721
|
css: `
|
|
41485
|
-
:root {
|
|
41722
|
+
:root {
|
|
41723
|
+
color-scheme: light;
|
|
41724
|
+
font-family: Inter, ui-sans-serif, system-ui, -apple-system, sans-serif;
|
|
41725
|
+
--mv-paper: #f8fafc;
|
|
41726
|
+
--mv-surface: #ffffff;
|
|
41727
|
+
--mv-border: #e2e8f0;
|
|
41728
|
+
--mv-ink: #0f172a;
|
|
41729
|
+
--mv-muted: #64748b;
|
|
41730
|
+
--mv-soft: #475569;
|
|
41731
|
+
--mv-brand: #2563eb;
|
|
41732
|
+
--mv-serif: Georgia, "Times New Roman", serif;
|
|
41733
|
+
--mv-mono: "SF Mono", "JetBrains Mono", Consolas, monospace;
|
|
41734
|
+
}
|
|
41486
41735
|
body { margin: 0; background: #f8fafc; color: #0f172a; }
|
|
41487
41736
|
.miao-viz-report { max-width: 1120px; margin: 0 auto; padding: 40px 24px 56px; }
|
|
41488
41737
|
header { margin-bottom: 28px; }
|
|
@@ -41523,6 +41772,7 @@ var editorialTheme = {
|
|
|
41523
41772
|
--mv-ink: #141413;
|
|
41524
41773
|
--mv-muted: #6b6a64;
|
|
41525
41774
|
--mv-soft-text: #504e49;
|
|
41775
|
+
--mv-soft: #504e49;
|
|
41526
41776
|
--mv-brand: #1b365d;
|
|
41527
41777
|
--mv-mono: "SF Mono", "JetBrains Mono", Consolas, monospace;
|
|
41528
41778
|
--mv-serif: Charter, Georgia, "Times New Roman", serif;
|
|
@@ -41587,6 +41837,7 @@ var darkTheme = {
|
|
|
41587
41837
|
--mv-ink: #e2e0d8;
|
|
41588
41838
|
--mv-muted: #737069;
|
|
41589
41839
|
--mv-soft-text: #a8a69f;
|
|
41840
|
+
--mv-soft: #a8a69f;
|
|
41590
41841
|
--mv-brand: #7eb8f7;
|
|
41591
41842
|
--mv-mono: "SF Mono", "JetBrains Mono", Consolas, monospace;
|
|
41592
41843
|
--mv-serif: Charter, Georgia, "Times New Roman", serif;
|
|
@@ -41644,7 +41895,19 @@ var minimalTheme = {
|
|
|
41644
41895
|
labelColor: "#6b7280"
|
|
41645
41896
|
},
|
|
41646
41897
|
css: `
|
|
41647
|
-
:root {
|
|
41898
|
+
:root {
|
|
41899
|
+
color-scheme: light;
|
|
41900
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif;
|
|
41901
|
+
--mv-paper: #ffffff;
|
|
41902
|
+
--mv-surface: #ffffff;
|
|
41903
|
+
--mv-border: #e5e7eb;
|
|
41904
|
+
--mv-ink: #111111;
|
|
41905
|
+
--mv-muted: #6b7280;
|
|
41906
|
+
--mv-soft: #374151;
|
|
41907
|
+
--mv-brand: #1d4ed8;
|
|
41908
|
+
--mv-serif: Georgia, "Times New Roman", serif;
|
|
41909
|
+
--mv-mono: "SF Mono", "JetBrains Mono", Consolas, monospace;
|
|
41910
|
+
}
|
|
41648
41911
|
body { margin: 0; background: #fff; color: #111; }
|
|
41649
41912
|
.miao-viz-report { max-width: 960px; margin: 0 auto; padding: 48px 24px 64px; }
|
|
41650
41913
|
header { margin-bottom: 32px; }
|
|
@@ -42117,12 +42380,38 @@ function escapeScriptJson(value) {
|
|
|
42117
42380
|
return JSON.stringify(value).replace(/</g, "\\u003c");
|
|
42118
42381
|
}
|
|
42119
42382
|
|
|
42383
|
+
// src/insight-utils.ts
|
|
42384
|
+
function normalizeInsight(insight) {
|
|
42385
|
+
if (typeof insight === "string") {
|
|
42386
|
+
return { text: insight, evidence: [], original: insight };
|
|
42387
|
+
}
|
|
42388
|
+
return {
|
|
42389
|
+
text: insight.text,
|
|
42390
|
+
evidence: insight.evidence ?? [],
|
|
42391
|
+
caveat: insight.caveat,
|
|
42392
|
+
severity: insight.severity,
|
|
42393
|
+
original: insight
|
|
42394
|
+
};
|
|
42395
|
+
}
|
|
42396
|
+
function normalizeInsights(insights) {
|
|
42397
|
+
return (insights ?? []).map(normalizeInsight);
|
|
42398
|
+
}
|
|
42399
|
+
function mapInsightText(insight, mapText) {
|
|
42400
|
+
if (typeof insight === "string") return mapText(insight);
|
|
42401
|
+
return { ...insight, text: mapText(insight.text) };
|
|
42402
|
+
}
|
|
42403
|
+
function insightPreview(text) {
|
|
42404
|
+
return `"${text.slice(0, 80)}${text.length > 80 ? "..." : ""}"`;
|
|
42405
|
+
}
|
|
42406
|
+
|
|
42120
42407
|
// src/html-export.ts
|
|
42121
42408
|
var INSIGHTS_CSS = `
|
|
42122
42409
|
.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
42410
|
.insights-label { font-size: 10px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.1em; opacity: 0.45; margin: 0 0 8px; }
|
|
42124
42411
|
.insights-list { margin: 0; padding: 0 0 0 18px; }
|
|
42125
42412
|
.insights-list li { margin: 5px 0; font-size: 13px; line-height: 1.55; opacity: 0.75; }
|
|
42413
|
+
.insight-warning { color: #8a4b00; }
|
|
42414
|
+
.insight-caveat { display: block; margin-top: 2px; font-size: 11px; opacity: 0.58; }
|
|
42126
42415
|
`;
|
|
42127
42416
|
function renderStaticHtml(spec, profile, rows, themeOverride, interactiveOptions = {}) {
|
|
42128
42417
|
const theme = getTheme(themeOverride ?? spec.theme);
|
|
@@ -42229,7 +42518,11 @@ function chartIdFor(chart, index) {
|
|
|
42229
42518
|
return chart.id ?? `chart-${index + 1}`;
|
|
42230
42519
|
}
|
|
42231
42520
|
function renderInsights(insights) {
|
|
42232
|
-
const items = insights.map((
|
|
42521
|
+
const items = normalizeInsights(insights).map((insight) => {
|
|
42522
|
+
const className = insight.severity === "warning" ? ' class="insight-warning"' : "";
|
|
42523
|
+
const caveat = insight.caveat ? `<span class="insight-caveat">${escapeHtml(insight.caveat)}</span>` : "";
|
|
42524
|
+
return `<li${className}>${escapeHtml(insight.text)}${caveat}</li>`;
|
|
42525
|
+
}).join("\n ");
|
|
42233
42526
|
return `<section class="report-insights">
|
|
42234
42527
|
<p class="insights-label">Key Observations</p>
|
|
42235
42528
|
<ul class="insights-list">
|
|
@@ -42239,9 +42532,9 @@ function renderInsights(insights) {
|
|
|
42239
42532
|
}
|
|
42240
42533
|
function buildCaption(chart) {
|
|
42241
42534
|
const parts = [];
|
|
42242
|
-
if (chart.encoding
|
|
42243
|
-
if (chart.encoding
|
|
42244
|
-
if (chart.encoding
|
|
42535
|
+
if (chart.encoding?.x?.field) parts.push(`x: ${chart.encoding.x.field}`);
|
|
42536
|
+
if (chart.encoding?.y?.field) parts.push(`y: ${chart.encoding.y.field}`);
|
|
42537
|
+
if (chart.encoding?.value?.field) parts.push(`value: ${chart.encoding.value.field}`);
|
|
42245
42538
|
const transforms = chart.data?.transform ?? [];
|
|
42246
42539
|
const agg = transforms.find((t) => t.type === "aggregate");
|
|
42247
42540
|
if (agg?.groupBy?.length) parts.push(`grouped by ${agg.groupBy.join(", ")}`);
|
|
@@ -56806,6 +57099,15 @@ var chartInteractionSchema = external_exports.object({
|
|
|
56806
57099
|
tooltip: external_exports.boolean().optional(),
|
|
56807
57100
|
select: external_exports.enum(["filter", "detail"]).optional()
|
|
56808
57101
|
});
|
|
57102
|
+
var insightSchema = external_exports.union([
|
|
57103
|
+
external_exports.string(),
|
|
57104
|
+
external_exports.object({
|
|
57105
|
+
text: external_exports.string().min(1),
|
|
57106
|
+
evidence: external_exports.array(external_exports.string().min(1)).optional(),
|
|
57107
|
+
caveat: external_exports.string().optional(),
|
|
57108
|
+
severity: external_exports.enum(["info", "warning"]).optional()
|
|
57109
|
+
})
|
|
57110
|
+
]);
|
|
56809
57111
|
var chartSpecSchema = external_exports.object({
|
|
56810
57112
|
id: external_exports.string().min(1).optional(),
|
|
56811
57113
|
type: external_exports.enum(MVP_CHART_TYPES),
|
|
@@ -56833,7 +57135,7 @@ var reportSpecSchema = external_exports.object({
|
|
|
56833
57135
|
interactions: external_exports.object({
|
|
56834
57136
|
globalFilters: external_exports.array(globalFilterSchema).optional()
|
|
56835
57137
|
}).optional(),
|
|
56836
|
-
insights: external_exports.array(
|
|
57138
|
+
insights: external_exports.array(insightSchema).optional(),
|
|
56837
57139
|
charts: external_exports.array(chartSpecSchema).min(1)
|
|
56838
57140
|
});
|
|
56839
57141
|
var singleOrReportSpecSchema = external_exports.union([
|
|
@@ -56889,12 +57191,23 @@ function resolveEvidencePath(evidence, id, path) {
|
|
|
56889
57191
|
}
|
|
56890
57192
|
return { found: false, value: void 0 };
|
|
56891
57193
|
}
|
|
57194
|
+
function resolveDirectives(text, evidence) {
|
|
57195
|
+
return text.replace(new RegExp(EVIDENCE_RE.source, "g"), (_, id, path) => {
|
|
57196
|
+
const { found, value } = resolveEvidencePath(evidence, id, path);
|
|
57197
|
+
return found ? String(value) : `[?${id}.${path}]`;
|
|
57198
|
+
});
|
|
57199
|
+
}
|
|
56892
57200
|
|
|
56893
57201
|
// src/chart-catalog.ts
|
|
56894
57202
|
var CHART_CATALOG = [
|
|
56895
57203
|
{
|
|
56896
57204
|
id: "bar",
|
|
56897
57205
|
displayName: "Bar Chart",
|
|
57206
|
+
compactFor: "rank,compare",
|
|
57207
|
+
requires: "dim(2-30)+measure",
|
|
57208
|
+
transformRecipe: "agg>sort(desc)>limit(10)",
|
|
57209
|
+
avoid: "dim>30,time>=3",
|
|
57210
|
+
insightPattern: "top {dimension} by {measure}",
|
|
56898
57211
|
requiredEncodings: ["x", "y"],
|
|
56899
57212
|
allowedTransforms: ["aggregate", "sort", "limit", "derive-month"],
|
|
56900
57213
|
bestFor: ["ranking by category", "comparison across dimensions", "top-N with limit"],
|
|
@@ -56917,6 +57230,25 @@ var CHART_CATALOG = [
|
|
|
56917
57230
|
return null;
|
|
56918
57231
|
}
|
|
56919
57232
|
},
|
|
57233
|
+
{
|
|
57234
|
+
code: "BAR_NO_AGGREGATE",
|
|
57235
|
+
severity: "warning",
|
|
57236
|
+
expression: "encoding.y.aggregate not set and no aggregate transform",
|
|
57237
|
+
message: "bar will plot one bar per raw row \u2014 unsorted and unaggregated. Add encoding.y.aggregate or data.transform aggregate + sort + limit.",
|
|
57238
|
+
validate: (chart) => {
|
|
57239
|
+
const hasAggTransform = (chart.data?.transform ?? []).some((t) => t.type === "aggregate");
|
|
57240
|
+
const hasEncodingAgg = !!chart.encoding?.y?.aggregate;
|
|
57241
|
+
if (!hasAggTransform && !hasEncodingAgg) {
|
|
57242
|
+
return {
|
|
57243
|
+
code: "BAR_NO_AGGREGATE",
|
|
57244
|
+
severity: "warning",
|
|
57245
|
+
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.`,
|
|
57246
|
+
chartId: chart.id
|
|
57247
|
+
};
|
|
57248
|
+
}
|
|
57249
|
+
return null;
|
|
57250
|
+
}
|
|
57251
|
+
},
|
|
56920
57252
|
{
|
|
56921
57253
|
code: "TOO_MANY_CATEGORIES",
|
|
56922
57254
|
severity: "warning",
|
|
@@ -56943,6 +57275,11 @@ var CHART_CATALOG = [
|
|
|
56943
57275
|
{
|
|
56944
57276
|
id: "line",
|
|
56945
57277
|
displayName: "Line Chart",
|
|
57278
|
+
compactFor: "trend",
|
|
57279
|
+
requires: "time(>=3)+measure",
|
|
57280
|
+
transformRecipe: "agg(time)>sort(asc)",
|
|
57281
|
+
avoid: "time<3,nominal_x",
|
|
57282
|
+
insightPattern: "{measure} over {time}",
|
|
56946
57283
|
requiredEncodings: ["x", "y"],
|
|
56947
57284
|
allowedTransforms: ["aggregate", "sort", "derive-month"],
|
|
56948
57285
|
bestFor: ["time series trends", "continuous data over ordered axis"],
|
|
@@ -56992,6 +57329,10 @@ var CHART_CATALOG = [
|
|
|
56992
57329
|
{
|
|
56993
57330
|
id: "area",
|
|
56994
57331
|
displayName: "Area Chart",
|
|
57332
|
+
compactFor: "trend,magnitude",
|
|
57333
|
+
requires: "time(>=3)+measure",
|
|
57334
|
+
transformRecipe: "agg(time)>sort(asc)",
|
|
57335
|
+
avoid: "time<3,negative_values,nominal_x",
|
|
56995
57336
|
requiredEncodings: ["x", "y"],
|
|
56996
57337
|
allowedTransforms: ["aggregate", "sort", "derive-month"],
|
|
56997
57338
|
bestFor: ["cumulative trends", "filled time series with visual mass"],
|
|
@@ -57041,11 +57382,35 @@ var CHART_CATALOG = [
|
|
|
57041
57382
|
{
|
|
57042
57383
|
id: "pie",
|
|
57043
57384
|
displayName: "Pie Chart",
|
|
57385
|
+
compactFor: "share,composition",
|
|
57386
|
+
requires: "dim(2-7)+measure",
|
|
57387
|
+
transformRecipe: "agg>sort(desc)>limit(7)",
|
|
57388
|
+
avoid: "dim>7,non_whole_values",
|
|
57389
|
+
insightPattern: "{dimension} share of {measure}",
|
|
57044
57390
|
requiredEncodings: ["label", "value"],
|
|
57045
57391
|
allowedTransforms: ["aggregate", "sort", "limit"],
|
|
57046
57392
|
bestFor: ["part-to-whole proportions", "share distribution with \u22647 categories"],
|
|
57047
57393
|
antiPatterns: ["more than 7 slices (use bar)", "values that do not sum to a meaningful whole"],
|
|
57048
57394
|
rules: [
|
|
57395
|
+
{
|
|
57396
|
+
code: "PIE_NO_AGGREGATE",
|
|
57397
|
+
severity: "warning",
|
|
57398
|
+
expression: "encoding.value.aggregate not set and no aggregate transform",
|
|
57399
|
+
message: "pie will show one slice per raw row. Add encoding.value.aggregate or data.transform aggregate + sort + limit.",
|
|
57400
|
+
validate: (chart) => {
|
|
57401
|
+
const hasAggTransform = (chart.data?.transform ?? []).some((t) => t.type === "aggregate");
|
|
57402
|
+
const hasEncodingAgg = !!chart.encoding?.value?.aggregate;
|
|
57403
|
+
if (!hasAggTransform && !hasEncodingAgg) {
|
|
57404
|
+
return {
|
|
57405
|
+
code: "PIE_NO_AGGREGATE",
|
|
57406
|
+
severity: "warning",
|
|
57407
|
+
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.`,
|
|
57408
|
+
chartId: chart.id
|
|
57409
|
+
};
|
|
57410
|
+
}
|
|
57411
|
+
return null;
|
|
57412
|
+
}
|
|
57413
|
+
},
|
|
57049
57414
|
{
|
|
57050
57415
|
code: "TOO_MANY_SLICES",
|
|
57051
57416
|
severity: "warning",
|
|
@@ -57072,6 +57437,10 @@ var CHART_CATALOG = [
|
|
|
57072
57437
|
{
|
|
57073
57438
|
id: "scatter",
|
|
57074
57439
|
displayName: "Scatter Chart",
|
|
57440
|
+
compactFor: "relationship,correlation",
|
|
57441
|
+
requires: "measure+measure",
|
|
57442
|
+
transformRecipe: "raw_or_limit",
|
|
57443
|
+
avoid: "single_measure,categorical_axis",
|
|
57075
57444
|
requiredEncodings: ["x", "y"],
|
|
57076
57445
|
allowedTransforms: ["sort", "limit"],
|
|
57077
57446
|
bestFor: ["correlation between two measures", "distribution of two quantitative variables"],
|
|
@@ -57081,6 +57450,11 @@ var CHART_CATALOG = [
|
|
|
57081
57450
|
{
|
|
57082
57451
|
id: "histogram",
|
|
57083
57452
|
displayName: "Histogram",
|
|
57453
|
+
compactFor: "distribution",
|
|
57454
|
+
requires: "measure+rows(>=20)",
|
|
57455
|
+
transformRecipe: "bin(numeric)>count",
|
|
57456
|
+
avoid: "rows<20,categorical_field",
|
|
57457
|
+
insightPattern: "{measure} distribution",
|
|
57084
57458
|
requiredEncodings: ["x"],
|
|
57085
57459
|
allowedTransforms: ["derive-month"],
|
|
57086
57460
|
bestFor: ["distribution of a single numeric field", "frequency across value bins"],
|
|
@@ -57091,6 +57465,11 @@ var CHART_CATALOG = [
|
|
|
57091
57465
|
{
|
|
57092
57466
|
id: "heatmap",
|
|
57093
57467
|
displayName: "Heatmap",
|
|
57468
|
+
compactFor: "matrix,density",
|
|
57469
|
+
requires: "dim+dim+measure",
|
|
57470
|
+
transformRecipe: "agg(dim,dim)>encode(value)",
|
|
57471
|
+
avoid: "single_dimension,sparse_matrix",
|
|
57472
|
+
insightPattern: "{measure} by {x_dimension} and {y_dimension}",
|
|
57094
57473
|
requiredEncodings: ["x", "y", "value"],
|
|
57095
57474
|
allowedTransforms: ["aggregate"],
|
|
57096
57475
|
bestFor: ["matrix of two dimensions vs one measure", "calendar-style density maps"],
|
|
@@ -57100,6 +57479,10 @@ var CHART_CATALOG = [
|
|
|
57100
57479
|
{
|
|
57101
57480
|
id: "table",
|
|
57102
57481
|
displayName: "Data Table",
|
|
57482
|
+
compactFor: "detail,high-cardinality,export",
|
|
57483
|
+
requires: "any_fields",
|
|
57484
|
+
transformRecipe: "sort_or_limit_optional",
|
|
57485
|
+
avoid: "too_many_columns_without_selection",
|
|
57103
57486
|
requiredEncodings: [],
|
|
57104
57487
|
allowedTransforms: ["aggregate", "sort", "limit", "derive-month"],
|
|
57105
57488
|
bestFor: ["high-cardinality dimension detail", "multi-measure comparison", "data export"],
|
|
@@ -57109,17 +57492,60 @@ var CHART_CATALOG = [
|
|
|
57109
57492
|
{
|
|
57110
57493
|
id: "bigvalue",
|
|
57111
57494
|
displayName: "Big Value (KPI Card)",
|
|
57495
|
+
compactFor: "kpi,summary",
|
|
57496
|
+
requires: "measure",
|
|
57497
|
+
transformRecipe: "agg(measure)>single_value",
|
|
57498
|
+
avoid: "raw_row_value,too_many_cards",
|
|
57499
|
+
insightPattern: "total {measure}",
|
|
57112
57500
|
requiredEncodings: ["value"],
|
|
57113
57501
|
allowedTransforms: ["aggregate"],
|
|
57114
57502
|
bestFor: ["single top-level KPI", "summary metric with optional delta"],
|
|
57115
57503
|
antiPatterns: ["more than 4 bigvalue cards per report (use kpigrid)", "showing a dimension value without a measure"],
|
|
57116
|
-
rules: [
|
|
57504
|
+
rules: [
|
|
57505
|
+
{
|
|
57506
|
+
code: "BIGVALUE_NO_REDUCTION",
|
|
57507
|
+
severity: "warning",
|
|
57508
|
+
expression: "no aggregate transform and encoding.value.aggregate not set",
|
|
57509
|
+
message: "bigvalue will show rows[0] raw value. Add encoding.value.aggregate or data.transform aggregate + limit: 1.",
|
|
57510
|
+
validate: (chart) => {
|
|
57511
|
+
const hasAggTransform = (chart.data?.transform ?? []).some((t) => t.type === "aggregate");
|
|
57512
|
+
const hasEncodingAgg = !!chart.encoding?.value?.aggregate;
|
|
57513
|
+
if (!hasAggTransform && !hasEncodingAgg) {
|
|
57514
|
+
const field = chart.encoding?.value?.field ?? "value";
|
|
57515
|
+
return {
|
|
57516
|
+
code: "BIGVALUE_NO_REDUCTION",
|
|
57517
|
+
severity: "warning",
|
|
57518
|
+
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.`,
|
|
57519
|
+
chartId: chart.id
|
|
57520
|
+
};
|
|
57521
|
+
}
|
|
57522
|
+
return null;
|
|
57523
|
+
}
|
|
57524
|
+
}
|
|
57525
|
+
]
|
|
57117
57526
|
}
|
|
57118
57527
|
];
|
|
57119
57528
|
function getCatalogItem(chartType) {
|
|
57120
57529
|
return CHART_CATALOG.find((c) => c.id === chartType);
|
|
57121
57530
|
}
|
|
57122
57531
|
|
|
57532
|
+
// src/spec-utils.ts
|
|
57533
|
+
function findChartIndex(spec, chartId) {
|
|
57534
|
+
if (!chartId) return spec.charts.length === 1 ? 0 : -1;
|
|
57535
|
+
const byId = spec.charts.findIndex((c) => c.id === chartId);
|
|
57536
|
+
if (byId >= 0) return byId;
|
|
57537
|
+
return spec.charts.findIndex((c) => c.type === chartId);
|
|
57538
|
+
}
|
|
57539
|
+
function findLastChartIndexById(spec, id) {
|
|
57540
|
+
for (let i = spec.charts.length - 1; i >= 0; i--) {
|
|
57541
|
+
if (spec.charts[i].id === id) return i;
|
|
57542
|
+
}
|
|
57543
|
+
return -1;
|
|
57544
|
+
}
|
|
57545
|
+
function countChartsByType(spec, type) {
|
|
57546
|
+
return spec.charts.filter((c) => c.type === type).length;
|
|
57547
|
+
}
|
|
57548
|
+
|
|
57123
57549
|
// src/spec-validator.ts
|
|
57124
57550
|
var FORBIDDEN_WORDS = [
|
|
57125
57551
|
{ pattern: /\b(trend|趋势)\b/i, word: "trend/\u8D8B\u52BF" },
|
|
@@ -57174,6 +57600,8 @@ function validateReportSpec(spec, profile, formats = ["html"], context) {
|
|
|
57174
57600
|
});
|
|
57175
57601
|
}
|
|
57176
57602
|
}
|
|
57603
|
+
const finalSchemaResult = validateEncodingFieldsInFinalSchema(chart);
|
|
57604
|
+
if (isAgentError(finalSchemaResult)) return finalSchemaResult;
|
|
57177
57605
|
}
|
|
57178
57606
|
return ok(parsed.data);
|
|
57179
57607
|
}
|
|
@@ -57195,7 +57623,7 @@ function collectValidationWarnings(spec, profile, context) {
|
|
|
57195
57623
|
`Report has ${spec.charts.length} charts (>6). Consider splitting into multiple sections or removing low-value charts.`
|
|
57196
57624
|
);
|
|
57197
57625
|
}
|
|
57198
|
-
const bigvalueCount = spec
|
|
57626
|
+
const bigvalueCount = countChartsByType(spec, "bigvalue");
|
|
57199
57627
|
if (bigvalueCount > 4) {
|
|
57200
57628
|
warnings.push(
|
|
57201
57629
|
`Report has ${bigvalueCount} bigvalue cards (>4). Use kpigrid for 5+ KPI cards to avoid visual clutter.`
|
|
@@ -57235,8 +57663,20 @@ function collectValidationWarnings(spec, profile, context) {
|
|
|
57235
57663
|
return warnings;
|
|
57236
57664
|
}
|
|
57237
57665
|
function validateEvidencePaths(spec, context) {
|
|
57666
|
+
const availableIds = context.evidence.map((e) => e.id);
|
|
57667
|
+
for (const insight of normalizeInsights(spec.insights)) {
|
|
57668
|
+
for (const evidenceId of insight.evidence) {
|
|
57669
|
+
if (!availableIds.includes(evidenceId)) {
|
|
57670
|
+
return agentError(
|
|
57671
|
+
"INSIGHT_EVIDENCE_NOT_FOUND",
|
|
57672
|
+
`insight.evidence id '${evidenceId}' not found in context.evidence. Available ids: ${availableIds.join(", ") || "(none)"}`,
|
|
57673
|
+
{ evidenceId, availableIds }
|
|
57674
|
+
);
|
|
57675
|
+
}
|
|
57676
|
+
}
|
|
57677
|
+
}
|
|
57238
57678
|
const texts = [
|
|
57239
|
-
...spec.insights
|
|
57679
|
+
...normalizeInsights(spec.insights).map((insight) => insight.text),
|
|
57240
57680
|
...spec.charts.map((c) => c.title).filter((t) => Boolean(t))
|
|
57241
57681
|
];
|
|
57242
57682
|
for (const text of texts) {
|
|
@@ -57255,22 +57695,29 @@ function validateEvidencePaths(spec, context) {
|
|
|
57255
57695
|
}
|
|
57256
57696
|
function collectVerifyWarnings(spec, context) {
|
|
57257
57697
|
const warnings = [];
|
|
57258
|
-
const insights = spec.insights
|
|
57259
|
-
for (const
|
|
57698
|
+
const insights = normalizeInsights(spec.insights);
|
|
57699
|
+
for (const insight of insights) {
|
|
57260
57700
|
for (const { pattern, word } of FORBIDDEN_WORDS) {
|
|
57261
|
-
if (pattern.test(text)) {
|
|
57701
|
+
if (pattern.test(insight.text)) {
|
|
57262
57702
|
warnings.push(
|
|
57263
|
-
`insight contains forbidden word '${word}':
|
|
57703
|
+
`insight contains forbidden word '${word}': ${insightPreview(insight.text)} \u2014 use only when backed by statistical evidence in context.evidence[]`
|
|
57264
57704
|
);
|
|
57265
57705
|
}
|
|
57266
57706
|
}
|
|
57707
|
+
if (typeof insight.original !== "string" && /\d/.test(insight.text) && insight.evidence.length === 0) {
|
|
57708
|
+
warnings.push(
|
|
57709
|
+
`insight contains numeric claim without structured evidence: ${insightPreview(insight.text)}`
|
|
57710
|
+
);
|
|
57711
|
+
}
|
|
57267
57712
|
}
|
|
57268
57713
|
if (context?.sampleWarnings.length) {
|
|
57269
57714
|
const CAVEAT_PATTERNS = [
|
|
57270
57715
|
/仅供参考|样本量|有限数据|based on.*rows?|N-row sample|limited data|small sample/i,
|
|
57271
57716
|
/环比变化|period.over.period/i
|
|
57272
57717
|
];
|
|
57273
|
-
const hasCaveat = insights.some(
|
|
57718
|
+
const hasCaveat = insights.some(
|
|
57719
|
+
(insight) => Boolean(insight.caveat) || CAVEAT_PATTERNS.some((p) => p.test(insight.text))
|
|
57720
|
+
);
|
|
57274
57721
|
if (insights.length > 0 && !hasCaveat) {
|
|
57275
57722
|
const codes = context.sampleWarnings.map((w) => w.code).join(", ");
|
|
57276
57723
|
warnings.push(
|
|
@@ -57280,6 +57727,24 @@ function collectVerifyWarnings(spec, context) {
|
|
|
57280
57727
|
}
|
|
57281
57728
|
return warnings;
|
|
57282
57729
|
}
|
|
57730
|
+
function strictVerifyError(warnings) {
|
|
57731
|
+
if (warnings.length === 0) return ok(void 0);
|
|
57732
|
+
const issues = warnings.map(warningToVerifyIssue);
|
|
57733
|
+
return agentError(
|
|
57734
|
+
"STRICT_VERIFY_FAILED",
|
|
57735
|
+
`Strict verify failed with ${warnings.length} warning(s).`,
|
|
57736
|
+
{ warnings, issues }
|
|
57737
|
+
);
|
|
57738
|
+
}
|
|
57739
|
+
function warningToVerifyIssue(message) {
|
|
57740
|
+
if (message.includes("forbidden word")) {
|
|
57741
|
+
return { code: "INSIGHT_FORBIDDEN_WORD_STRICT", message };
|
|
57742
|
+
}
|
|
57743
|
+
if (message.includes("numeric claim without structured evidence")) {
|
|
57744
|
+
return { code: "INSIGHT_NUMERIC_CLAIM_WITHOUT_EVIDENCE_STRICT", message };
|
|
57745
|
+
}
|
|
57746
|
+
return { code: "INSIGHT_MISSING_CAVEAT_STRICT", message };
|
|
57747
|
+
}
|
|
57283
57748
|
function validateChartType(chart) {
|
|
57284
57749
|
if (!MVP_CHART_TYPES.includes(chart.type)) {
|
|
57285
57750
|
return agentError("UNSUPPORTED_CHART_TYPE", `Chart type '${chart.type}' is not supported in the MVP.`, {
|
|
@@ -57365,6 +57830,38 @@ function validateTransforms(chart) {
|
|
|
57365
57830
|
}
|
|
57366
57831
|
return ok(chart);
|
|
57367
57832
|
}
|
|
57833
|
+
function simulateFinalSchema(chart) {
|
|
57834
|
+
let schema = null;
|
|
57835
|
+
for (const transform2 of chart.data?.transform ?? []) {
|
|
57836
|
+
if (transform2.type === "aggregate") {
|
|
57837
|
+
const next = /* @__PURE__ */ new Set();
|
|
57838
|
+
for (const field of transform2.groupBy ?? []) next.add(field);
|
|
57839
|
+
for (const measure of transform2.measures ?? []) next.add(measure.as);
|
|
57840
|
+
schema = next;
|
|
57841
|
+
} else if (transform2.type === "derive-month" && transform2.as && schema !== null) {
|
|
57842
|
+
schema.add(transform2.as);
|
|
57843
|
+
}
|
|
57844
|
+
}
|
|
57845
|
+
return schema;
|
|
57846
|
+
}
|
|
57847
|
+
function validateEncodingFieldsInFinalSchema(chart) {
|
|
57848
|
+
const finalSchema = simulateFinalSchema(chart);
|
|
57849
|
+
if (!finalSchema) return ok(chart);
|
|
57850
|
+
const chartLabel = chart.id ? `chart '${chart.id}'` : `${chart.type} chart`;
|
|
57851
|
+
const available = [...finalSchema].join(", ") || "(none)";
|
|
57852
|
+
for (const [channel, encoding] of Object.entries(chart.encoding ?? {})) {
|
|
57853
|
+
const field = encoding?.field;
|
|
57854
|
+
if (!field) continue;
|
|
57855
|
+
if (!finalSchema.has(field)) {
|
|
57856
|
+
return agentError(
|
|
57857
|
+
"ENCODING_FIELD_NOT_IN_FINAL_ROWS",
|
|
57858
|
+
`${chartLabel}: encoding.${channel}.field '${field}' does not exist in rows after transforms. Available fields after transforms: ${available}`,
|
|
57859
|
+
{ chartId: chart.id, channel, field, availableAfterTransforms: [...finalSchema] }
|
|
57860
|
+
);
|
|
57861
|
+
}
|
|
57862
|
+
}
|
|
57863
|
+
return ok(chart);
|
|
57864
|
+
}
|
|
57368
57865
|
function collectSourceFields(chart, derivedFields) {
|
|
57369
57866
|
const fields = /* @__PURE__ */ new Set();
|
|
57370
57867
|
for (const encoding of Object.values(chart.encoding ?? {})) {
|
|
@@ -57428,6 +57925,18 @@ var blockedBlockEntrySchema = external_exports.object({
|
|
|
57428
57925
|
id: external_exports.string().min(1),
|
|
57429
57926
|
reason: external_exports.string().min(1)
|
|
57430
57927
|
});
|
|
57928
|
+
var catalogTemplateEntrySchema = external_exports.object({
|
|
57929
|
+
id: external_exports.string().min(1),
|
|
57930
|
+
score: external_exports.number().min(0).max(1),
|
|
57931
|
+
bestFor: external_exports.array(external_exports.string()),
|
|
57932
|
+
requires: external_exports.array(external_exports.enum(["measure", "dimension", "time"])),
|
|
57933
|
+
blocks: external_exports.array(external_exports.string()),
|
|
57934
|
+
density: external_exports.enum(["compact", "medium", "full"])
|
|
57935
|
+
});
|
|
57936
|
+
var blockedTemplateEntrySchema = external_exports.object({
|
|
57937
|
+
id: external_exports.string().min(1),
|
|
57938
|
+
reason: external_exports.string().min(1)
|
|
57939
|
+
});
|
|
57431
57940
|
var analyzeCatalogSchema = external_exports.object({
|
|
57432
57941
|
charts: external_exports.array(external_exports.string().min(1)),
|
|
57433
57942
|
blockedCharts: external_exports.array(external_exports.object({
|
|
@@ -57439,7 +57948,9 @@ var analyzeCatalogSchema = external_exports.object({
|
|
|
57439
57948
|
note: external_exports.string().optional()
|
|
57440
57949
|
})),
|
|
57441
57950
|
blocks: external_exports.array(catalogBlockEntrySchema).optional(),
|
|
57442
|
-
blockedBlocks: external_exports.array(blockedBlockEntrySchema).optional()
|
|
57951
|
+
blockedBlocks: external_exports.array(blockedBlockEntrySchema).optional(),
|
|
57952
|
+
templates: external_exports.array(catalogTemplateEntrySchema).optional(),
|
|
57953
|
+
blockedTemplates: external_exports.array(blockedTemplateEntrySchema).optional()
|
|
57443
57954
|
});
|
|
57444
57955
|
var metricCandidateSchema = external_exports.object({
|
|
57445
57956
|
id: external_exports.string().min(1),
|
|
@@ -57450,15 +57961,35 @@ var metricCandidateSchema = external_exports.object({
|
|
|
57450
57961
|
confidence: external_exports.enum(["high", "medium"]),
|
|
57451
57962
|
caveat: external_exports.string().optional()
|
|
57452
57963
|
});
|
|
57964
|
+
var analyzeAssumptionSchema = external_exports.object({
|
|
57965
|
+
key: external_exports.enum(["primary_measure", "primary_dimension", "time_field"]),
|
|
57966
|
+
value: external_exports.string(),
|
|
57967
|
+
confidence: external_exports.number().min(0).max(1),
|
|
57968
|
+
alternatives: external_exports.array(external_exports.string()).optional(),
|
|
57969
|
+
reason: external_exports.string().optional()
|
|
57970
|
+
});
|
|
57971
|
+
var legacyAssumptionSchema = external_exports.string().transform((value) => ({
|
|
57972
|
+
key: value.includes("dimension") ? "primary_dimension" : value.includes("time") ? "time_field" : "primary_measure",
|
|
57973
|
+
value,
|
|
57974
|
+
confidence: 0.5,
|
|
57975
|
+
reason: "legacy string assumption"
|
|
57976
|
+
}));
|
|
57453
57977
|
var analyzeIntentSchema = external_exports.object({
|
|
57454
57978
|
raw: external_exports.string().min(1),
|
|
57455
57979
|
coverage: external_exports.enum(["full", "partial"]),
|
|
57456
|
-
assumptions: external_exports.array(external_exports.
|
|
57980
|
+
assumptions: external_exports.array(external_exports.union([analyzeAssumptionSchema, legacyAssumptionSchema]))
|
|
57457
57981
|
});
|
|
57458
57982
|
var analyzeSampleWarningSchema = external_exports.object({
|
|
57459
57983
|
code: external_exports.string().min(1),
|
|
57460
57984
|
message: external_exports.string().min(1)
|
|
57461
57985
|
});
|
|
57986
|
+
var clarificationQuestionSchema = external_exports.object({
|
|
57987
|
+
id: external_exports.string().min(1),
|
|
57988
|
+
question: external_exports.string().min(1),
|
|
57989
|
+
options: external_exports.array(external_exports.string()),
|
|
57990
|
+
blocking: external_exports.boolean(),
|
|
57991
|
+
appliesTo: external_exports.enum(["measure", "dimension", "time", "template"])
|
|
57992
|
+
});
|
|
57462
57993
|
var analyzeContextSchema = external_exports.object({
|
|
57463
57994
|
intent: analyzeIntentSchema,
|
|
57464
57995
|
fields: external_exports.array(analyzeFieldSchema),
|
|
@@ -57466,13 +57997,199 @@ var analyzeContextSchema = external_exports.object({
|
|
|
57466
57997
|
catalog: analyzeCatalogSchema,
|
|
57467
57998
|
sampleWarnings: external_exports.array(analyzeSampleWarningSchema),
|
|
57468
57999
|
promptRules: external_exports.array(external_exports.string()),
|
|
57469
|
-
metricCandidates: external_exports.array(metricCandidateSchema).optional()
|
|
57470
|
-
|
|
58000
|
+
metricCandidates: external_exports.array(metricCandidateSchema).optional(),
|
|
58001
|
+
clarificationQuestions: external_exports.array(clarificationQuestionSchema).optional()
|
|
58002
|
+
});
|
|
58003
|
+
var compactAnalyzeContextSchema = external_exports.object({
|
|
58004
|
+
format: external_exports.literal("compact-v1"),
|
|
58005
|
+
intent: external_exports.object({
|
|
58006
|
+
raw: external_exports.string(),
|
|
58007
|
+
coverage: external_exports.enum(["full", "partial"])
|
|
58008
|
+
}),
|
|
58009
|
+
assumptions: external_exports.array(external_exports.tuple([
|
|
58010
|
+
external_exports.enum(["primary_measure", "primary_dimension", "time_field"]),
|
|
58011
|
+
external_exports.string(),
|
|
58012
|
+
external_exports.number(),
|
|
58013
|
+
external_exports.array(external_exports.string()).nullable().optional()
|
|
58014
|
+
])),
|
|
58015
|
+
fields: external_exports.array(external_exports.tuple([
|
|
58016
|
+
external_exports.string(),
|
|
58017
|
+
external_exports.enum(["measure", "dimension", "time", "id", "status", "score", "unknown"]),
|
|
58018
|
+
external_exports.enum(["number", "string", "date", "boolean", "unknown"]),
|
|
58019
|
+
external_exports.number().nullable().optional(),
|
|
58020
|
+
external_exports.number().nullable().optional()
|
|
58021
|
+
])),
|
|
58022
|
+
evidence: external_exports.array(external_exports.tuple([external_exports.string(), external_exports.union([
|
|
58023
|
+
external_exports.record(external_exports.string(), external_exports.unknown()),
|
|
58024
|
+
external_exports.array(external_exports.record(external_exports.string(), external_exports.unknown()))
|
|
58025
|
+
])])),
|
|
58026
|
+
metricCandidates: external_exports.array(external_exports.tuple([
|
|
58027
|
+
external_exports.string(),
|
|
58028
|
+
external_exports.enum(["unit_average", "rate", "share", "period_change", "difference"]),
|
|
58029
|
+
external_exports.string(),
|
|
58030
|
+
external_exports.number().nullable().optional()
|
|
58031
|
+
])),
|
|
58032
|
+
catalog: external_exports.object({
|
|
58033
|
+
charts: external_exports.array(external_exports.string()),
|
|
58034
|
+
blockedCharts: external_exports.array(external_exports.tuple([external_exports.string(), external_exports.string()])),
|
|
58035
|
+
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(),
|
|
58036
|
+
blockedBlocks: external_exports.array(external_exports.tuple([external_exports.string(), external_exports.string()])).optional(),
|
|
58037
|
+
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(),
|
|
58038
|
+
blockedTemplates: external_exports.array(external_exports.tuple([external_exports.string(), external_exports.string()])).optional()
|
|
58039
|
+
}),
|
|
58040
|
+
warnings: external_exports.array(external_exports.tuple([external_exports.string(), external_exports.string()])),
|
|
58041
|
+
clarificationQuestions: external_exports.array(external_exports.tuple([
|
|
58042
|
+
external_exports.string(),
|
|
58043
|
+
external_exports.string(),
|
|
58044
|
+
external_exports.array(external_exports.string()),
|
|
58045
|
+
external_exports.boolean(),
|
|
58046
|
+
external_exports.enum(["measure", "dimension", "time", "template"])
|
|
58047
|
+
]))
|
|
58048
|
+
});
|
|
58049
|
+
function toCompactAnalyzeContext(ctx) {
|
|
58050
|
+
return {
|
|
58051
|
+
format: "compact-v1",
|
|
58052
|
+
intent: { raw: ctx.intent.raw, coverage: ctx.intent.coverage },
|
|
58053
|
+
assumptions: ctx.intent.assumptions.map((a) => [a.key, a.value, a.confidence, a.alternatives]),
|
|
58054
|
+
fields: ctx.fields.map((f) => [f.name, f.role, f.type, f.distinctCount, f.timePeriods]),
|
|
58055
|
+
evidence: ctx.evidence.map((e) => [e.id, e.values ?? e.rows ?? {}]),
|
|
58056
|
+
metricCandidates: (ctx.metricCandidates ?? []).map((m) => [m.id, m.type, m.formula, m.value]),
|
|
58057
|
+
catalog: {
|
|
58058
|
+
charts: ctx.catalog.charts,
|
|
58059
|
+
blockedCharts: ctx.catalog.blockedCharts.map((c) => [c.type, c.reason]),
|
|
58060
|
+
blocks: ctx.catalog.blocks?.map((b) => [b.id, b.score, b.density, b.charts]),
|
|
58061
|
+
blockedBlocks: ctx.catalog.blockedBlocks?.map((b) => [b.id, b.reason]),
|
|
58062
|
+
templates: ctx.catalog.templates?.map((t) => [t.id, t.score, t.density, t.blocks]),
|
|
58063
|
+
blockedTemplates: ctx.catalog.blockedTemplates?.map((t) => [t.id, t.reason])
|
|
58064
|
+
},
|
|
58065
|
+
warnings: ctx.sampleWarnings.map((w) => [w.code, w.message]),
|
|
58066
|
+
clarificationQuestions: (ctx.clarificationQuestions ?? []).map((q) => [
|
|
58067
|
+
q.id,
|
|
58068
|
+
q.question,
|
|
58069
|
+
q.options,
|
|
58070
|
+
q.blocking,
|
|
58071
|
+
q.appliesTo
|
|
58072
|
+
])
|
|
58073
|
+
};
|
|
58074
|
+
}
|
|
58075
|
+
function fromCompactAnalyzeContext(ctx) {
|
|
58076
|
+
return {
|
|
58077
|
+
intent: {
|
|
58078
|
+
raw: ctx.intent.raw,
|
|
58079
|
+
coverage: ctx.intent.coverage,
|
|
58080
|
+
assumptions: ctx.assumptions.map(([key, value, confidence, alternatives]) => ({
|
|
58081
|
+
key,
|
|
58082
|
+
value,
|
|
58083
|
+
confidence,
|
|
58084
|
+
alternatives: alternatives ?? void 0
|
|
58085
|
+
}))
|
|
58086
|
+
},
|
|
58087
|
+
fields: ctx.fields.map(([name, role, type, distinctCount, timePeriods]) => ({
|
|
58088
|
+
name,
|
|
58089
|
+
role,
|
|
58090
|
+
type,
|
|
58091
|
+
...distinctCount !== void 0 && distinctCount !== null ? { distinctCount } : {},
|
|
58092
|
+
...timePeriods !== void 0 && timePeriods !== null ? { timePeriods } : {}
|
|
58093
|
+
})),
|
|
58094
|
+
evidence: ctx.evidence.map(([id, value]) => ({
|
|
58095
|
+
id,
|
|
58096
|
+
query: `compact evidence: ${id}`,
|
|
58097
|
+
...Array.isArray(value) ? { rows: value } : { values: value }
|
|
58098
|
+
})),
|
|
58099
|
+
catalog: {
|
|
58100
|
+
charts: ctx.catalog.charts,
|
|
58101
|
+
blockedCharts: ctx.catalog.blockedCharts.map(([type, reason]) => ({ type, reason })),
|
|
58102
|
+
recommendedPlan: [],
|
|
58103
|
+
blocks: ctx.catalog.blocks?.map(([id, score, density, charts]) => ({
|
|
58104
|
+
id,
|
|
58105
|
+
score,
|
|
58106
|
+
description: "",
|
|
58107
|
+
bestFor: [],
|
|
58108
|
+
density,
|
|
58109
|
+
examplePrompt: "",
|
|
58110
|
+
charts,
|
|
58111
|
+
variables: {},
|
|
58112
|
+
qualityChecks: []
|
|
58113
|
+
})),
|
|
58114
|
+
blockedBlocks: ctx.catalog.blockedBlocks?.map(([id, reason]) => ({ id, reason })),
|
|
58115
|
+
templates: ctx.catalog.templates?.map(([id, score, density, blocks]) => ({
|
|
58116
|
+
id,
|
|
58117
|
+
score,
|
|
58118
|
+
bestFor: [],
|
|
58119
|
+
requires: [],
|
|
58120
|
+
blocks,
|
|
58121
|
+
density
|
|
58122
|
+
})),
|
|
58123
|
+
blockedTemplates: ctx.catalog.blockedTemplates?.map(([id, reason]) => ({ id, reason }))
|
|
58124
|
+
},
|
|
58125
|
+
sampleWarnings: ctx.warnings.map(([code, message]) => ({ code, message })),
|
|
58126
|
+
promptRules: [],
|
|
58127
|
+
metricCandidates: ctx.metricCandidates.map(([id, type, formula, value]) => ({
|
|
58128
|
+
id,
|
|
58129
|
+
type,
|
|
58130
|
+
label: id,
|
|
58131
|
+
formula,
|
|
58132
|
+
...value !== void 0 && value !== null ? { value } : {},
|
|
58133
|
+
confidence: "medium"
|
|
58134
|
+
})),
|
|
58135
|
+
clarificationQuestions: ctx.clarificationQuestions.map(([id, question, options, blocking, appliesTo]) => ({
|
|
58136
|
+
id,
|
|
58137
|
+
question,
|
|
58138
|
+
options,
|
|
58139
|
+
blocking,
|
|
58140
|
+
appliesTo
|
|
58141
|
+
}))
|
|
58142
|
+
};
|
|
58143
|
+
}
|
|
58144
|
+
function parseAnalyzeContext(value) {
|
|
58145
|
+
const unwrapped = value.ok === true ? value.value : value;
|
|
58146
|
+
const full = analyzeContextSchema.safeParse(unwrapped);
|
|
58147
|
+
if (full.success) return full.data;
|
|
58148
|
+
const compact = compactAnalyzeContextSchema.safeParse(unwrapped);
|
|
58149
|
+
if (compact.success) return fromCompactAnalyzeContext(compact.data);
|
|
58150
|
+
return null;
|
|
58151
|
+
}
|
|
57471
58152
|
|
|
57472
58153
|
// src/deck-layouts.ts
|
|
57473
58154
|
function withSize(chart, width, height) {
|
|
57474
58155
|
return { ...chart, style: { ...chart.style, width, height } };
|
|
57475
58156
|
}
|
|
58157
|
+
function formatMetricValue(num, fmt) {
|
|
58158
|
+
const hasCurrency = fmt.includes("$");
|
|
58159
|
+
const hasPercent = fmt.includes("%");
|
|
58160
|
+
const hasThousands = fmt.includes(",");
|
|
58161
|
+
const hasSiPrefix = fmt.includes("s");
|
|
58162
|
+
const precisionMatch = fmt.match(/\.(\d+)/);
|
|
58163
|
+
let precision = precisionMatch ? parseInt(precisionMatch[1], 10) : void 0;
|
|
58164
|
+
let value = num;
|
|
58165
|
+
let suffix = "";
|
|
58166
|
+
if (hasPercent) {
|
|
58167
|
+
value = num < 2 ? num * 100 : num;
|
|
58168
|
+
suffix = "%";
|
|
58169
|
+
if (precision === void 0) precision = 1;
|
|
58170
|
+
}
|
|
58171
|
+
if (hasSiPrefix) {
|
|
58172
|
+
const abs = Math.abs(value);
|
|
58173
|
+
if (abs >= 1e9) {
|
|
58174
|
+
value /= 1e9;
|
|
58175
|
+
suffix = "B" + suffix;
|
|
58176
|
+
} else if (abs >= 1e6) {
|
|
58177
|
+
value /= 1e6;
|
|
58178
|
+
suffix = "M" + suffix;
|
|
58179
|
+
} else if (abs >= 1e3) {
|
|
58180
|
+
value /= 1e3;
|
|
58181
|
+
suffix = "K" + suffix;
|
|
58182
|
+
}
|
|
58183
|
+
if (precision === void 0) precision = 0;
|
|
58184
|
+
}
|
|
58185
|
+
if (precision === void 0 && hasThousands) precision = 0;
|
|
58186
|
+
const fixed = precision !== void 0 ? value.toFixed(precision) : Math.round(value).toString();
|
|
58187
|
+
const [intPart, decPart] = fixed.split(".");
|
|
58188
|
+
const formattedInt = hasThousands ? intPart.replace(/\B(?=(\d{3})+(?!\d))/g, ",") : intPart;
|
|
58189
|
+
const formatted = decPart !== void 0 ? `${formattedInt}.${decPart}` : formattedInt;
|
|
58190
|
+
const prefix = hasCurrency ? "$" : "";
|
|
58191
|
+
return prefix + formatted + suffix;
|
|
58192
|
+
}
|
|
57476
58193
|
function resolveMetricValue(metric, rows) {
|
|
57477
58194
|
let raw = metric.value;
|
|
57478
58195
|
if (raw === void 0 && metric.data?.transform?.length) {
|
|
@@ -57482,10 +58199,7 @@ function resolveMetricValue(metric, rows) {
|
|
|
57482
58199
|
}
|
|
57483
58200
|
const num = Number(raw);
|
|
57484
58201
|
if (!Number.isFinite(num)) return String(raw ?? "\u2014");
|
|
57485
|
-
|
|
57486
|
-
if (fmt.includes("$")) return "$" + Math.round(num).toLocaleString();
|
|
57487
|
-
if (fmt.includes("%")) return (num < 2 ? (num * 100).toFixed(1) : num.toFixed(1)) + "%";
|
|
57488
|
-
return Math.round(num).toLocaleString();
|
|
58202
|
+
return formatMetricValue(num, metric.format ?? "");
|
|
57489
58203
|
}
|
|
57490
58204
|
function renderEyebrow(text) {
|
|
57491
58205
|
return `<div class="slide-eyebrow">${escapeHtml(text)}</div>`;
|
|
@@ -57511,21 +58225,22 @@ function pageFooter(index, _total, mark) {
|
|
|
57511
58225
|
<div class="slide-page-num">${String(index + 1).padStart(2, "0")}</div>`;
|
|
57512
58226
|
}
|
|
57513
58227
|
var COVER_DECO = `<svg viewBox="0 0 320 320" xmlns="http://www.w3.org/2000/svg" style="width:260px;opacity:0.9">
|
|
57514
|
-
<circle cx="160" cy="160" r="140" fill="none" stroke="#e5e3d8" stroke-width="1"/>
|
|
57515
|
-
<circle cx="160" cy="160" r="100" fill="none" stroke="#e5e3d8" stroke-width="1"/>
|
|
57516
|
-
<circle cx="160" cy="160" r="60" fill="#eef2f7" stroke="#1b365d" stroke-width="1.2"/>
|
|
57517
|
-
<path d="M160 20 A140 140 0 0 1 300 160" fill="none" stroke="#1b365d" stroke-width="2.2" stroke-linecap="round"/>
|
|
57518
|
-
<path d="M300 160 A140 140 0 0 1 160 300" fill="none" stroke="#6b6a64" stroke-width="1" stroke-linecap="round"/>
|
|
57519
|
-
<path d="M160 300 A140 140 0 0 1 20 160" fill="none" stroke="#6b6a64" stroke-width="1" stroke-linecap="round"/>
|
|
57520
|
-
<path d="M20 160 A140 140 0 0 1 160 20" fill="none" stroke="#6b6a64" stroke-width="1" stroke-linecap="round"/>
|
|
58228
|
+
<circle cx="160" cy="160" r="140" fill="none" stroke="var(--mv-border, #e5e3d8)" stroke-width="1"/>
|
|
58229
|
+
<circle cx="160" cy="160" r="100" fill="none" stroke="var(--mv-border, #e5e3d8)" stroke-width="1"/>
|
|
58230
|
+
<circle cx="160" cy="160" r="60" fill="var(--mv-surface, #eef2f7)" stroke="var(--mv-brand, #1b365d)" stroke-width="1.2"/>
|
|
58231
|
+
<path d="M160 20 A140 140 0 0 1 300 160" fill="none" stroke="var(--mv-brand, #1b365d)" stroke-width="2.2" stroke-linecap="round"/>
|
|
58232
|
+
<path d="M300 160 A140 140 0 0 1 160 300" fill="none" stroke="var(--mv-muted, #6b6a64)" stroke-width="1" stroke-linecap="round"/>
|
|
58233
|
+
<path d="M160 300 A140 140 0 0 1 20 160" fill="none" stroke="var(--mv-muted, #6b6a64)" stroke-width="1" stroke-linecap="round"/>
|
|
58234
|
+
<path d="M20 160 A140 140 0 0 1 160 20" fill="none" stroke="var(--mv-muted, #6b6a64)" stroke-width="1" stroke-linecap="round"/>
|
|
57521
58235
|
</svg>`;
|
|
57522
|
-
function renderCoverSlide(slide, _rows, _svg, index, total) {
|
|
58236
|
+
function renderCoverSlide(slide, _rows, _svg, index, total, deckDescription) {
|
|
58237
|
+
const subtitle = slide.claim ?? deckDescription;
|
|
57523
58238
|
return `<div class="slide">
|
|
57524
58239
|
<div class="slide-cover">
|
|
57525
58240
|
<div class="slide-cover-left">
|
|
57526
58241
|
<div class="slide-mark">miao-vision</div>
|
|
57527
58242
|
<h1>${escapeHtml(slide.title ?? "Presentation")}</h1>
|
|
57528
|
-
${
|
|
58243
|
+
${subtitle ? `<div class="sub">${escapeHtml(subtitle)}</div>` : ""}
|
|
57529
58244
|
<div class="line"></div>
|
|
57530
58245
|
<div class="meta">${escapeHtml(slide.eyebrow ?? (/* @__PURE__ */ new Date()).toISOString().slice(0, 10))}</div>
|
|
57531
58246
|
</div>
|
|
@@ -57611,18 +58326,6 @@ function renderEndingSlide(slide, _rows, _svg, index, total) {
|
|
|
57611
58326
|
|
|
57612
58327
|
// src/deck-renderer.ts
|
|
57613
58328
|
var SLIDE_CSS = `
|
|
57614
|
-
:root {
|
|
57615
|
-
--mv-paper: #f5f4ed;
|
|
57616
|
-
--mv-surface: #faf9f5;
|
|
57617
|
-
--mv-brand: #1b365d;
|
|
57618
|
-
--mv-ink: #141413;
|
|
57619
|
-
--mv-muted: #6b6a64;
|
|
57620
|
-
--mv-soft: #504e49;
|
|
57621
|
-
--mv-border: #e5e3d8;
|
|
57622
|
-
--mv-serif: Charter, Georgia, "Times New Roman", serif;
|
|
57623
|
-
--mv-mono: "SF Mono", "JetBrains Mono", Consolas, monospace;
|
|
57624
|
-
--slide-scale: 1;
|
|
57625
|
-
}
|
|
57626
58329
|
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
|
57627
58330
|
body { font-family: var(--mv-serif); -webkit-font-smoothing: antialiased; }
|
|
57628
58331
|
|
|
@@ -57714,7 +58417,7 @@ var SLIDE_CSS = `
|
|
|
57714
58417
|
.slide:last-child { break-after: auto; page-break-after: auto; }
|
|
57715
58418
|
.slide-nav { display: none !important; }
|
|
57716
58419
|
}
|
|
57717
|
-
@page { size: A4 landscape; margin: 0; background: #f5f4ed; }
|
|
58420
|
+
@page { size: A4 landscape; margin: 0; background: var(--mv-paper, #f5f4ed); }
|
|
57718
58421
|
`;
|
|
57719
58422
|
var SLIDE_JS = `
|
|
57720
58423
|
(function() {
|
|
@@ -57766,10 +58469,14 @@ var SLIDE_JS = `
|
|
|
57766
58469
|
goTo(0);
|
|
57767
58470
|
})();
|
|
57768
58471
|
`;
|
|
57769
|
-
function
|
|
58472
|
+
function extractRootVars(css) {
|
|
58473
|
+
const match = css.match(/:root\s*\{([\s\S]*?)\}/);
|
|
58474
|
+
return match ? `:root {${match[1]}}` : "";
|
|
58475
|
+
}
|
|
58476
|
+
function renderSlide(slide, rows, svgTheme, index, total, deckDescription) {
|
|
57770
58477
|
switch (slide.layout) {
|
|
57771
58478
|
case "cover":
|
|
57772
|
-
return renderCoverSlide(slide, rows, svgTheme, index, total);
|
|
58479
|
+
return renderCoverSlide(slide, rows, svgTheme, index, total, deckDescription);
|
|
57773
58480
|
case "title-only":
|
|
57774
58481
|
return renderTitleOnlySlide(slide, rows, svgTheme, index, total);
|
|
57775
58482
|
case "text-points":
|
|
@@ -57788,16 +58495,19 @@ function renderSlide(slide, rows, svgTheme, index, total) {
|
|
|
57788
58495
|
}
|
|
57789
58496
|
function renderDeckHtml(spec, rows, themeOverride) {
|
|
57790
58497
|
const theme = getTheme(themeOverride ?? spec.theme ?? "editorial");
|
|
58498
|
+
const themeRootVars = extractRootVars(theme.css);
|
|
57791
58499
|
const title = spec.title ?? "Presentation";
|
|
57792
58500
|
const total = spec.slides.length;
|
|
57793
|
-
const slidesHtml = spec.slides.map((slide, i) => renderSlide(slide, rows, theme.svg, i, total)).join("\n");
|
|
58501
|
+
const slidesHtml = spec.slides.map((slide, i) => renderSlide(slide, rows, theme.svg, i, total, spec.description)).join("\n");
|
|
57794
58502
|
return `<!doctype html>
|
|
57795
58503
|
<html lang="en">
|
|
57796
58504
|
<head>
|
|
57797
58505
|
<meta charset="utf-8" />
|
|
57798
58506
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
58507
|
+
${spec.description ? `<meta name="description" content="${escapeHtml(spec.description)}" />` : ""}
|
|
57799
58508
|
<title>${escapeHtml(title)}</title>
|
|
57800
58509
|
<style>${SLIDE_CSS}</style>
|
|
58510
|
+
${themeRootVars ? `<style id="miao-deck-theme">${themeRootVars}</style>` : ""}
|
|
57801
58511
|
</head>
|
|
57802
58512
|
<body class="present-mode">
|
|
57803
58513
|
<div class="slide-viewport">
|
|
@@ -57919,6 +58629,14 @@ function validateDeckSpecSemantics(spec) {
|
|
|
57919
58629
|
hint: hintForIssue(path, "at most 4 metrics")
|
|
57920
58630
|
});
|
|
57921
58631
|
}
|
|
58632
|
+
if (slide.charts && slide.charts.length > 1) {
|
|
58633
|
+
const path = `slides[${index}].charts`;
|
|
58634
|
+
errors.push({
|
|
58635
|
+
path,
|
|
58636
|
+
message: `${path}: A slide can include at most 1 chart.`,
|
|
58637
|
+
hint: hintForIssue(path, "at most 1 chart")
|
|
58638
|
+
});
|
|
58639
|
+
}
|
|
57922
58640
|
if (slide.layout === "table-full" && slide.charts?.[0] && slide.charts[0].type !== "table") {
|
|
57923
58641
|
const path = `slides[${index}].charts[0].type`;
|
|
57924
58642
|
errors.push({
|
|
@@ -57961,13 +58679,13 @@ function validateChartFields(chart, sourceFields, path) {
|
|
|
57961
58679
|
return deckFieldError(path, chart.type, `Chart type '${chart.type}' is not supported.`);
|
|
57962
58680
|
}
|
|
57963
58681
|
for (const encoding of REQUIRED_ENCODINGS[chart.type] ?? []) {
|
|
57964
|
-
if (!chart.encoding[encoding]?.field) {
|
|
58682
|
+
if (!chart.encoding?.[encoding]?.field) {
|
|
57965
58683
|
return deckFieldError(`${path}.encoding.${encoding}`, encoding, `Chart type '${chart.type}' requires encoding '${encoding}'.`);
|
|
57966
58684
|
}
|
|
57967
58685
|
}
|
|
57968
58686
|
const available = applyTransforms(chart.data?.transform ?? [], sourceFields, path);
|
|
57969
58687
|
if (isAgentError(available)) return available;
|
|
57970
|
-
for (const [encoding, spec] of Object.entries(chart.encoding)) {
|
|
58688
|
+
for (const [encoding, spec] of Object.entries(chart.encoding ?? {})) {
|
|
57971
58689
|
if (spec?.field && !available.value.has(spec.field)) {
|
|
57972
58690
|
return deckFieldError(`${path}.encoding.${encoding}.field`, spec.field, `Field '${spec.field}' is not available for this chart encoding.`);
|
|
57973
58691
|
}
|
|
@@ -58031,6 +58749,7 @@ function hintForIssue(path, message) {
|
|
|
58031
58749
|
if (message.includes("requires at least one chart")) return `Add a chart under ${path}.`;
|
|
58032
58750
|
if (message.includes("requires at least one metric")) return `Add one to four metrics under ${path}.`;
|
|
58033
58751
|
if (message.includes("at most 4 metrics")) return `Reduce ${path} to four metrics or split them across multiple slides.`;
|
|
58752
|
+
if (message.includes("at most 1 chart")) return `Reduce ${path} to a single chart or spread multiple charts across separate slides.`;
|
|
58034
58753
|
if (message.includes("only accepts a table chart")) return `Change ${path} to 'table' or use a chart-focused layout.`;
|
|
58035
58754
|
return `Check ${path} in the DeckSpec.`;
|
|
58036
58755
|
}
|
|
@@ -58064,7 +58783,7 @@ var infographicSpecSchema = external_exports.object({
|
|
|
58064
58783
|
inputFile: external_exports.string().default(""),
|
|
58065
58784
|
generatedAt: external_exports.string().default(() => (/* @__PURE__ */ new Date()).toISOString()),
|
|
58066
58785
|
wordCount: external_exports.number().int().min(0).default(0)
|
|
58067
|
-
}).default({})
|
|
58786
|
+
}).default(() => ({ inputFile: "", generatedAt: (/* @__PURE__ */ new Date()).toISOString(), wordCount: 0 }))
|
|
58068
58787
|
});
|
|
58069
58788
|
function loadInfographicSpec(file2) {
|
|
58070
58789
|
let raw;
|
|
@@ -58768,6 +59487,178 @@ function toCatalogBlockEntry(resolver, decision, ctx) {
|
|
|
58768
59487
|
};
|
|
58769
59488
|
}
|
|
58770
59489
|
|
|
59490
|
+
// src/report-template-registry.ts
|
|
59491
|
+
var YAML = __toESM(require_dist(), 1);
|
|
59492
|
+
function scoreForRequirements(ctx, requires) {
|
|
59493
|
+
for (const role of requires) {
|
|
59494
|
+
if (role === "measure" && !ctx.fields.some((f) => f.role === "measure" || f.role === "score")) {
|
|
59495
|
+
return { ok: false, score: 0, reason: "missing required measure" };
|
|
59496
|
+
}
|
|
59497
|
+
if (role === "dimension" && !ctx.fields.some((f) => f.role === "dimension" || f.role === "status")) {
|
|
59498
|
+
return { ok: false, score: 0, reason: "missing required dimension" };
|
|
59499
|
+
}
|
|
59500
|
+
if (role === "time") {
|
|
59501
|
+
const time3 = ctx.fields.find((f) => f.role === "time");
|
|
59502
|
+
if (!time3) return { ok: false, score: 0, reason: "missing required time" };
|
|
59503
|
+
const periods = time3.timePeriods ?? 0;
|
|
59504
|
+
if (periods < 3) return { ok: false, score: 0, reason: `timePeriods=${periods} < 3` };
|
|
59505
|
+
}
|
|
59506
|
+
}
|
|
59507
|
+
return { ok: true, score: Math.min(0.65 + requires.length * 0.1 + (ctx.evidence.length > 0 ? 0.05 : 0), 1) };
|
|
59508
|
+
}
|
|
59509
|
+
function compileBlocks(blockIds, ctx) {
|
|
59510
|
+
const charts = [];
|
|
59511
|
+
for (const id of blockIds) {
|
|
59512
|
+
const block = getBlockById(id);
|
|
59513
|
+
if (!block) continue;
|
|
59514
|
+
const decision = block.canUse(ctx);
|
|
59515
|
+
if (!decision.ok) continue;
|
|
59516
|
+
const variables = block.defaultVariables(ctx);
|
|
59517
|
+
charts.push(...block.compile(variables, ctx).charts);
|
|
59518
|
+
}
|
|
59519
|
+
const seen = /* @__PURE__ */ new Set();
|
|
59520
|
+
return {
|
|
59521
|
+
title: "Miao Vision Report",
|
|
59522
|
+
insights: [],
|
|
59523
|
+
charts: charts.map((chart) => {
|
|
59524
|
+
if (!chart.id || !seen.has(chart.id)) {
|
|
59525
|
+
if (chart.id) seen.add(chart.id);
|
|
59526
|
+
return chart;
|
|
59527
|
+
}
|
|
59528
|
+
const next = { ...chart, id: `${chart.id}_${seen.size + 1}` };
|
|
59529
|
+
seen.add(next.id);
|
|
59530
|
+
return next;
|
|
59531
|
+
})
|
|
59532
|
+
};
|
|
59533
|
+
}
|
|
59534
|
+
var TEMPLATE_REGISTRY = [
|
|
59535
|
+
{
|
|
59536
|
+
id: "snapshot-overview",
|
|
59537
|
+
bestFor: ["static comparison", "category ranking", "no time axis"],
|
|
59538
|
+
requires: ["measure", "dimension"],
|
|
59539
|
+
blocks: ["kpi-summary", "snapshot-ranking"],
|
|
59540
|
+
density: "compact",
|
|
59541
|
+
qualityNotes: ["Use for static comparisons without requiring a trend.", "Add sample caveats when sampleWarnings are present."],
|
|
59542
|
+
canUse: (ctx) => scoreForRequirements(ctx, ["measure", "dimension"]),
|
|
59543
|
+
instantiate: (ctx) => compileBlocks(["kpi-summary", "snapshot-ranking"], ctx)
|
|
59544
|
+
},
|
|
59545
|
+
{
|
|
59546
|
+
id: "trend-ranking-overview",
|
|
59547
|
+
bestFor: ["executive trend with category ranking", "monthly review"],
|
|
59548
|
+
requires: ["measure", "dimension", "time"],
|
|
59549
|
+
blocks: ["trend-ranking"],
|
|
59550
|
+
density: "full",
|
|
59551
|
+
qualityNotes: ["Requires at least 3 time periods.", "Combines KPI, trend, and ranking views."],
|
|
59552
|
+
canUse: (ctx) => scoreForRequirements(ctx, ["measure", "dimension", "time"]),
|
|
59553
|
+
instantiate: (ctx) => compileBlocks(["trend-ranking"], ctx)
|
|
59554
|
+
},
|
|
59555
|
+
{
|
|
59556
|
+
id: "full-detail-report",
|
|
59557
|
+
bestFor: ["comprehensive business review", "trend plus ranking plus table"],
|
|
59558
|
+
requires: ["measure", "dimension", "time"],
|
|
59559
|
+
blocks: ["full-detail-report"],
|
|
59560
|
+
density: "full",
|
|
59561
|
+
qualityNotes: ["Use for comprehensive reviews where detail table is acceptable.", "Keep total chart count within report limits."],
|
|
59562
|
+
canUse: (ctx) => scoreForRequirements(ctx, ["measure", "dimension", "time"]),
|
|
59563
|
+
instantiate: (ctx) => compileBlocks(["full-detail-report"], ctx)
|
|
59564
|
+
},
|
|
59565
|
+
{
|
|
59566
|
+
id: "composition-review",
|
|
59567
|
+
bestFor: ["share analysis", "part-to-whole breakdown", "composition report"],
|
|
59568
|
+
requires: ["measure", "dimension"],
|
|
59569
|
+
blocks: ["kpi-summary", "comparison-breakdown"],
|
|
59570
|
+
density: "medium",
|
|
59571
|
+
qualityNotes: ["Use for share or composition analysis.", "Avoid when the primary dimension has too many categories for pie."],
|
|
59572
|
+
canUse: (ctx) => scoreForRequirements(ctx, ["measure", "dimension"]),
|
|
59573
|
+
instantiate: (ctx) => compileBlocks(["kpi-summary", "comparison-breakdown"], ctx)
|
|
59574
|
+
}
|
|
59575
|
+
];
|
|
59576
|
+
function getTemplateById(id) {
|
|
59577
|
+
return TEMPLATE_REGISTRY.find((template) => template.id === id);
|
|
59578
|
+
}
|
|
59579
|
+
function templateInfo(template) {
|
|
59580
|
+
return {
|
|
59581
|
+
id: template.id,
|
|
59582
|
+
bestFor: template.bestFor,
|
|
59583
|
+
requires: template.requires,
|
|
59584
|
+
blocks: template.blocks,
|
|
59585
|
+
density: template.density,
|
|
59586
|
+
qualityNotes: template.qualityNotes
|
|
59587
|
+
};
|
|
59588
|
+
}
|
|
59589
|
+
function toCatalogTemplateEntry(resolver, decision) {
|
|
59590
|
+
return {
|
|
59591
|
+
id: resolver.id,
|
|
59592
|
+
score: decision.score,
|
|
59593
|
+
bestFor: resolver.bestFor,
|
|
59594
|
+
requires: resolver.requires,
|
|
59595
|
+
blocks: resolver.blocks,
|
|
59596
|
+
density: resolver.density
|
|
59597
|
+
};
|
|
59598
|
+
}
|
|
59599
|
+
function templateSpecToYaml(spec) {
|
|
59600
|
+
return YAML.stringify(spec);
|
|
59601
|
+
}
|
|
59602
|
+
function buildTemplateCatalog(ctx) {
|
|
59603
|
+
const templates = [];
|
|
59604
|
+
const blockedTemplates = [];
|
|
59605
|
+
for (const template of TEMPLATE_REGISTRY) {
|
|
59606
|
+
const decision = template.canUse(ctx);
|
|
59607
|
+
if (decision.ok && decision.score >= 0.5) {
|
|
59608
|
+
templates.push(toCatalogTemplateEntry(template, decision));
|
|
59609
|
+
} else {
|
|
59610
|
+
blockedTemplates.push({ id: template.id, reason: decision.reason ?? `score=${decision.score.toFixed(2)} < 0.5` });
|
|
59611
|
+
}
|
|
59612
|
+
}
|
|
59613
|
+
templates.sort((a, b) => b.score - a.score);
|
|
59614
|
+
return { templates, blockedTemplates };
|
|
59615
|
+
}
|
|
59616
|
+
|
|
59617
|
+
// src/analyze-clarifications.ts
|
|
59618
|
+
function buildClarificationQuestions(fields, intent = "") {
|
|
59619
|
+
const questions = [];
|
|
59620
|
+
const measures = fields.filter((f) => f.role === "measure" || f.role === "score");
|
|
59621
|
+
const dimensions = fields.filter((f) => f.role === "dimension" || f.role === "status");
|
|
59622
|
+
const times = fields.filter((f) => f.role === "time");
|
|
59623
|
+
const precisionMode = /precise|accurate|decision|executive|老板|决策|精准/i.test(intent);
|
|
59624
|
+
if (measures.length === 0) {
|
|
59625
|
+
questions.push({
|
|
59626
|
+
id: "primary_measure",
|
|
59627
|
+
question: "Which field should be treated as the primary measure?",
|
|
59628
|
+
options: fields.map((f) => f.name),
|
|
59629
|
+
blocking: true,
|
|
59630
|
+
appliesTo: "measure"
|
|
59631
|
+
});
|
|
59632
|
+
} else if (measures.length > 1) {
|
|
59633
|
+
questions.push({
|
|
59634
|
+
id: "primary_measure",
|
|
59635
|
+
question: `This dataset has multiple measures. Analyze ${measures.map((f) => f.name).join(", ")}?`,
|
|
59636
|
+
options: measures.map((f) => f.name),
|
|
59637
|
+
blocking: precisionMode,
|
|
59638
|
+
appliesTo: "measure"
|
|
59639
|
+
});
|
|
59640
|
+
}
|
|
59641
|
+
if (dimensions.length > 1) {
|
|
59642
|
+
questions.push({
|
|
59643
|
+
id: "primary_dimension",
|
|
59644
|
+
question: `Which dimension should drive the main comparison: ${dimensions.map((f) => f.name).join(", ")}?`,
|
|
59645
|
+
options: dimensions.map((f) => f.name),
|
|
59646
|
+
blocking: false,
|
|
59647
|
+
appliesTo: "dimension"
|
|
59648
|
+
});
|
|
59649
|
+
}
|
|
59650
|
+
if (times.length > 1) {
|
|
59651
|
+
questions.push({
|
|
59652
|
+
id: "time_field",
|
|
59653
|
+
question: `Which time field should drive trend views: ${times.map((f) => f.name).join(", ")}?`,
|
|
59654
|
+
options: times.map((f) => f.name),
|
|
59655
|
+
blocking: false,
|
|
59656
|
+
appliesTo: "time"
|
|
59657
|
+
});
|
|
59658
|
+
}
|
|
59659
|
+
return questions;
|
|
59660
|
+
}
|
|
59661
|
+
|
|
58771
59662
|
// src/analyzer.ts
|
|
58772
59663
|
function analyzeDataset(dataset, options = {}) {
|
|
58773
59664
|
const profile = profileDataset(dataset);
|
|
@@ -58782,7 +59673,8 @@ function analyzeDataset(dataset, options = {}) {
|
|
|
58782
59673
|
const catalog = buildCatalog(fields, sampleWarnings, profile.rows, evidence);
|
|
58783
59674
|
const promptRules = buildPromptRules(catalog.charts, sampleWarnings);
|
|
58784
59675
|
const metricCandidates = buildMetricCandidates(fields, evidence);
|
|
58785
|
-
|
|
59676
|
+
const clarificationQuestions = buildClarificationQuestions(fields, options.intent ?? "");
|
|
59677
|
+
return { intent, fields, evidence, catalog, sampleWarnings, promptRules, metricCandidates, clarificationQuestions };
|
|
58786
59678
|
}
|
|
58787
59679
|
function buildAnalyzeFields(columns) {
|
|
58788
59680
|
return columns.map((col) => {
|
|
@@ -58816,7 +59708,6 @@ function refineRole(col) {
|
|
|
58816
59708
|
if (col.type === "string") {
|
|
58817
59709
|
const name = col.name.toLowerCase();
|
|
58818
59710
|
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
59711
|
return "dimension";
|
|
58821
59712
|
}
|
|
58822
59713
|
return "unknown";
|
|
@@ -58838,27 +59729,50 @@ function parseIntent(raw, fields, correctAssumption) {
|
|
|
58838
59729
|
const times = fields.filter((f) => f.role === "time");
|
|
58839
59730
|
let primaryMeasure = measures[0]?.name;
|
|
58840
59731
|
let primaryDimension = dimensions[0]?.name;
|
|
59732
|
+
let timeField = times[0]?.name;
|
|
58841
59733
|
if (correctAssumption) {
|
|
58842
|
-
const m = correctAssumption.match(/^primary_measure=(\w+)$/);
|
|
58843
|
-
const d = correctAssumption.match(/^primary_dimension=(\w+)$/);
|
|
59734
|
+
const m = correctAssumption.match(/^primary_measure=([\w-]+)$/);
|
|
59735
|
+
const d = correctAssumption.match(/^primary_dimension=([\w-]+)$/);
|
|
59736
|
+
const t = correctAssumption.match(/^time_field=([\w-]+)$/);
|
|
58844
59737
|
if (m) primaryMeasure = m[1];
|
|
58845
59738
|
if (d) primaryDimension = d[1];
|
|
59739
|
+
if (t) timeField = t[1];
|
|
58846
59740
|
}
|
|
58847
59741
|
const assumptions = [];
|
|
58848
59742
|
if (primaryMeasure) {
|
|
58849
|
-
|
|
58850
|
-
|
|
59743
|
+
assumptions.push({
|
|
59744
|
+
key: "primary_measure",
|
|
59745
|
+
value: primaryMeasure,
|
|
59746
|
+
confidence: measures.length > 1 ? 0.62 : 0.9,
|
|
59747
|
+
alternatives: measures.filter((f) => f.name !== primaryMeasure).map((f) => f.name),
|
|
59748
|
+
reason: measures.length > 1 ? "multiple numeric measures detected" : "single clear numeric measure"
|
|
59749
|
+
});
|
|
58851
59750
|
}
|
|
58852
59751
|
if (primaryDimension) {
|
|
58853
|
-
assumptions.push(
|
|
59752
|
+
assumptions.push({
|
|
59753
|
+
key: "primary_dimension",
|
|
59754
|
+
value: primaryDimension,
|
|
59755
|
+
confidence: dimensions.length > 1 ? 0.72 : 0.9,
|
|
59756
|
+
alternatives: dimensions.filter((f) => f.name !== primaryDimension).map((f) => f.name),
|
|
59757
|
+
reason: dimensions.length > 1 ? "multiple dimensions detected" : "single clear dimension"
|
|
59758
|
+
});
|
|
58854
59759
|
}
|
|
58855
59760
|
if (times.length > 0) {
|
|
58856
|
-
|
|
58857
|
-
|
|
58858
|
-
|
|
59761
|
+
assumptions.push({
|
|
59762
|
+
key: "time_field",
|
|
59763
|
+
value: timeField ?? times[0].name,
|
|
59764
|
+
confidence: times.length > 1 ? 0.7 : 0.9,
|
|
59765
|
+
alternatives: times.filter((f) => f.name !== timeField).map((f) => f.name),
|
|
59766
|
+
reason: times.length > 1 ? "multiple time fields detected" : "single clear time field"
|
|
59767
|
+
});
|
|
58859
59768
|
}
|
|
58860
59769
|
if (measures.length === 0) {
|
|
58861
|
-
assumptions.push(
|
|
59770
|
+
assumptions.push({
|
|
59771
|
+
key: "primary_measure",
|
|
59772
|
+
value: "",
|
|
59773
|
+
confidence: 0,
|
|
59774
|
+
reason: "no numeric measure detected"
|
|
59775
|
+
});
|
|
58862
59776
|
}
|
|
58863
59777
|
const rawLower = raw.toLowerCase();
|
|
58864
59778
|
const wantsTrend = /trend|over time|by month|by year/.test(rawLower);
|
|
@@ -58877,7 +59791,7 @@ function runStandardQueries(dataset, fields, sampleWarnings) {
|
|
|
58877
59791
|
if (measures.length > 0) {
|
|
58878
59792
|
const measureExpr = measures.slice(0, 4).map((m) => `sum(${m.name}) as total_${m.name}`).concat(["count(*) as row_count"]).join(", ");
|
|
58879
59793
|
const result = queryDataset(dataset.rows, { measure: measureExpr });
|
|
58880
|
-
if (result &&
|
|
59794
|
+
if (!isAgentError(result) && result.rows.length > 0) {
|
|
58881
59795
|
evidence.push({
|
|
58882
59796
|
id: "total",
|
|
58883
59797
|
query: `Total aggregates: ${measureExpr}`,
|
|
@@ -58892,7 +59806,7 @@ function runStandardQueries(dataset, fields, sampleWarnings) {
|
|
|
58892
59806
|
measure: measureExpr,
|
|
58893
59807
|
orderby: `total_${primaryMeasure.name} desc`
|
|
58894
59808
|
});
|
|
58895
|
-
if (result &&
|
|
59809
|
+
if (!isAgentError(result) && result.rows.length > 0) {
|
|
58896
59810
|
const totalVal = result.rows.reduce((s, r) => s + Number(r[`total_${primaryMeasure.name}`] ?? 0), 0);
|
|
58897
59811
|
const rows = result.rows.map((r) => {
|
|
58898
59812
|
const val = Number(r[`total_${primaryMeasure.name}`] ?? 0);
|
|
@@ -58913,7 +59827,7 @@ function runStandardQueries(dataset, fields, sampleWarnings) {
|
|
|
58913
59827
|
measure: measureExpr,
|
|
58914
59828
|
orderby: `${primaryTime.name} asc`
|
|
58915
59829
|
});
|
|
58916
|
-
if (result &&
|
|
59830
|
+
if (!isAgentError(result) && result.rows.length > 0) {
|
|
58917
59831
|
evidence.push({
|
|
58918
59832
|
id: "by_time",
|
|
58919
59833
|
query: `${primaryMeasure.name} by ${primaryTime.name} (ascending)`,
|
|
@@ -58937,7 +59851,7 @@ function runExtraQuery(dataset, extraQuery, existingCount) {
|
|
|
58937
59851
|
orderby: parts.orderby,
|
|
58938
59852
|
limit: parts.limit ? Number(parts.limit) : void 0
|
|
58939
59853
|
});
|
|
58940
|
-
if (
|
|
59854
|
+
if (isAgentError(result)) return null;
|
|
58941
59855
|
return {
|
|
58942
59856
|
id: `extra_${existingCount + 1}`,
|
|
58943
59857
|
query: `Custom query: ${extraQuery}`,
|
|
@@ -59027,7 +59941,8 @@ function buildCatalog(fields, warnings, rowCount, evidence) {
|
|
|
59027
59941
|
}
|
|
59028
59942
|
}
|
|
59029
59943
|
blocks.sort((a, b) => b.score - a.score);
|
|
59030
|
-
|
|
59944
|
+
const templateCatalog = buildTemplateCatalog(matchCtx);
|
|
59945
|
+
return { charts, blockedCharts, recommendedPlan, blocks, blockedBlocks, ...templateCatalog };
|
|
59031
59946
|
}
|
|
59032
59947
|
function buildRecommendedPlan(charts, fields) {
|
|
59033
59948
|
const plan = [];
|
|
@@ -59127,7 +60042,7 @@ function generatePatchHints(error51, spec, catalogCharts) {
|
|
|
59127
60042
|
switch (error51.code) {
|
|
59128
60043
|
case "UNSUPPORTED_TRANSFORM": {
|
|
59129
60044
|
const chartId = detail?.["chartId"];
|
|
59130
|
-
const chartIndex = spec
|
|
60045
|
+
const chartIndex = findChartIndex(spec, chartId);
|
|
59131
60046
|
if (chartIndex < 0) return void 0;
|
|
59132
60047
|
const transforms = spec.charts[chartIndex].data?.transform ?? [];
|
|
59133
60048
|
const tIdx = transforms.findIndex((t) => t.type === "filter");
|
|
@@ -59136,7 +60051,7 @@ function generatePatchHints(error51, spec, catalogCharts) {
|
|
|
59136
60051
|
}
|
|
59137
60052
|
case "BLOCKED_CHART_STRICT": {
|
|
59138
60053
|
const chartType = detail?.["chartType"];
|
|
59139
|
-
const chartIdx = spec
|
|
60054
|
+
const chartIdx = findChartIndex(spec, chartType);
|
|
59140
60055
|
if (chartIdx < 0 || !catalogCharts?.length) return void 0;
|
|
59141
60056
|
const suggestion = catalogCharts[0];
|
|
59142
60057
|
return [{ op: "replace", path: `/charts/${chartIdx}/type`, value: suggestion }];
|
|
@@ -59144,20 +60059,14 @@ function generatePatchHints(error51, spec, catalogCharts) {
|
|
|
59144
60059
|
case "DUPLICATE_CHART_ID": {
|
|
59145
60060
|
const dupId = detail?.["chartId"];
|
|
59146
60061
|
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
|
-
}
|
|
60062
|
+
const lastIdx = findLastChartIndexById(spec, dupId);
|
|
59154
60063
|
if (lastIdx < 0) return void 0;
|
|
59155
60064
|
return [{ op: "replace", path: `/charts/${lastIdx}/id`, value: `${dupId}_2` }];
|
|
59156
60065
|
}
|
|
59157
60066
|
case "MISSING_ENCODING": {
|
|
59158
60067
|
const chartType = detail?.["chartType"];
|
|
59159
60068
|
const required2 = detail?.["requiredEncodings"];
|
|
59160
|
-
const chartIdx = spec
|
|
60069
|
+
const chartIdx = findChartIndex(spec, chartType);
|
|
59161
60070
|
if (chartIdx < 0 || !required2?.length) return void 0;
|
|
59162
60071
|
const existing = Object.keys(spec.charts[chartIdx].encoding ?? {});
|
|
59163
60072
|
const missing = required2.filter((enc) => !existing.includes(enc));
|
|
@@ -59203,12 +60112,6 @@ function collectWarningPatches(spec) {
|
|
|
59203
60112
|
}
|
|
59204
60113
|
return patches;
|
|
59205
60114
|
}
|
|
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
60115
|
|
|
59213
60116
|
// src/cli-help.ts
|
|
59214
60117
|
var COMMAND_HELP = {
|
|
@@ -59220,8 +60123,10 @@ Outputs context.json: intent, fields, evidence, catalog (blockedCharts), sampleW
|
|
|
59220
60123
|
Options:
|
|
59221
60124
|
--intent <text> Natural language description of the report goal
|
|
59222
60125
|
--output <file> Write context.json to this path (default: stdout)
|
|
60126
|
+
--compact Output compact context for agent consumption
|
|
60127
|
+
--verbose Keep full context output explicitly for debugging
|
|
59223
60128
|
--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"
|
|
60129
|
+
--correct-assumption <expr> Override an assumption: "primary_measure=orders", "primary_dimension=region", or "time_field=date"
|
|
59225
60130
|
--sheet <name> Sheet name (Excel only)
|
|
59226
60131
|
--limit <n> Max rows to read
|
|
59227
60132
|
`,
|
|
@@ -59246,13 +60151,28 @@ Options:
|
|
|
59246
60151
|
--profile <file> Path to profile JSON (output of "profile")
|
|
59247
60152
|
--context <file> Path to context.json (output of "analyze") for catalog compliance and
|
|
59248
60153
|
$evidence path checks
|
|
59249
|
-
--strict
|
|
60154
|
+
--strict With --verify, treat verify warnings as hard errors; also hard-fails blockedChart violations
|
|
59250
60155
|
--verify Also check for forbidden words and missing sampleWarning caveats
|
|
59251
60156
|
--patch-hints Attach machine-readable JSON Patch hints to fixable errors
|
|
59252
60157
|
`,
|
|
59253
60158
|
catalog: `Usage: miao-viz catalog
|
|
59254
60159
|
|
|
59255
60160
|
List all available chart types and their required fields.
|
|
60161
|
+
`,
|
|
60162
|
+
block: `Usage: miao-viz block instantiate <block-id> --context <context.json> [--output <file>]
|
|
60163
|
+
|
|
60164
|
+
Instantiate a report block using full or compact analyze context.
|
|
60165
|
+
`,
|
|
60166
|
+
template: `Usage:
|
|
60167
|
+
miao-viz template list
|
|
60168
|
+
miao-viz template inspect <template-id>
|
|
60169
|
+
miao-viz template instantiate <template-id> --context <context.json> [--output <file>]
|
|
60170
|
+
|
|
60171
|
+
List, inspect, or instantiate report templates using full or compact analyze context.
|
|
60172
|
+
`,
|
|
60173
|
+
inspect: `Usage: miao-viz inspect --input <file> --spec <file> --context <context.json> --output <file>
|
|
60174
|
+
|
|
60175
|
+
Inspect chart transform pipelines and evidence usage for debugging.
|
|
59256
60176
|
`,
|
|
59257
60177
|
render: `Usage: miao-viz render --input <file> --spec <file> --output <file> [options]
|
|
59258
60178
|
|
|
@@ -59264,6 +60184,7 @@ Options:
|
|
|
59264
60184
|
--output <file> Output file path
|
|
59265
60185
|
--format <fmt> Output format: html, svg (default: html)
|
|
59266
60186
|
--theme <name> Theme: default, editorial, dark, minimal
|
|
60187
|
+
--context <file> Path to context.json (output of "analyze") \u2014 resolves $evidence: directives in insights[]
|
|
59267
60188
|
--interactive Force interactive runtime for HTML output
|
|
59268
60189
|
--no-interactive Force static HTML output
|
|
59269
60190
|
--sheet <name> Sheet name (Excel only)
|
|
@@ -59323,6 +60244,9 @@ Commands:
|
|
|
59323
60244
|
query Run an aggregation query to get real computed values
|
|
59324
60245
|
validate Validate a vizspec against a data profile
|
|
59325
60246
|
catalog List all available chart types
|
|
60247
|
+
block Instantiate report blocks from analyze context
|
|
60248
|
+
template List, inspect, or instantiate report templates
|
|
60249
|
+
inspect Inspect chart transforms and evidence usage
|
|
59326
60250
|
render Render a vizspec to HTML or SVG
|
|
59327
60251
|
deck Render a deck spec to HTML slides
|
|
59328
60252
|
article Convert a local article to an infographic artifact
|
|
@@ -59332,12 +60256,12 @@ Run "miao-viz <command> --help" for command-specific options.
|
|
|
59332
60256
|
}
|
|
59333
60257
|
|
|
59334
60258
|
// src/cli-block.ts
|
|
59335
|
-
var
|
|
60259
|
+
var YAML3 = __toESM(require_dist(), 1);
|
|
59336
60260
|
|
|
59337
60261
|
// src/cli-utils.ts
|
|
59338
60262
|
var import_node_fs3 = require("node:fs");
|
|
59339
60263
|
var import_node_path3 = require("node:path");
|
|
59340
|
-
var
|
|
60264
|
+
var YAML2 = __toESM(require_dist(), 1);
|
|
59341
60265
|
var BOOLEAN_FLAGS = /* @__PURE__ */ new Set([
|
|
59342
60266
|
"h",
|
|
59343
60267
|
"help",
|
|
@@ -59348,7 +60272,9 @@ var BOOLEAN_FLAGS = /* @__PURE__ */ new Set([
|
|
|
59348
60272
|
"strict",
|
|
59349
60273
|
"patch-hints",
|
|
59350
60274
|
"verify",
|
|
59351
|
-
"for-llm"
|
|
60275
|
+
"for-llm",
|
|
60276
|
+
"compact",
|
|
60277
|
+
"verbose"
|
|
59352
60278
|
]);
|
|
59353
60279
|
function parseArgs(argv) {
|
|
59354
60280
|
const [command, ...rest] = argv;
|
|
@@ -59410,7 +60336,7 @@ function printJson(value) {
|
|
|
59410
60336
|
function readSpec(file2) {
|
|
59411
60337
|
const text = (0, import_node_fs3.readFileSync)(file2, "utf8");
|
|
59412
60338
|
if (file2.endsWith(".json")) return JSON.parse(text);
|
|
59413
|
-
return
|
|
60339
|
+
return YAML2.parse(text);
|
|
59414
60340
|
}
|
|
59415
60341
|
function readJson(file2) {
|
|
59416
60342
|
return JSON.parse((0, import_node_fs3.readFileSync)(file2, "utf8"));
|
|
@@ -59443,6 +60369,11 @@ function runCatalog(args) {
|
|
|
59443
60369
|
const catalog = {
|
|
59444
60370
|
charts: CHART_CATALOG.map((c) => ({
|
|
59445
60371
|
id: c.id,
|
|
60372
|
+
compactFor: c.compactFor,
|
|
60373
|
+
requires: c.requires,
|
|
60374
|
+
transformRecipe: c.transformRecipe,
|
|
60375
|
+
avoid: c.avoid,
|
|
60376
|
+
insightPattern: c.insightPattern,
|
|
59446
60377
|
requiredEncodings: c.requiredEncodings,
|
|
59447
60378
|
allowedTransforms: c.allowedTransforms,
|
|
59448
60379
|
rules: c.rules.map((r) => r.expression),
|
|
@@ -59474,11 +60405,11 @@ function runBlock(args) {
|
|
|
59474
60405
|
if (isAgentError(contextPath)) return fail(contextPath);
|
|
59475
60406
|
const raw = readJson(contextPath);
|
|
59476
60407
|
const unwrapped = raw.ok === true ? raw.value : raw;
|
|
59477
|
-
const parsed =
|
|
59478
|
-
if (!parsed
|
|
59479
|
-
return fail(agentError("INVALID_CONTEXT",
|
|
60408
|
+
const parsed = parseAnalyzeContext(unwrapped);
|
|
60409
|
+
if (!parsed) {
|
|
60410
|
+
return fail(agentError("INVALID_CONTEXT", "context.json format is invalid.", { contextPath }));
|
|
59480
60411
|
}
|
|
59481
|
-
const ctx = parsed
|
|
60412
|
+
const ctx = parsed;
|
|
59482
60413
|
const resolver = getBlockById(blockId);
|
|
59483
60414
|
if (!resolver) {
|
|
59484
60415
|
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 +60436,7 @@ function runBlock(args) {
|
|
|
59505
60436
|
}
|
|
59506
60437
|
function buildBlockDraft(resolver, variables, score, compiled) {
|
|
59507
60438
|
const varSummary = Object.entries(variables).map(([k, v]) => `${k}=${v}`).join(", ");
|
|
59508
|
-
const yamlBody =
|
|
60439
|
+
const yamlBody = YAML3.stringify({ charts: compiled.charts, insights: compiled.insights ?? [] });
|
|
59509
60440
|
const checks = resolver.qualityChecks.map((c) => `# [ ] ${c}`).join("\n");
|
|
59510
60441
|
return [
|
|
59511
60442
|
`# Generated by: miao-viz block instantiate ${resolver.id}`,
|
|
@@ -59519,6 +60450,140 @@ function buildBlockDraft(resolver, variables, score, compiled) {
|
|
|
59519
60450
|
].join("\n");
|
|
59520
60451
|
}
|
|
59521
60452
|
|
|
60453
|
+
// src/cli-template.ts
|
|
60454
|
+
function runTemplate(args) {
|
|
60455
|
+
const subcommand = args.positional[0];
|
|
60456
|
+
if (subcommand === "list") {
|
|
60457
|
+
return {
|
|
60458
|
+
ok: true,
|
|
60459
|
+
value: TEMPLATE_REGISTRY.map(templateInfo)
|
|
60460
|
+
};
|
|
60461
|
+
}
|
|
60462
|
+
if (subcommand === "inspect") {
|
|
60463
|
+
const templateId = args.positional[1];
|
|
60464
|
+
if (!templateId) return fail(agentError("MISSING_INPUT", "Usage: miao-viz template inspect <template-id>"));
|
|
60465
|
+
const template = getTemplateById(templateId);
|
|
60466
|
+
if (!template) return fail(agentError("UNKNOWN_TEMPLATE_ID", `Template id '${templateId}' not found.`, { availableIds: TEMPLATE_REGISTRY.map((t) => t.id) }));
|
|
60467
|
+
return { ok: true, value: templateInfo(template) };
|
|
60468
|
+
}
|
|
60469
|
+
if (subcommand === "instantiate") {
|
|
60470
|
+
return runTemplateInstantiate(args);
|
|
60471
|
+
}
|
|
60472
|
+
return fail(agentError("UNKNOWN_SUBCOMMAND", `Unknown template subcommand '${subcommand ?? "(none)"}'. Supported: list, inspect, instantiate`, { subcommand }));
|
|
60473
|
+
}
|
|
60474
|
+
function runTemplateInstantiate(args) {
|
|
60475
|
+
const templateId = args.positional[1];
|
|
60476
|
+
if (!templateId) {
|
|
60477
|
+
return fail(agentError("MISSING_INPUT", "Usage: miao-viz template instantiate <template-id> --context <context.json> [--output <file>]"));
|
|
60478
|
+
}
|
|
60479
|
+
const contextPath = requiredFlag(args, "context");
|
|
60480
|
+
if (isAgentError(contextPath)) return fail(contextPath);
|
|
60481
|
+
const ctx = parseAnalyzeContext(readJson(contextPath));
|
|
60482
|
+
if (!ctx) return fail(agentError("INVALID_CONTEXT", "context.json format is invalid.", { contextPath }));
|
|
60483
|
+
const template = getTemplateById(templateId);
|
|
60484
|
+
if (!template) {
|
|
60485
|
+
return fail(agentError("UNKNOWN_TEMPLATE_ID", `Template id '${templateId}' not found.`, { templateId, availableIds: TEMPLATE_REGISTRY.map((t) => t.id) }));
|
|
60486
|
+
}
|
|
60487
|
+
const matchCtx = {
|
|
60488
|
+
fields: ctx.fields,
|
|
60489
|
+
evidence: ctx.evidence,
|
|
60490
|
+
catalog: ctx.catalog,
|
|
60491
|
+
sampleWarnings: ctx.sampleWarnings
|
|
60492
|
+
};
|
|
60493
|
+
const decision = template.canUse(matchCtx);
|
|
60494
|
+
if (!decision.ok) {
|
|
60495
|
+
return fail(agentError("TEMPLATE_NOT_APPLICABLE", `Template '${templateId}' cannot be used: ${decision.reason}`, { templateId, reason: decision.reason }));
|
|
60496
|
+
}
|
|
60497
|
+
const spec = template.instantiate(matchCtx);
|
|
60498
|
+
const yaml = [
|
|
60499
|
+
`# Generated by: miao-viz template instantiate ${template.id}`,
|
|
60500
|
+
`# Template: ${template.id} (score: ${decision.score.toFixed(2)})`,
|
|
60501
|
+
"# IMPORTANT: Review chart order and fill structured insights[] before validate",
|
|
60502
|
+
"",
|
|
60503
|
+
templateSpecToYaml(spec)
|
|
60504
|
+
].join("\n");
|
|
60505
|
+
const outputPath = stringFlag(args, "output");
|
|
60506
|
+
if (outputPath) {
|
|
60507
|
+
writeOutput(outputPath, yaml);
|
|
60508
|
+
return { ok: true, value: { output: outputPath, templateId, score: decision.score } };
|
|
60509
|
+
}
|
|
60510
|
+
return { ok: true, value: { yaml, templateId, score: decision.score } };
|
|
60511
|
+
}
|
|
60512
|
+
|
|
60513
|
+
// src/cli-inspect.ts
|
|
60514
|
+
function runInspect(args) {
|
|
60515
|
+
const input = requiredFlag(args, "input");
|
|
60516
|
+
const specPath = requiredFlag(args, "spec");
|
|
60517
|
+
const contextPath = requiredFlag(args, "context");
|
|
60518
|
+
const output = requiredFlag(args, "output");
|
|
60519
|
+
if (isAgentError(input)) return fail(input);
|
|
60520
|
+
if (isAgentError(specPath)) return fail(specPath);
|
|
60521
|
+
if (isAgentError(contextPath)) return fail(contextPath);
|
|
60522
|
+
if (isAgentError(output)) return fail(output);
|
|
60523
|
+
const dataset = loadDataset(input);
|
|
60524
|
+
if (isAgentError(dataset)) return fail(dataset);
|
|
60525
|
+
const spec = normalizeSpec(readSpec(specPath));
|
|
60526
|
+
if (isAgentError(spec)) return fail(spec);
|
|
60527
|
+
const context = parseAnalyzeContext(readJson(contextPath));
|
|
60528
|
+
if (!context) return fail(agentError("INVALID_CONTEXT", "context.json format is invalid.", { contextPath }));
|
|
60529
|
+
const profile = profileDataset(dataset.value);
|
|
60530
|
+
const charts = spec.charts.map((chart, index) => {
|
|
60531
|
+
const inspected = inspectChartTransforms(dataset.value.rows, chart);
|
|
60532
|
+
return {
|
|
60533
|
+
id: chart.id ?? `chart-${index + 1}`,
|
|
60534
|
+
type: chart.type,
|
|
60535
|
+
transforms: inspected.transforms,
|
|
60536
|
+
encoding: Object.fromEntries(Object.entries(chart.encoding ?? {}).map(([channel, enc]) => {
|
|
60537
|
+
const field = enc?.field;
|
|
60538
|
+
const profileField = field ? profile.columns.find((c) => c.name === field) : void 0;
|
|
60539
|
+
const finalMatch = field ? inspected.rows.some((row) => Object.prototype.hasOwnProperty.call(row, field)) : false;
|
|
60540
|
+
const sourceFieldMatch = Boolean(profileField);
|
|
60541
|
+
return [channel, {
|
|
60542
|
+
field,
|
|
60543
|
+
specType: enc?.type,
|
|
60544
|
+
resolvedType: profileField?.type ?? (finalMatch ? inferFinalFieldType(inspected.rows, field) : void 0),
|
|
60545
|
+
sourceFieldMatch,
|
|
60546
|
+
finalRowsMatch: finalMatch,
|
|
60547
|
+
match: sourceFieldMatch || finalMatch
|
|
60548
|
+
}];
|
|
60549
|
+
}))
|
|
60550
|
+
};
|
|
60551
|
+
});
|
|
60552
|
+
function inferFinalFieldType(rows, field) {
|
|
60553
|
+
if (!field) return void 0;
|
|
60554
|
+
const values = rows.map((row) => row[field]).filter((value) => value !== null && value !== void 0);
|
|
60555
|
+
if (values.length === 0) return void 0;
|
|
60556
|
+
if (values.every((value) => typeof value === "number")) return "number";
|
|
60557
|
+
if (values.every((value) => typeof value === "boolean")) return "boolean";
|
|
60558
|
+
if (values.every((value) => value instanceof Date)) return "date";
|
|
60559
|
+
return "string";
|
|
60560
|
+
}
|
|
60561
|
+
const referenced = /* @__PURE__ */ new Set();
|
|
60562
|
+
for (const insight of normalizeInsights(spec.insights)) {
|
|
60563
|
+
for (const id of insight.evidence) referenced.add(id);
|
|
60564
|
+
for (const ref of parseEvidenceRefs(insight.text)) referenced.add(ref.id);
|
|
60565
|
+
}
|
|
60566
|
+
for (const chart of spec.charts) {
|
|
60567
|
+
if (!chart.title) continue;
|
|
60568
|
+
for (const ref of parseEvidenceRefs(chart.title)) referenced.add(ref.id);
|
|
60569
|
+
}
|
|
60570
|
+
const defined = context.evidence.map((e) => e.id);
|
|
60571
|
+
const result = {
|
|
60572
|
+
ok: true,
|
|
60573
|
+
value: {
|
|
60574
|
+
charts,
|
|
60575
|
+
evidence: {
|
|
60576
|
+
defined,
|
|
60577
|
+
referenced: [...referenced],
|
|
60578
|
+
unreferenced: defined.filter((id) => !referenced.has(id))
|
|
60579
|
+
}
|
|
60580
|
+
}
|
|
60581
|
+
};
|
|
60582
|
+
writeOutput(output, `${JSON.stringify(result.value, null, 2)}
|
|
60583
|
+
`);
|
|
60584
|
+
return result;
|
|
60585
|
+
}
|
|
60586
|
+
|
|
59522
60587
|
// src/cli.ts
|
|
59523
60588
|
async function main() {
|
|
59524
60589
|
const args = parseArgs(process.argv.slice(2));
|
|
@@ -59547,6 +60612,14 @@ async function main() {
|
|
|
59547
60612
|
printJson(runBlock(args));
|
|
59548
60613
|
return;
|
|
59549
60614
|
}
|
|
60615
|
+
if (args.command === "template") {
|
|
60616
|
+
printJson(runTemplate(args));
|
|
60617
|
+
return;
|
|
60618
|
+
}
|
|
60619
|
+
if (args.command === "inspect") {
|
|
60620
|
+
printJson(runInspect(args));
|
|
60621
|
+
return;
|
|
60622
|
+
}
|
|
59550
60623
|
if (args.command === "render") {
|
|
59551
60624
|
printJson(runRender(args));
|
|
59552
60625
|
return;
|
|
@@ -59568,7 +60641,7 @@ async function main() {
|
|
|
59568
60641
|
return;
|
|
59569
60642
|
}
|
|
59570
60643
|
printJson(agentError("UNKNOWN_COMMAND", `Unknown command: ${args.command ?? "(none)"}`, {
|
|
59571
|
-
commands: ["profile", "validate", "catalog", "block", "render", "deck", "article", "query", "analyze"]
|
|
60644
|
+
commands: ["profile", "validate", "catalog", "block", "template", "inspect", "render", "deck", "article", "query", "analyze"]
|
|
59572
60645
|
}));
|
|
59573
60646
|
process.exitCode = 1;
|
|
59574
60647
|
} catch (error51) {
|
|
@@ -59613,15 +60686,15 @@ function runValidate(args) {
|
|
|
59613
60686
|
if (contextPath) {
|
|
59614
60687
|
const raw = readJson(contextPath);
|
|
59615
60688
|
const unwrapped = raw.ok === true ? raw.value : raw;
|
|
59616
|
-
const parsed =
|
|
59617
|
-
if (!parsed
|
|
60689
|
+
const parsed = parseAnalyzeContext(unwrapped);
|
|
60690
|
+
if (!parsed) {
|
|
59618
60691
|
return fail(agentError(
|
|
59619
60692
|
"INVALID_CONTEXT",
|
|
59620
|
-
|
|
60693
|
+
"context.json format is invalid.",
|
|
59621
60694
|
{ contextPath }
|
|
59622
60695
|
));
|
|
59623
60696
|
}
|
|
59624
|
-
context = parsed
|
|
60697
|
+
context = parsed;
|
|
59625
60698
|
}
|
|
59626
60699
|
const warnings = collectValidationWarnings(result.value, profile, context);
|
|
59627
60700
|
if (args.flags["strict"] === true && context) {
|
|
@@ -59653,6 +60726,10 @@ function runValidate(args) {
|
|
|
59653
60726
|
if (args.flags["verify"] === true) {
|
|
59654
60727
|
const verifyWarnings = collectVerifyWarnings(result.value, context);
|
|
59655
60728
|
warnings.push(...verifyWarnings);
|
|
60729
|
+
if (args.flags["strict"] === true) {
|
|
60730
|
+
const strictResult = strictVerifyError(verifyWarnings);
|
|
60731
|
+
if (isAgentError(strictResult)) return fail(strictResult);
|
|
60732
|
+
}
|
|
59656
60733
|
}
|
|
59657
60734
|
if (args.flags["patch-hints"] === true) {
|
|
59658
60735
|
const warningPatches = collectWarningPatches(result.value);
|
|
@@ -59682,6 +60759,17 @@ function runRender(args) {
|
|
|
59682
60759
|
if (isAgentError(normalized)) return fail(normalized);
|
|
59683
60760
|
const validation = validateReportSpec(normalized, profile, formats);
|
|
59684
60761
|
if (isAgentError(validation)) return fail(validation);
|
|
60762
|
+
const contextPath = stringFlag(args, "context");
|
|
60763
|
+
if (contextPath && validation.value.insights && validation.value.insights.length > 0) {
|
|
60764
|
+
const raw = readJson(contextPath);
|
|
60765
|
+
const unwrapped = raw.ok === true ? raw.value : raw;
|
|
60766
|
+
const parsed = parseAnalyzeContext(unwrapped);
|
|
60767
|
+
if (parsed) {
|
|
60768
|
+
validation.value.insights = validation.value.insights.map(
|
|
60769
|
+
(insight) => mapInsightText(insight, (text) => resolveDirectives(text, parsed.evidence))
|
|
60770
|
+
);
|
|
60771
|
+
}
|
|
60772
|
+
}
|
|
59685
60773
|
const themeFlag = stringFlag(args, "theme");
|
|
59686
60774
|
const interactive = args.flags["interactive"] === true ? true : args.flags["no-interactive"] === true ? false : void 0;
|
|
59687
60775
|
const written = [];
|
|
@@ -59762,7 +60850,8 @@ async function runAnalyze(args) {
|
|
|
59762
60850
|
extraQuery: stringFlag(args, "extra-query"),
|
|
59763
60851
|
correctAssumption: stringFlag(args, "correct-assumption")
|
|
59764
60852
|
});
|
|
59765
|
-
const
|
|
60853
|
+
const value = args.flags["compact"] === true ? toCompactAnalyzeContext(context) : context;
|
|
60854
|
+
const result = { ok: true, value };
|
|
59766
60855
|
const outputPath = stringFlag(args, "output");
|
|
59767
60856
|
if (outputPath) {
|
|
59768
60857
|
writeOutput(outputPath, `${JSON.stringify(result, null, 2)}
|