kirograph 0.16.0 → 0.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +227 -8
- package/dist/bin/commands/affected.js +40 -5
- package/dist/bin/commands/affected.js.map +2 -2
- package/dist/bin/commands/data.js +665 -0
- package/dist/bin/commands/data.js.map +7 -0
- package/dist/bin/commands/help.js +5 -0
- package/dist/bin/commands/help.js.map +2 -2
- package/dist/bin/installer/config-prompt.js +28 -1
- package/dist/bin/installer/config-prompt.js.map +2 -2
- package/dist/bin/installer/index.js +28 -1
- package/dist/bin/installer/index.js.map +2 -2
- package/dist/bin/installer/steering.js +33 -0
- package/dist/bin/installer/steering.js.map +2 -2
- package/dist/bin/installer/targets/index.js.map +1 -1
- package/dist/bin/installer/targets/kiro.js +2 -2
- package/dist/bin/installer/targets/kiro.js.map +2 -2
- package/dist/bin/kirograph.js +3 -1
- package/dist/bin/kirograph.js.map +3 -3
- package/dist/bin/progress.js +30 -0
- package/dist/bin/progress.js.map +2 -2
- package/dist/compression/naive-cost.js +25 -0
- package/dist/compression/naive-cost.js.map +2 -2
- package/dist/compression/tracker.js +11 -2
- package/dist/compression/tracker.js.map +2 -2
- package/dist/compression/types.js.map +1 -1
- package/dist/config.js +37 -1
- package/dist/config.js.map +2 -2
- package/dist/core/pipeline.js +56 -2
- package/dist/core/pipeline.js.map +2 -2
- package/dist/data/filters.js +105 -0
- package/dist/data/filters.js.map +7 -0
- package/dist/data/indexer.js +225 -0
- package/dist/data/indexer.js.map +7 -0
- package/dist/data/linker.js +150 -0
- package/dist/data/linker.js.map +7 -0
- package/dist/data/lint.js +109 -0
- package/dist/data/lint.js.map +7 -0
- package/dist/data/parsers/csv.js +132 -0
- package/dist/data/parsers/csv.js.map +7 -0
- package/dist/data/parsers/excel.js +88 -0
- package/dist/data/parsers/excel.js.map +7 -0
- package/dist/data/parsers/index.js +79 -0
- package/dist/data/parsers/index.js.map +7 -0
- package/dist/data/parsers/json-array.js +89 -0
- package/dist/data/parsers/json-array.js.map +7 -0
- package/dist/data/parsers/jsonl.js +84 -0
- package/dist/data/parsers/jsonl.js.map +7 -0
- package/dist/data/parsers/parquet.js +95 -0
- package/dist/data/parsers/parquet.js.map +7 -0
- package/dist/data/profiler.js +182 -0
- package/dist/data/profiler.js.map +7 -0
- package/dist/data/queries.js +512 -0
- package/dist/data/queries.js.map +7 -0
- package/dist/data/types.js +17 -0
- package/dist/data/types.js.map +7 -0
- package/dist/db/data-schema.sql +61 -0
- package/dist/db/database.js +11 -0
- package/dist/db/database.js.map +2 -2
- package/dist/mcp/tool-names.js +9 -1
- package/dist/mcp/tool-names.js.map +2 -2
- package/dist/mcp/tools.js +423 -0
- package/dist/mcp/tools.js.map +2 -2
- package/dist/types.js.map +1 -1
- package/package.json +4 -2
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../src/data/parsers/jsonl.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * JSONL/NDJSON Streaming Parser\n *\n * Handles: .jsonl, .ndjson\n * Parses one JSON object per line without loading the full file.\n */\n\nimport * as fs from 'fs';\nimport * as readline from 'readline';\nimport type { DataFormatParser, ParsedRow } from '../types';\n\nconst BATCH_SIZE = 10_000;\n\nexport const jsonlParser: DataFormatParser = {\n name: 'jsonl',\n extensions: ['.jsonl', '.ndjson'],\n isAvailable: () => true,\n\n async parse(filePath, opts) {\n const stream = fs.createReadStream(filePath, { encoding: 'utf8' });\n const rl = readline.createInterface({ input: stream, crlfDelay: Infinity });\n\n const columnSet = new Set<string>();\n let totalRows = 0;\n let batch: ParsedRow[] = [];\n\n for await (const line of rl) {\n if (line.trim() === '') continue;\n if (totalRows >= opts.maxRows) break;\n\n try {\n const obj = JSON.parse(line);\n if (typeof obj !== 'object' || obj === null || Array.isArray(obj)) continue;\n\n const row: ParsedRow = {};\n for (const [key, value] of Object.entries(obj)) {\n columnSet.add(key);\n if (value === null || value === undefined) {\n row[key] = null;\n } else if (typeof value === 'object') {\n row[key] = JSON.stringify(value);\n } else {\n row[key] = value as string | number | boolean;\n }\n }\n\n batch.push(row);\n totalRows++;\n\n if (batch.length >= BATCH_SIZE) {\n opts.onBatch(batch, [...columnSet]);\n batch = [];\n }\n } catch {\n // Skip malformed lines\n }\n }\n\n const columns = [...columnSet];\n if (batch.length > 0) {\n opts.onBatch(batch, columns);\n }\n\n return { columns, totalRows };\n },\n};\n"],
|
|
5
|
+
"mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAOA,SAAoB;AACpB,eAA0B;AAG1B,MAAM,aAAa;AAEZ,MAAM,cAAgC;AAAA,EAC3C,MAAM;AAAA,EACN,YAAY,CAAC,UAAU,SAAS;AAAA,EAChC,aAAa,MAAM;AAAA,EAEnB,MAAM,MAAM,UAAU,MAAM;AAC1B,UAAM,SAAS,GAAG,iBAAiB,UAAU,EAAE,UAAU,OAAO,CAAC;AACjE,UAAM,KAAK,SAAS,gBAAgB,EAAE,OAAO,QAAQ,WAAW,SAAS,CAAC;AAE1E,UAAM,YAAY,oBAAI,IAAY;AAClC,QAAI,YAAY;AAChB,QAAI,QAAqB,CAAC;AAE1B,qBAAiB,QAAQ,IAAI;AAC3B,UAAI,KAAK,KAAK,MAAM,GAAI;AACxB,UAAI,aAAa,KAAK,QAAS;AAE/B,UAAI;AACF,cAAM,MAAM,KAAK,MAAM,IAAI;AAC3B,YAAI,OAAO,QAAQ,YAAY,QAAQ,QAAQ,MAAM,QAAQ,GAAG,EAAG;AAEnE,cAAM,MAAiB,CAAC;AACxB,mBAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,GAAG,GAAG;AAC9C,oBAAU,IAAI,GAAG;AACjB,cAAI,UAAU,QAAQ,UAAU,QAAW;AACzC,gBAAI,GAAG,IAAI;AAAA,UACb,WAAW,OAAO,UAAU,UAAU;AACpC,gBAAI,GAAG,IAAI,KAAK,UAAU,KAAK;AAAA,UACjC,OAAO;AACL,gBAAI,GAAG,IAAI;AAAA,UACb;AAAA,QACF;AAEA,cAAM,KAAK,GAAG;AACd;AAEA,YAAI,MAAM,UAAU,YAAY;AAC9B,eAAK,QAAQ,OAAO,CAAC,GAAG,SAAS,CAAC;AAClC,kBAAQ,CAAC;AAAA,QACX;AAAA,MACF,QAAQ;AAAA,MAER;AAAA,IACF;AAEA,UAAM,UAAU,CAAC,GAAG,SAAS;AAC7B,QAAI,MAAM,SAAS,GAAG;AACpB,WAAK,QAAQ,OAAO,OAAO;AAAA,IAC7B;AAEA,WAAO,EAAE,SAAS,UAAU;AAAA,EAC9B;AACF;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
var parquet_exports = {};
|
|
20
|
+
__export(parquet_exports, {
|
|
21
|
+
parquetParser: () => parquetParser
|
|
22
|
+
});
|
|
23
|
+
module.exports = __toCommonJS(parquet_exports);
|
|
24
|
+
const BATCH_SIZE = 1e4;
|
|
25
|
+
let parquetModule = null;
|
|
26
|
+
let parquetChecked = false;
|
|
27
|
+
function tryLoadParquet() {
|
|
28
|
+
if (parquetChecked) return parquetModule !== null;
|
|
29
|
+
parquetChecked = true;
|
|
30
|
+
try {
|
|
31
|
+
parquetModule = require("parquetjs-lite");
|
|
32
|
+
return true;
|
|
33
|
+
} catch {
|
|
34
|
+
try {
|
|
35
|
+
parquetModule = require("parquetjs");
|
|
36
|
+
return true;
|
|
37
|
+
} catch {
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
const parquetParser = {
|
|
43
|
+
name: "parquet",
|
|
44
|
+
extensions: [".parquet"],
|
|
45
|
+
isAvailable() {
|
|
46
|
+
return tryLoadParquet();
|
|
47
|
+
},
|
|
48
|
+
async parse(filePath, opts) {
|
|
49
|
+
if (!tryLoadParquet()) {
|
|
50
|
+
return { columns: [], totalRows: 0 };
|
|
51
|
+
}
|
|
52
|
+
const reader = await parquetModule.ParquetReader.openFile(filePath);
|
|
53
|
+
const schema = reader.getSchema();
|
|
54
|
+
const columns = Object.keys(schema.fields);
|
|
55
|
+
const cursor = reader.getCursor();
|
|
56
|
+
let totalRows = 0;
|
|
57
|
+
let batch = [];
|
|
58
|
+
let record;
|
|
59
|
+
while ((record = await cursor.next()) && totalRows < opts.maxRows) {
|
|
60
|
+
const row = {};
|
|
61
|
+
for (const col of columns) {
|
|
62
|
+
const val = record[col];
|
|
63
|
+
if (val === null || val === void 0) {
|
|
64
|
+
row[col] = null;
|
|
65
|
+
} else if (val instanceof Date) {
|
|
66
|
+
row[col] = val.toISOString();
|
|
67
|
+
} else if (Buffer.isBuffer(val)) {
|
|
68
|
+
row[col] = val.toString("utf8");
|
|
69
|
+
} else if (typeof val === "bigint") {
|
|
70
|
+
row[col] = Number(val);
|
|
71
|
+
} else if (typeof val === "object") {
|
|
72
|
+
row[col] = JSON.stringify(val);
|
|
73
|
+
} else {
|
|
74
|
+
row[col] = val;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
batch.push(row);
|
|
78
|
+
totalRows++;
|
|
79
|
+
if (batch.length >= BATCH_SIZE) {
|
|
80
|
+
opts.onBatch(batch, columns);
|
|
81
|
+
batch = [];
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
if (batch.length > 0) {
|
|
85
|
+
opts.onBatch(batch, columns);
|
|
86
|
+
}
|
|
87
|
+
await reader.close();
|
|
88
|
+
return { columns, totalRows };
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
92
|
+
0 && (module.exports = {
|
|
93
|
+
parquetParser
|
|
94
|
+
});
|
|
95
|
+
//# sourceMappingURL=parquet.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../src/data/parsers/parquet.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * Parquet Parser (Optional Dependency)\n *\n * Handles: .parquet\n * Requires: parquetjs-lite package (optional dependency)\n * Gracefully returns isAvailable()=false if not installed.\n */\n\nimport type { DataFormatParser, ParsedRow } from '../types';\n\nconst BATCH_SIZE = 10_000;\n\nlet parquetModule: any = null;\nlet parquetChecked = false;\n\nfunction tryLoadParquet(): boolean {\n if (parquetChecked) return parquetModule !== null;\n parquetChecked = true;\n try {\n parquetModule = require('parquetjs-lite');\n return true;\n } catch {\n try {\n parquetModule = require('parquetjs');\n return true;\n } catch {\n return false;\n }\n }\n}\n\nexport const parquetParser: DataFormatParser = {\n name: 'parquet',\n extensions: ['.parquet'],\n\n isAvailable(): boolean {\n return tryLoadParquet();\n },\n\n async parse(filePath, opts) {\n if (!tryLoadParquet()) {\n return { columns: [], totalRows: 0 };\n }\n\n const reader = await parquetModule.ParquetReader.openFile(filePath);\n const schema = reader.getSchema();\n const columns = Object.keys(schema.fields);\n\n const cursor = reader.getCursor();\n let totalRows = 0;\n let batch: ParsedRow[] = [];\n let record: any;\n\n while ((record = await cursor.next()) && totalRows < opts.maxRows) {\n const row: ParsedRow = {};\n for (const col of columns) {\n const val = record[col];\n if (val === null || val === undefined) {\n row[col] = null;\n } else if (val instanceof Date) {\n row[col] = val.toISOString();\n } else if (Buffer.isBuffer(val)) {\n row[col] = val.toString('utf8');\n } else if (typeof val === 'bigint') {\n row[col] = Number(val);\n } else if (typeof val === 'object') {\n row[col] = JSON.stringify(val);\n } else {\n row[col] = val;\n }\n }\n\n batch.push(row);\n totalRows++;\n\n if (batch.length >= BATCH_SIZE) {\n opts.onBatch(batch, columns);\n batch = [];\n }\n }\n\n if (batch.length > 0) {\n opts.onBatch(batch, columns);\n }\n\n await reader.close();\n return { columns, totalRows };\n },\n};\n"],
|
|
5
|
+
"mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAUA,MAAM,aAAa;AAEnB,IAAI,gBAAqB;AACzB,IAAI,iBAAiB;AAErB,SAAS,iBAA0B;AACjC,MAAI,eAAgB,QAAO,kBAAkB;AAC7C,mBAAiB;AACjB,MAAI;AACF,oBAAgB,QAAQ,gBAAgB;AACxC,WAAO;AAAA,EACT,QAAQ;AACN,QAAI;AACF,sBAAgB,QAAQ,WAAW;AACnC,aAAO;AAAA,IACT,QAAQ;AACN,aAAO;AAAA,IACT;AAAA,EACF;AACF;AAEO,MAAM,gBAAkC;AAAA,EAC7C,MAAM;AAAA,EACN,YAAY,CAAC,UAAU;AAAA,EAEvB,cAAuB;AACrB,WAAO,eAAe;AAAA,EACxB;AAAA,EAEA,MAAM,MAAM,UAAU,MAAM;AAC1B,QAAI,CAAC,eAAe,GAAG;AACrB,aAAO,EAAE,SAAS,CAAC,GAAG,WAAW,EAAE;AAAA,IACrC;AAEA,UAAM,SAAS,MAAM,cAAc,cAAc,SAAS,QAAQ;AAClE,UAAM,SAAS,OAAO,UAAU;AAChC,UAAM,UAAU,OAAO,KAAK,OAAO,MAAM;AAEzC,UAAM,SAAS,OAAO,UAAU;AAChC,QAAI,YAAY;AAChB,QAAI,QAAqB,CAAC;AAC1B,QAAI;AAEJ,YAAQ,SAAS,MAAM,OAAO,KAAK,MAAM,YAAY,KAAK,SAAS;AACjE,YAAM,MAAiB,CAAC;AACxB,iBAAW,OAAO,SAAS;AACzB,cAAM,MAAM,OAAO,GAAG;AACtB,YAAI,QAAQ,QAAQ,QAAQ,QAAW;AACrC,cAAI,GAAG,IAAI;AAAA,QACb,WAAW,eAAe,MAAM;AAC9B,cAAI,GAAG,IAAI,IAAI,YAAY;AAAA,QAC7B,WAAW,OAAO,SAAS,GAAG,GAAG;AAC/B,cAAI,GAAG,IAAI,IAAI,SAAS,MAAM;AAAA,QAChC,WAAW,OAAO,QAAQ,UAAU;AAClC,cAAI,GAAG,IAAI,OAAO,GAAG;AAAA,QACvB,WAAW,OAAO,QAAQ,UAAU;AAClC,cAAI,GAAG,IAAI,KAAK,UAAU,GAAG;AAAA,QAC/B,OAAO;AACL,cAAI,GAAG,IAAI;AAAA,QACb;AAAA,MACF;AAEA,YAAM,KAAK,GAAG;AACd;AAEA,UAAI,MAAM,UAAU,YAAY;AAC9B,aAAK,QAAQ,OAAO,OAAO;AAC3B,gBAAQ,CAAC;AAAA,MACX;AAAA,IACF;AAEA,QAAI,MAAM,SAAS,GAAG;AACpB,WAAK,QAAQ,OAAO,OAAO;AAAA,IAC7B;AAEA,UAAM,OAAO,MAAM;AACnB,WAAO,EAAE,SAAS,UAAU;AAAA,EAC9B;AACF;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
var profiler_exports = {};
|
|
20
|
+
__export(profiler_exports, {
|
|
21
|
+
ColumnProfiler: () => ColumnProfiler
|
|
22
|
+
});
|
|
23
|
+
module.exports = __toCommonJS(profiler_exports);
|
|
24
|
+
function detectType(value) {
|
|
25
|
+
if (value === null || value === void 0) return "null";
|
|
26
|
+
if (typeof value === "boolean") return "boolean";
|
|
27
|
+
if (typeof value === "number") {
|
|
28
|
+
return Number.isInteger(value) ? "integer" : "float";
|
|
29
|
+
}
|
|
30
|
+
if (typeof value === "string") {
|
|
31
|
+
if (/^\d{4}-\d{2}-\d{2}/.test(value) && !isNaN(Date.parse(value))) return "date";
|
|
32
|
+
return "string";
|
|
33
|
+
}
|
|
34
|
+
return "string";
|
|
35
|
+
}
|
|
36
|
+
function inferFinalType(types, totalCount) {
|
|
37
|
+
const nonNull = new Map(types);
|
|
38
|
+
nonNull.delete("null");
|
|
39
|
+
if (nonNull.size === 0) return "null";
|
|
40
|
+
if (nonNull.size === 1) return [...nonNull.keys()][0];
|
|
41
|
+
if (nonNull.size === 2 && nonNull.has("integer") && nonNull.has("float")) return "float";
|
|
42
|
+
for (const [type, count] of nonNull) {
|
|
43
|
+
if (count / totalCount > 0.8) return type;
|
|
44
|
+
}
|
|
45
|
+
return "mixed";
|
|
46
|
+
}
|
|
47
|
+
class ColumnProfiler {
|
|
48
|
+
constructor() {
|
|
49
|
+
this.stats = /* @__PURE__ */ new Map();
|
|
50
|
+
this.columnOrder = [];
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Process a batch of rows.
|
|
54
|
+
*/
|
|
55
|
+
addBatch(rows, columns) {
|
|
56
|
+
for (let i = 0; i < columns.length; i++) {
|
|
57
|
+
const col = columns[i];
|
|
58
|
+
if (!this.stats.has(col)) {
|
|
59
|
+
this.columnOrder.push(col);
|
|
60
|
+
this.stats.set(col, {
|
|
61
|
+
name: col,
|
|
62
|
+
position: this.columnOrder.length - 1,
|
|
63
|
+
types: /* @__PURE__ */ new Map(),
|
|
64
|
+
nullCount: 0,
|
|
65
|
+
totalCount: 0,
|
|
66
|
+
distinctValues: /* @__PURE__ */ new Set(),
|
|
67
|
+
numericSum: 0,
|
|
68
|
+
numericCount: 0,
|
|
69
|
+
minValue: null,
|
|
70
|
+
maxValue: null,
|
|
71
|
+
samples: []
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
for (const row of rows) {
|
|
76
|
+
for (const col of columns) {
|
|
77
|
+
const stat = this.stats.get(col);
|
|
78
|
+
const value = row[col];
|
|
79
|
+
stat.totalCount++;
|
|
80
|
+
if (value === null || value === void 0) {
|
|
81
|
+
stat.nullCount++;
|
|
82
|
+
stat.types.set("null", (stat.types.get("null") ?? 0) + 1);
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
const type = detectType(value);
|
|
86
|
+
stat.types.set(type, (stat.types.get(type) ?? 0) + 1);
|
|
87
|
+
const strVal = String(value);
|
|
88
|
+
if (stat.distinctValues.size < 1e4) {
|
|
89
|
+
stat.distinctValues.add(strVal);
|
|
90
|
+
}
|
|
91
|
+
if (typeof value === "number" && !isNaN(value)) {
|
|
92
|
+
stat.numericSum += value;
|
|
93
|
+
stat.numericCount++;
|
|
94
|
+
const numStr = String(value);
|
|
95
|
+
if (stat.minValue === null || value < parseFloat(stat.minValue)) stat.minValue = numStr;
|
|
96
|
+
if (stat.maxValue === null || value > parseFloat(stat.maxValue)) stat.maxValue = numStr;
|
|
97
|
+
} else if (type === "string" || type === "date") {
|
|
98
|
+
if (stat.minValue === null || strVal < stat.minValue) stat.minValue = strVal;
|
|
99
|
+
if (stat.maxValue === null || strVal > stat.maxValue) stat.maxValue = strVal;
|
|
100
|
+
}
|
|
101
|
+
if (stat.samples.length < 5 && !stat.samples.includes(strVal) && strVal.length <= 100) {
|
|
102
|
+
stat.samples.push(strVal);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Finalize and return column profiles.
|
|
109
|
+
*/
|
|
110
|
+
finalize(datasetId) {
|
|
111
|
+
const now = Date.now();
|
|
112
|
+
const profiles = [];
|
|
113
|
+
for (const col of this.columnOrder) {
|
|
114
|
+
const stat = this.stats.get(col);
|
|
115
|
+
const inferredType = inferFinalType(stat.types, stat.totalCount);
|
|
116
|
+
const nullPct = stat.totalCount > 0 ? stat.nullCount / stat.totalCount : 0;
|
|
117
|
+
const meanValue = stat.numericCount > 0 ? stat.numericSum / stat.numericCount : null;
|
|
118
|
+
profiles.push({
|
|
119
|
+
id: `${datasetId}::${col}#column`,
|
|
120
|
+
datasetId,
|
|
121
|
+
name: col,
|
|
122
|
+
position: stat.position,
|
|
123
|
+
inferredType,
|
|
124
|
+
nullable: stat.nullCount > 0,
|
|
125
|
+
nullCount: stat.nullCount,
|
|
126
|
+
nullPct,
|
|
127
|
+
cardinality: stat.distinctValues.size,
|
|
128
|
+
minValue: stat.minValue,
|
|
129
|
+
maxValue: stat.maxValue,
|
|
130
|
+
meanValue,
|
|
131
|
+
sampleValues: stat.samples,
|
|
132
|
+
summary: generateColumnSummary(col, inferredType, stat, nullPct),
|
|
133
|
+
updatedAt: now
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
return profiles;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
function generateColumnSummary(name, type, stat, nullPct) {
|
|
140
|
+
const parts = [];
|
|
141
|
+
const cardinality = stat.distinctValues.size;
|
|
142
|
+
const total = stat.totalCount;
|
|
143
|
+
if (type === "integer" || type === "float") {
|
|
144
|
+
parts.push(`Numeric (${type})`);
|
|
145
|
+
if (stat.minValue != null && stat.maxValue != null) {
|
|
146
|
+
parts.push(`range ${stat.minValue}\u2013${stat.maxValue}`);
|
|
147
|
+
}
|
|
148
|
+
} else if (type === "boolean") {
|
|
149
|
+
parts.push("Boolean flag");
|
|
150
|
+
} else if (type === "date") {
|
|
151
|
+
parts.push("Date/timestamp");
|
|
152
|
+
if (stat.minValue && stat.maxValue) {
|
|
153
|
+
parts.push(`from ${stat.minValue} to ${stat.maxValue}`);
|
|
154
|
+
}
|
|
155
|
+
} else if (type === "string") {
|
|
156
|
+
if (cardinality <= 10 && total > 20) {
|
|
157
|
+
parts.push(`Categorical (${cardinality} values)`);
|
|
158
|
+
} else if (cardinality === total && total > 10) {
|
|
159
|
+
parts.push("Unique identifier");
|
|
160
|
+
} else {
|
|
161
|
+
parts.push("Text");
|
|
162
|
+
}
|
|
163
|
+
} else {
|
|
164
|
+
parts.push(type);
|
|
165
|
+
}
|
|
166
|
+
if (cardinality === 1 && total > 1) {
|
|
167
|
+
parts.push("constant");
|
|
168
|
+
} else if (total > 0 && cardinality / total > 0.95 && total > 10) {
|
|
169
|
+
parts.push("near-unique");
|
|
170
|
+
}
|
|
171
|
+
if (nullPct > 0.5) {
|
|
172
|
+
parts.push(`mostly null (${(nullPct * 100).toFixed(0)}%)`);
|
|
173
|
+
} else if (nullPct > 0.1) {
|
|
174
|
+
parts.push(`some nulls (${(nullPct * 100).toFixed(0)}%)`);
|
|
175
|
+
}
|
|
176
|
+
return parts.join(", ");
|
|
177
|
+
}
|
|
178
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
179
|
+
0 && (module.exports = {
|
|
180
|
+
ColumnProfiler
|
|
181
|
+
});
|
|
182
|
+
//# sourceMappingURL=profiler.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/data/profiler.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * Column Profiler\n *\n * Analyzes parsed rows to produce column profiles:\n * - Type inference (string, integer, float, boolean, date, null, mixed)\n * - Null count and percentage\n * - Cardinality (distinct values)\n * - Min/max values\n * - Mean (for numeric columns)\n * - Sample values (up to 5)\n */\n\nimport type { DataColumn, InferredType, ParsedRow } from './types';\n\ninterface ColumnStats {\n name: string;\n position: number;\n types: Map<string, number>; // type \u2192 count\n nullCount: number;\n totalCount: number;\n distinctValues: Set<string>;\n numericSum: number;\n numericCount: number;\n minValue: string | null;\n maxValue: string | null;\n samples: string[];\n}\n\nfunction detectType(value: any): string {\n if (value === null || value === undefined) return 'null';\n if (typeof value === 'boolean') return 'boolean';\n if (typeof value === 'number') {\n return Number.isInteger(value) ? 'integer' : 'float';\n }\n if (typeof value === 'string') {\n // Try to detect dates\n if (/^\\d{4}-\\d{2}-\\d{2}/.test(value) && !isNaN(Date.parse(value))) return 'date';\n return 'string';\n }\n return 'string';\n}\n\nfunction inferFinalType(types: Map<string, number>, totalCount: number): InferredType {\n // Remove null from consideration\n const nonNull = new Map(types);\n nonNull.delete('null');\n\n if (nonNull.size === 0) return 'null';\n if (nonNull.size === 1) return [...nonNull.keys()][0] as InferredType;\n\n // integer + float \u2192 float\n if (nonNull.size === 2 && nonNull.has('integer') && nonNull.has('float')) return 'float';\n\n // If one type dominates (>80%), use it\n for (const [type, count] of nonNull) {\n if (count / totalCount > 0.8) return type as InferredType;\n }\n\n return 'mixed';\n}\n\n/**\n * Profile columns from a batch of rows.\n * Call repeatedly with batches, then call finalize() to get the final profiles.\n */\nexport class ColumnProfiler {\n private stats = new Map<string, ColumnStats>();\n private columnOrder: string[] = [];\n\n /**\n * Process a batch of rows.\n */\n addBatch(rows: ParsedRow[], columns: string[]): void {\n // Initialize stats for new columns\n for (let i = 0; i < columns.length; i++) {\n const col = columns[i];\n if (!this.stats.has(col)) {\n this.columnOrder.push(col);\n this.stats.set(col, {\n name: col,\n position: this.columnOrder.length - 1,\n types: new Map(),\n nullCount: 0,\n totalCount: 0,\n distinctValues: new Set(),\n numericSum: 0,\n numericCount: 0,\n minValue: null,\n maxValue: null,\n samples: [],\n });\n }\n }\n\n // Process each row\n for (const row of rows) {\n for (const col of columns) {\n const stat = this.stats.get(col)!;\n const value = row[col];\n stat.totalCount++;\n\n if (value === null || value === undefined) {\n stat.nullCount++;\n stat.types.set('null', (stat.types.get('null') ?? 0) + 1);\n continue;\n }\n\n const type = detectType(value);\n stat.types.set(type, (stat.types.get(type) ?? 0) + 1);\n\n // Track distinct values (cap at 10000 to avoid memory issues)\n const strVal = String(value);\n if (stat.distinctValues.size < 10_000) {\n stat.distinctValues.add(strVal);\n }\n\n // Numeric stats\n if (typeof value === 'number' && !isNaN(value)) {\n stat.numericSum += value;\n stat.numericCount++;\n const numStr = String(value);\n if (stat.minValue === null || value < parseFloat(stat.minValue)) stat.minValue = numStr;\n if (stat.maxValue === null || value > parseFloat(stat.maxValue)) stat.maxValue = numStr;\n } else if (type === 'string' || type === 'date') {\n if (stat.minValue === null || strVal < stat.minValue) stat.minValue = strVal;\n if (stat.maxValue === null || strVal > stat.maxValue) stat.maxValue = strVal;\n }\n\n // Collect samples (up to 5 unique)\n if (stat.samples.length < 5 && !stat.samples.includes(strVal) && strVal.length <= 100) {\n stat.samples.push(strVal);\n }\n }\n }\n }\n\n /**\n * Finalize and return column profiles.\n */\n finalize(datasetId: string): DataColumn[] {\n const now = Date.now();\n const profiles: DataColumn[] = [];\n\n for (const col of this.columnOrder) {\n const stat = this.stats.get(col)!;\n const inferredType = inferFinalType(stat.types, stat.totalCount);\n const nullPct = stat.totalCount > 0 ? stat.nullCount / stat.totalCount : 0;\n const meanValue = stat.numericCount > 0 ? stat.numericSum / stat.numericCount : null;\n\n profiles.push({\n id: `${datasetId}::${col}#column`,\n datasetId,\n name: col,\n position: stat.position,\n inferredType,\n nullable: stat.nullCount > 0,\n nullCount: stat.nullCount,\n nullPct,\n cardinality: stat.distinctValues.size,\n minValue: stat.minValue,\n maxValue: stat.maxValue,\n meanValue,\n sampleValues: stat.samples,\n summary: generateColumnSummary(col, inferredType, stat, nullPct),\n updatedAt: now,\n });\n }\n\n return profiles;\n }\n}\n\n/**\n * Generate a natural-language summary for a column based on its profile.\n */\nfunction generateColumnSummary(\n name: string,\n type: InferredType,\n stat: ColumnStats,\n nullPct: number,\n): string {\n const parts: string[] = [];\n const cardinality = stat.distinctValues.size;\n const total = stat.totalCount;\n\n // Type description\n if (type === 'integer' || type === 'float') {\n parts.push(`Numeric (${type})`);\n if (stat.minValue != null && stat.maxValue != null) {\n parts.push(`range ${stat.minValue}\u2013${stat.maxValue}`);\n }\n } else if (type === 'boolean') {\n parts.push('Boolean flag');\n } else if (type === 'date') {\n parts.push('Date/timestamp');\n if (stat.minValue && stat.maxValue) {\n parts.push(`from ${stat.minValue} to ${stat.maxValue}`);\n }\n } else if (type === 'string') {\n if (cardinality <= 10 && total > 20) {\n parts.push(`Categorical (${cardinality} values)`);\n } else if (cardinality === total && total > 10) {\n parts.push('Unique identifier');\n } else {\n parts.push('Text');\n }\n } else {\n parts.push(type);\n }\n\n // Cardinality insight\n if (cardinality === 1 && total > 1) {\n parts.push('constant');\n } else if (total > 0 && cardinality / total > 0.95 && total > 10) {\n parts.push('near-unique');\n }\n\n // Null info\n if (nullPct > 0.5) {\n parts.push(`mostly null (${(nullPct * 100).toFixed(0)}%)`);\n } else if (nullPct > 0.1) {\n parts.push(`some nulls (${(nullPct * 100).toFixed(0)}%)`);\n }\n\n return parts.join(', ');\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|