@duckcodeailabs/dql-agent 1.6.18 → 1.6.20
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/answer-loop.d.ts.map +1 -1
- package/dist/answer-loop.js +6 -2
- package/dist/answer-loop.js.map +1 -1
- package/dist/app-builder.d.ts +1 -1
- package/dist/app-builder.d.ts.map +1 -1
- package/dist/app-builder.js +57 -13
- package/dist/app-builder.js.map +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/kg/build.d.ts.map +1 -1
- package/dist/kg/build.js +76 -1
- package/dist/kg/build.js.map +1 -1
- package/dist/kg/sqlite-fts.js +30 -0
- package/dist/kg/sqlite-fts.js.map +1 -1
- package/dist/kg/types.d.ts +43 -0
- package/dist/kg/types.d.ts.map +1 -1
- package/dist/metadata/block-fingerprints.d.ts +29 -0
- package/dist/metadata/block-fingerprints.d.ts.map +1 -0
- package/dist/metadata/block-fingerprints.js +84 -0
- package/dist/metadata/block-fingerprints.js.map +1 -0
- package/dist/metadata/catalog.d.ts +23 -0
- package/dist/metadata/catalog.d.ts.map +1 -1
- package/dist/metadata/catalog.js +324 -1
- package/dist/metadata/catalog.js.map +1 -1
- package/dist/metadata/drafts.d.ts.map +1 -1
- package/dist/metadata/drafts.js +20 -11
- package/dist/metadata/drafts.js.map +1 -1
- package/dist/providers/openai.d.ts.map +1 -1
- package/dist/providers/openai.js +49 -16
- package/dist/providers/openai.js.map +1 -1
- package/package.json +4 -4
package/dist/metadata/catalog.js
CHANGED
|
@@ -10,8 +10,9 @@ import { createHash } from 'node:crypto';
|
|
|
10
10
|
import { mkdirSync, readFileSync } from 'node:fs';
|
|
11
11
|
import { createRequire } from 'node:module';
|
|
12
12
|
import { dirname, join } from 'node:path';
|
|
13
|
-
import { buildManifest, loadProjectConfig, resolveDbtManifestPath, resolveSemanticLayerWithDiagnostics, } from '@duckcodeailabs/dql-core';
|
|
13
|
+
import { buildManifest, loadProjectConfig, parseContractRef, resolveDataLexManifestPath, resolveDbtManifestPath, resolveSemanticLayerWithDiagnostics, } from '@duckcodeailabs/dql-core';
|
|
14
14
|
import { buildKGFromManifest, buildKGFromSemanticLayer } from '../kg/build.js';
|
|
15
|
+
import { buildBlockBusinessFingerprint, buildBlockSqlFingerprints } from './block-fingerprints.js';
|
|
15
16
|
import { buildAnalysisQuestionPlan, certifiedApplicabilityForObject, scoreAllowedSqlRelationWithAnalysisPlan, scoreMetadataObjectWithAnalysisPlan, sortAllowedSqlContextForAnalysisPlan, } from './analysis-planner.js';
|
|
16
17
|
import { extractSimpleSelectShape, sourceSqlShapeColumns } from './sql-shape.js';
|
|
17
18
|
const require = createRequire(import.meta.url);
|
|
@@ -294,6 +295,7 @@ export function buildMetadataSnapshot(projectRoot, manifest, semanticLayer) {
|
|
|
294
295
|
addDbtDagObjects(manifest, objects, edges, diagnostics);
|
|
295
296
|
addRawDbtManifestCatalogObjects(projectRoot, manifest, objects, edges, diagnostics);
|
|
296
297
|
addBlockDependencyEdges(manifest, edges);
|
|
298
|
+
addDataLexManifestObjects(projectRoot, manifest, objects, edges, diagnostics);
|
|
297
299
|
const nodeKeyMap = new Map();
|
|
298
300
|
for (const node of [...manifestGraph.nodes, ...semanticGraph.nodes]) {
|
|
299
301
|
nodeKeyMap.set(node.nodeId, objectKeyFromKGNode(node));
|
|
@@ -423,6 +425,23 @@ export class MetadataCatalog {
|
|
|
423
425
|
created_at TEXT NOT NULL
|
|
424
426
|
);
|
|
425
427
|
CREATE INDEX IF NOT EXISTS idx_metadata_diagnostics_severity ON metadata_diagnostics(severity);
|
|
428
|
+
|
|
429
|
+
CREATE TABLE IF NOT EXISTS metadata_source_fingerprints (
|
|
430
|
+
source_path TEXT PRIMARY KEY,
|
|
431
|
+
fingerprint TEXT NOT NULL,
|
|
432
|
+
object_count INTEGER NOT NULL,
|
|
433
|
+
updated_at TEXT NOT NULL
|
|
434
|
+
);
|
|
435
|
+
|
|
436
|
+
CREATE TABLE IF NOT EXISTS metadata_domain_shards (
|
|
437
|
+
domain TEXT PRIMARY KEY,
|
|
438
|
+
object_count INTEGER NOT NULL,
|
|
439
|
+
block_count INTEGER NOT NULL,
|
|
440
|
+
certified_block_count INTEGER NOT NULL,
|
|
441
|
+
semantic_metric_count INTEGER NOT NULL,
|
|
442
|
+
dbt_object_count INTEGER NOT NULL,
|
|
443
|
+
updated_at TEXT NOT NULL
|
|
444
|
+
);
|
|
426
445
|
`);
|
|
427
446
|
}
|
|
428
447
|
rebuild(snapshot) {
|
|
@@ -450,11 +469,26 @@ export class MetadataCatalog {
|
|
|
450
469
|
const setState = this.db.prepare(`
|
|
451
470
|
INSERT OR REPLACE INTO metadata_state (key, value) VALUES (?, ?)
|
|
452
471
|
`);
|
|
472
|
+
const insertSourceFingerprint = this.db.prepare(`
|
|
473
|
+
INSERT OR REPLACE INTO metadata_source_fingerprints (
|
|
474
|
+
source_path, fingerprint, object_count, updated_at
|
|
475
|
+
) VALUES (?, ?, ?, ?)
|
|
476
|
+
`);
|
|
477
|
+
const insertDomainShard = this.db.prepare(`
|
|
478
|
+
INSERT OR REPLACE INTO metadata_domain_shards (
|
|
479
|
+
domain, object_count, block_count, certified_block_count,
|
|
480
|
+
semantic_metric_count, dbt_object_count, updated_at
|
|
481
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
482
|
+
`);
|
|
483
|
+
const sourceFingerprints = buildSourceFingerprints(snapshot.objects, now);
|
|
484
|
+
const domainShards = buildDomainShards(snapshot.objects, now);
|
|
453
485
|
const txn = this.db.transaction(() => {
|
|
454
486
|
this.db.prepare('DELETE FROM metadata_edges').run();
|
|
455
487
|
this.db.prepare('DELETE FROM metadata_fts').run();
|
|
456
488
|
this.db.prepare('DELETE FROM metadata_objects').run();
|
|
457
489
|
this.db.prepare('DELETE FROM metadata_diagnostics').run();
|
|
490
|
+
this.db.prepare('DELETE FROM metadata_source_fingerprints').run();
|
|
491
|
+
this.db.prepare('DELETE FROM metadata_domain_shards').run();
|
|
458
492
|
for (const object of snapshot.objects) {
|
|
459
493
|
const payload = object.payload ?? {};
|
|
460
494
|
insertObject.run(object.objectKey, object.objectType, object.name, object.fullName ?? null, object.domain ?? null, object.owner ?? null, object.status ?? null, object.description ?? null, object.sourcePath ?? null, object.sourceSystem ?? null, JSON.stringify(payload), object.updatedAt ?? now);
|
|
@@ -466,11 +500,19 @@ export class MetadataCatalog {
|
|
|
466
500
|
for (const diagnostic of snapshot.diagnostics) {
|
|
467
501
|
insertDiagnostic.run(diagnosticId(diagnostic), diagnostic.kind, diagnostic.severity, diagnostic.message, diagnostic.objectKey ?? null, diagnostic.filePath ?? null, now);
|
|
468
502
|
}
|
|
503
|
+
for (const item of sourceFingerprints) {
|
|
504
|
+
insertSourceFingerprint.run(item.sourcePath, item.fingerprint, item.objectCount, item.updatedAt);
|
|
505
|
+
}
|
|
506
|
+
for (const item of domainShards) {
|
|
507
|
+
insertDomainShard.run(item.domain, item.objectCount, item.blockCount, item.certifiedBlockCount, item.semanticMetricCount, item.dbtObjectCount, item.updatedAt);
|
|
508
|
+
}
|
|
469
509
|
setState.run('built_at', now);
|
|
470
510
|
setState.run('fingerprint', snapshot.fingerprint);
|
|
471
511
|
setState.run('project_root', snapshot.projectRoot);
|
|
472
512
|
setState.run('object_count', String(snapshot.objects.length));
|
|
473
513
|
setState.run('edge_count', String(snapshot.edges.length));
|
|
514
|
+
setState.run('source_fingerprint_count', String(sourceFingerprints.length));
|
|
515
|
+
setState.run('domain_shard_count', String(domainShards.length));
|
|
474
516
|
setState.run('diagnostics_json', JSON.stringify(snapshot.diagnostics));
|
|
475
517
|
setState.run('manifest_generated_at', snapshot.manifest.generatedAt);
|
|
476
518
|
});
|
|
@@ -696,6 +738,38 @@ export class MetadataCatalog {
|
|
|
696
738
|
filePath: row.file_path ?? undefined,
|
|
697
739
|
}));
|
|
698
740
|
}
|
|
741
|
+
sourceFingerprints(limit = 500) {
|
|
742
|
+
const rows = this.db.prepare(`
|
|
743
|
+
SELECT source_path, fingerprint, object_count, updated_at
|
|
744
|
+
FROM metadata_source_fingerprints
|
|
745
|
+
ORDER BY object_count DESC, source_path
|
|
746
|
+
LIMIT ?
|
|
747
|
+
`).all(limit);
|
|
748
|
+
return rows.map((row) => ({
|
|
749
|
+
sourcePath: row.source_path,
|
|
750
|
+
fingerprint: row.fingerprint,
|
|
751
|
+
objectCount: row.object_count,
|
|
752
|
+
updatedAt: row.updated_at,
|
|
753
|
+
}));
|
|
754
|
+
}
|
|
755
|
+
domainShards(limit = 100) {
|
|
756
|
+
const rows = this.db.prepare(`
|
|
757
|
+
SELECT domain, object_count, block_count, certified_block_count,
|
|
758
|
+
semantic_metric_count, dbt_object_count, updated_at
|
|
759
|
+
FROM metadata_domain_shards
|
|
760
|
+
ORDER BY object_count DESC, domain
|
|
761
|
+
LIMIT ?
|
|
762
|
+
`).all(limit);
|
|
763
|
+
return rows.map((row) => ({
|
|
764
|
+
domain: row.domain,
|
|
765
|
+
objectCount: row.object_count,
|
|
766
|
+
blockCount: row.block_count,
|
|
767
|
+
certifiedBlockCount: row.certified_block_count,
|
|
768
|
+
semanticMetricCount: row.semantic_metric_count,
|
|
769
|
+
dbtObjectCount: row.dbt_object_count,
|
|
770
|
+
updatedAt: row.updated_at,
|
|
771
|
+
}));
|
|
772
|
+
}
|
|
699
773
|
state(key) {
|
|
700
774
|
const row = this.db.prepare('SELECT value FROM metadata_state WHERE key = ?').get(key);
|
|
701
775
|
return row?.value ?? null;
|
|
@@ -754,6 +828,21 @@ function objectFromKGNode(node) {
|
|
|
754
828
|
businessOwner: node.businessOwner,
|
|
755
829
|
decisionUse: node.decisionUse,
|
|
756
830
|
reviewCadence: node.reviewCadence,
|
|
831
|
+
pattern: node.pattern,
|
|
832
|
+
grain: node.grain,
|
|
833
|
+
entities: node.entities ?? [],
|
|
834
|
+
declaredOutputs: node.declaredOutputs ?? [],
|
|
835
|
+
dimensions: node.dimensions ?? [],
|
|
836
|
+
allowedFilters: node.allowedFilters ?? [],
|
|
837
|
+
parameterPolicy: node.parameterPolicy ?? [],
|
|
838
|
+
filterBindings: node.filterBindings ?? [],
|
|
839
|
+
sourceSystems: node.sourceSystems ?? [],
|
|
840
|
+
replacementFor: node.replacementFor ?? [],
|
|
841
|
+
sqlFingerprints: node.sqlFingerprints,
|
|
842
|
+
businessFingerprint: node.businessFingerprint,
|
|
843
|
+
datalexContract: node.datalexContract,
|
|
844
|
+
boundedContext: node.boundedContext,
|
|
845
|
+
primaryTerms: node.primaryTerms ?? [],
|
|
757
846
|
businessRules: node.businessRules ?? [],
|
|
758
847
|
caveats: node.caveats ?? [],
|
|
759
848
|
llmContext: node.llmContext,
|
|
@@ -1056,14 +1145,206 @@ function addManifestBlockDetails(manifest, objects) {
|
|
|
1056
1145
|
refDependencies: block.refDependencies,
|
|
1057
1146
|
metricRefs: block.metricRefs,
|
|
1058
1147
|
dimensionRefs: block.dimensionRefs,
|
|
1148
|
+
dimensions: block.dimensions,
|
|
1059
1149
|
chartType: block.chartType,
|
|
1060
1150
|
blockType: block.blockType,
|
|
1061
1151
|
tests: block.tests,
|
|
1152
|
+
parameterPolicy: block.parameterPolicy,
|
|
1153
|
+
filterBindings: block.filterBindings,
|
|
1154
|
+
sqlFingerprints: buildBlockSqlFingerprints(block.sql),
|
|
1155
|
+
businessFingerprint: buildBlockBusinessFingerprint({
|
|
1156
|
+
name: block.name,
|
|
1157
|
+
domain: block.domain,
|
|
1158
|
+
pattern: block.pattern,
|
|
1159
|
+
grain: block.grain,
|
|
1160
|
+
entities: block.entities,
|
|
1161
|
+
terms: block.termRefs,
|
|
1162
|
+
outputs: block.declaredOutputs,
|
|
1163
|
+
dimensions: block.dimensions,
|
|
1164
|
+
filters: block.allowedFilters,
|
|
1165
|
+
sources: [...(block.tableDependencies ?? []), ...(block.rawTableRefs ?? [])],
|
|
1166
|
+
sourceSystems: block.sourceSystems,
|
|
1167
|
+
}),
|
|
1062
1168
|
draftMetadata: block.draftMetadata,
|
|
1063
1169
|
}),
|
|
1064
1170
|
}));
|
|
1065
1171
|
}
|
|
1066
1172
|
}
|
|
1173
|
+
function addDataLexManifestObjects(projectRoot, manifest, objects, edges, diagnostics) {
|
|
1174
|
+
const manifestPath = resolveDataLexManifestPath(projectRoot);
|
|
1175
|
+
if (!manifestPath)
|
|
1176
|
+
return;
|
|
1177
|
+
let raw;
|
|
1178
|
+
try {
|
|
1179
|
+
raw = JSON.parse(readFileSync(manifestPath, 'utf-8'));
|
|
1180
|
+
}
|
|
1181
|
+
catch (error) {
|
|
1182
|
+
diagnostics.push({
|
|
1183
|
+
kind: 'datalex',
|
|
1184
|
+
severity: 'warning',
|
|
1185
|
+
message: `Could not read DataLex manifest context from ${manifestPath}: ${error instanceof Error ? error.message : String(error)}`,
|
|
1186
|
+
filePath: manifestPath,
|
|
1187
|
+
});
|
|
1188
|
+
return;
|
|
1189
|
+
}
|
|
1190
|
+
if (!Array.isArray(raw.domains)) {
|
|
1191
|
+
diagnostics.push({
|
|
1192
|
+
kind: 'datalex',
|
|
1193
|
+
severity: 'warning',
|
|
1194
|
+
message: `DataLex manifest at ${manifestPath} does not contain a domains array.`,
|
|
1195
|
+
filePath: manifestPath,
|
|
1196
|
+
});
|
|
1197
|
+
return;
|
|
1198
|
+
}
|
|
1199
|
+
const latestContractKeyById = new Map();
|
|
1200
|
+
const contractKeyByVersionedRef = new Map();
|
|
1201
|
+
for (const domain of raw.domains) {
|
|
1202
|
+
if (!domain?.name)
|
|
1203
|
+
continue;
|
|
1204
|
+
const domainKey = `datalex:domain:${domain.name}`;
|
|
1205
|
+
objects.set(domainKey, mergeObject(objects.get(domainKey), {
|
|
1206
|
+
objectKey: domainKey,
|
|
1207
|
+
objectType: 'datalex_domain',
|
|
1208
|
+
name: domain.name,
|
|
1209
|
+
fullName: domain.name,
|
|
1210
|
+
domain: domain.name,
|
|
1211
|
+
owner: domain.owners?.[0],
|
|
1212
|
+
status: 'contract_evidence',
|
|
1213
|
+
description: domain.description,
|
|
1214
|
+
sourcePath: manifestPath,
|
|
1215
|
+
sourceSystem: 'DataLex manifest',
|
|
1216
|
+
payload: compactObject({
|
|
1217
|
+
project: raw.project?.name,
|
|
1218
|
+
owners: domain.owners ?? [],
|
|
1219
|
+
tags: [],
|
|
1220
|
+
generatedAt: raw.generatedAt,
|
|
1221
|
+
}),
|
|
1222
|
+
}));
|
|
1223
|
+
for (const term of domain.glossary ?? []) {
|
|
1224
|
+
if (!term?.term)
|
|
1225
|
+
continue;
|
|
1226
|
+
const termKey = `datalex:term:${domain.name}.${term.term}`;
|
|
1227
|
+
objects.set(termKey, mergeObject(objects.get(termKey), {
|
|
1228
|
+
objectKey: termKey,
|
|
1229
|
+
objectType: 'datalex_term',
|
|
1230
|
+
name: term.term,
|
|
1231
|
+
fullName: `${domain.name}.${term.term}`,
|
|
1232
|
+
domain: domain.name,
|
|
1233
|
+
status: 'contract_evidence',
|
|
1234
|
+
description: term.definition,
|
|
1235
|
+
sourcePath: manifestPath,
|
|
1236
|
+
sourceSystem: 'DataLex manifest',
|
|
1237
|
+
payload: compactObject({
|
|
1238
|
+
tags: term.tags ?? [],
|
|
1239
|
+
relatedFields: term.related_fields ?? [],
|
|
1240
|
+
}),
|
|
1241
|
+
}));
|
|
1242
|
+
putEdge(edges, {
|
|
1243
|
+
edgeType: 'contains',
|
|
1244
|
+
fromKey: domainKey,
|
|
1245
|
+
toKey: termKey,
|
|
1246
|
+
confidence: 1,
|
|
1247
|
+
payload: { source: 'datalex manifest glossary' },
|
|
1248
|
+
});
|
|
1249
|
+
}
|
|
1250
|
+
for (const entity of domain.entities ?? []) {
|
|
1251
|
+
if (!entity?.name)
|
|
1252
|
+
continue;
|
|
1253
|
+
const entityKey = `datalex:entity:${domain.name}.${entity.name}`;
|
|
1254
|
+
objects.set(entityKey, mergeObject(objects.get(entityKey), {
|
|
1255
|
+
objectKey: entityKey,
|
|
1256
|
+
objectType: 'datalex_entity',
|
|
1257
|
+
name: entity.name,
|
|
1258
|
+
fullName: `${domain.name}.${entity.name}`,
|
|
1259
|
+
domain: domain.name,
|
|
1260
|
+
status: 'contract_evidence',
|
|
1261
|
+
description: entity.description,
|
|
1262
|
+
sourcePath: manifestPath,
|
|
1263
|
+
sourceSystem: 'DataLex manifest',
|
|
1264
|
+
payload: compactObject({
|
|
1265
|
+
tags: entity.tags ?? [],
|
|
1266
|
+
binding: entity.binding,
|
|
1267
|
+
fields: (entity.fields ?? []).slice(0, 100).map((field) => compactObject({
|
|
1268
|
+
name: field.name,
|
|
1269
|
+
type: field.type,
|
|
1270
|
+
description: field.description,
|
|
1271
|
+
primaryKey: field.primary_key,
|
|
1272
|
+
classification: field.classification,
|
|
1273
|
+
tags: field.tags ?? [],
|
|
1274
|
+
})),
|
|
1275
|
+
}),
|
|
1276
|
+
}));
|
|
1277
|
+
putEdge(edges, {
|
|
1278
|
+
edgeType: 'contains',
|
|
1279
|
+
fromKey: domainKey,
|
|
1280
|
+
toKey: entityKey,
|
|
1281
|
+
confidence: 1,
|
|
1282
|
+
payload: { source: 'datalex manifest entity' },
|
|
1283
|
+
});
|
|
1284
|
+
for (const contract of entity.contracts ?? []) {
|
|
1285
|
+
if (!contract?.id)
|
|
1286
|
+
continue;
|
|
1287
|
+
const version = Number(contract.version);
|
|
1288
|
+
if (!Number.isFinite(version))
|
|
1289
|
+
continue;
|
|
1290
|
+
const contractKey = `datalex:contract:${contract.id}@${version}`;
|
|
1291
|
+
contractKeyByVersionedRef.set(`${contract.id}@${version}`, contractKey);
|
|
1292
|
+
const latest = latestContractKeyById.get(contract.id);
|
|
1293
|
+
if (!latest || Number(latest.split('@').at(-1) ?? 0) < version) {
|
|
1294
|
+
latestContractKeyById.set(contract.id, contractKey);
|
|
1295
|
+
}
|
|
1296
|
+
objects.set(contractKey, mergeObject(objects.get(contractKey), {
|
|
1297
|
+
objectKey: contractKey,
|
|
1298
|
+
objectType: 'datalex_contract',
|
|
1299
|
+
name: contract.name || contract.id,
|
|
1300
|
+
fullName: `${contract.id}@${contract.version}`,
|
|
1301
|
+
domain: domain.name,
|
|
1302
|
+
owner: contract.owner ?? domain.owners?.[0],
|
|
1303
|
+
status: 'contract_evidence',
|
|
1304
|
+
description: contract.description,
|
|
1305
|
+
sourcePath: manifestPath,
|
|
1306
|
+
sourceSystem: 'DataLex manifest',
|
|
1307
|
+
payload: compactObject({
|
|
1308
|
+
contractId: contract.id,
|
|
1309
|
+
version,
|
|
1310
|
+
entity: entity.name,
|
|
1311
|
+
tags: contract.tags ?? [],
|
|
1312
|
+
signature: contract.signature,
|
|
1313
|
+
}),
|
|
1314
|
+
}));
|
|
1315
|
+
putEdge(edges, {
|
|
1316
|
+
edgeType: 'contains',
|
|
1317
|
+
fromKey: entityKey,
|
|
1318
|
+
toKey: contractKey,
|
|
1319
|
+
confidence: 1,
|
|
1320
|
+
payload: { source: 'datalex manifest contract' },
|
|
1321
|
+
});
|
|
1322
|
+
}
|
|
1323
|
+
}
|
|
1324
|
+
}
|
|
1325
|
+
for (const block of Object.values(manifest.blocks ?? {})) {
|
|
1326
|
+
if (!block.datalexContract)
|
|
1327
|
+
continue;
|
|
1328
|
+
const parsed = parseContractRef(block.datalexContract);
|
|
1329
|
+
if (!parsed.ok || !parsed.id)
|
|
1330
|
+
continue;
|
|
1331
|
+
const contractKey = parsed.version
|
|
1332
|
+
? contractKeyByVersionedRef.get(`${parsed.id}@${parsed.version}`)
|
|
1333
|
+
: latestContractKeyById.get(parsed.id);
|
|
1334
|
+
if (!contractKey)
|
|
1335
|
+
continue;
|
|
1336
|
+
putEdge(edges, {
|
|
1337
|
+
edgeType: 'resolves_contract',
|
|
1338
|
+
fromKey: `dql:block:${block.name}`,
|
|
1339
|
+
toKey: contractKey,
|
|
1340
|
+
confidence: 1,
|
|
1341
|
+
payload: {
|
|
1342
|
+
source: 'dql datalex_contract',
|
|
1343
|
+
reference: block.datalexContract,
|
|
1344
|
+
},
|
|
1345
|
+
});
|
|
1346
|
+
}
|
|
1347
|
+
}
|
|
1067
1348
|
function addBlockDependencyEdges(manifest, edges) {
|
|
1068
1349
|
const dbtLookup = buildDbtModelLookup(manifest);
|
|
1069
1350
|
for (const block of Object.values(manifest.blocks ?? {})) {
|
|
@@ -2376,6 +2657,48 @@ function mergeObject(a, b) {
|
|
|
2376
2657
|
payload: compactObject({ ...(a.payload ?? {}), ...(b.payload ?? {}) }),
|
|
2377
2658
|
};
|
|
2378
2659
|
}
|
|
2660
|
+
function buildSourceFingerprints(objects, updatedAt) {
|
|
2661
|
+
const bySource = new Map();
|
|
2662
|
+
for (const object of objects) {
|
|
2663
|
+
const sourcePath = object.sourcePath ?? object.sourceSystem;
|
|
2664
|
+
if (!sourcePath)
|
|
2665
|
+
continue;
|
|
2666
|
+
const list = bySource.get(sourcePath) ?? [];
|
|
2667
|
+
list.push(object);
|
|
2668
|
+
bySource.set(sourcePath, list);
|
|
2669
|
+
}
|
|
2670
|
+
return Array.from(bySource.entries()).map(([sourcePath, rows]) => ({
|
|
2671
|
+
sourcePath,
|
|
2672
|
+
fingerprint: sha256(stableStringify(rows.map((row) => ({
|
|
2673
|
+
objectKey: row.objectKey,
|
|
2674
|
+
objectType: row.objectType,
|
|
2675
|
+
name: row.name,
|
|
2676
|
+
domain: row.domain,
|
|
2677
|
+
status: row.status,
|
|
2678
|
+
payload: row.payload,
|
|
2679
|
+
})))),
|
|
2680
|
+
objectCount: rows.length,
|
|
2681
|
+
updatedAt,
|
|
2682
|
+
})).sort((a, b) => a.sourcePath.localeCompare(b.sourcePath));
|
|
2683
|
+
}
|
|
2684
|
+
function buildDomainShards(objects, updatedAt) {
|
|
2685
|
+
const byDomain = new Map();
|
|
2686
|
+
for (const object of objects) {
|
|
2687
|
+
const domain = object.domain?.trim() || '_uncategorized';
|
|
2688
|
+
const list = byDomain.get(domain) ?? [];
|
|
2689
|
+
list.push(object);
|
|
2690
|
+
byDomain.set(domain, list);
|
|
2691
|
+
}
|
|
2692
|
+
return Array.from(byDomain.entries()).map(([domain, rows]) => ({
|
|
2693
|
+
domain,
|
|
2694
|
+
objectCount: rows.length,
|
|
2695
|
+
blockCount: rows.filter((row) => row.objectType === 'dql_block').length,
|
|
2696
|
+
certifiedBlockCount: rows.filter((row) => row.objectType === 'dql_block' && row.status === 'certified').length,
|
|
2697
|
+
semanticMetricCount: rows.filter((row) => row.objectType === 'semantic_metric').length,
|
|
2698
|
+
dbtObjectCount: rows.filter((row) => row.objectType === 'dbt_model' || row.objectType === 'dbt_source' || row.objectType === 'dbt_column').length,
|
|
2699
|
+
updatedAt,
|
|
2700
|
+
})).sort((a, b) => a.domain.localeCompare(b.domain));
|
|
2701
|
+
}
|
|
2379
2702
|
function fingerprintSnapshot(snapshot) {
|
|
2380
2703
|
return sha256(stableStringify({
|
|
2381
2704
|
projectRoot: snapshot.projectRoot,
|