@chengxianglibra/dsh-data-analysis 0.1.1

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.
Files changed (66) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +72 -0
  3. package/cordis.patch.yml +11 -0
  4. package/lib/bin/environment.js +91 -0
  5. package/lib/bridges.js +11 -0
  6. package/lib/client.js +744 -0
  7. package/lib/compatibility.js +66 -0
  8. package/lib/datasource/access.js +60 -0
  9. package/lib/datasource/bridge-programs.js +80 -0
  10. package/lib/datasource/bridge.js +204 -0
  11. package/lib/datasource/credentials.js +24 -0
  12. package/lib/datasource/index.js +4 -0
  13. package/lib/datasource/shell-env.js +386 -0
  14. package/lib/datasource/test.js +65 -0
  15. package/lib/disclosure/activation.js +369 -0
  16. package/lib/disclosure/bridge-program.js +11 -0
  17. package/lib/disclosure/bridge.js +65 -0
  18. package/lib/disclosure/help.js +227 -0
  19. package/lib/disclosure/index.js +3 -0
  20. package/lib/environment/binding.js +273 -0
  21. package/lib/environment/doctor.js +123 -0
  22. package/lib/environment/errors.js +11 -0
  23. package/lib/environment/index.js +7 -0
  24. package/lib/environment/runtime.js +558 -0
  25. package/lib/environment/source.js +6 -0
  26. package/lib/environment/subprocess.js +178 -0
  27. package/lib/environment/summary.js +19 -0
  28. package/lib/environment/workspace.js +49 -0
  29. package/lib/evidence/bridge-program.js +153 -0
  30. package/lib/evidence/bridge.js +166 -0
  31. package/lib/evidence/index.js +2 -0
  32. package/lib/evidence/sources.js +263 -0
  33. package/lib/index.js +7 -0
  34. package/lib/plugin.js +179 -0
  35. package/lib/types/bridges.d.ts +11 -0
  36. package/lib/types/client.d.ts +125 -0
  37. package/lib/types/compatibility.d.ts +29 -0
  38. package/lib/types/datasource/access.d.ts +27 -0
  39. package/lib/types/datasource/bridge.d.ts +53 -0
  40. package/lib/types/datasource/index.d.ts +8 -0
  41. package/lib/types/datasource/shell-env.d.ts +38 -0
  42. package/lib/types/datasource/test.d.ts +32 -0
  43. package/lib/types/disclosure/activation.d.ts +73 -0
  44. package/lib/types/disclosure/bridge.d.ts +28 -0
  45. package/lib/types/disclosure/help.d.ts +64 -0
  46. package/lib/types/disclosure/index.d.ts +5 -0
  47. package/lib/types/environment/binding.d.ts +26 -0
  48. package/lib/types/environment/doctor.d.ts +8 -0
  49. package/lib/types/environment/errors.d.ts +7 -0
  50. package/lib/types/environment/index.d.ts +9 -0
  51. package/lib/types/environment/runtime.d.ts +17 -0
  52. package/lib/types/environment/source.d.ts +7 -0
  53. package/lib/types/environment/subprocess.d.ts +19 -0
  54. package/lib/types/environment/types.d.ts +96 -0
  55. package/lib/types/environment/workspace.d.ts +10 -0
  56. package/lib/types/evidence/bridge.d.ts +40 -0
  57. package/lib/types/evidence/index.d.ts +4 -0
  58. package/lib/types/evidence/sources.d.ts +57 -0
  59. package/lib/types/index.d.ts +9 -0
  60. package/lib/types/plugin.d.ts +43 -0
  61. package/package.json +149 -0
  62. package/python/report-kit/dist/dsh_data_analysis_report_kit-3.0.0-py3-none-any.whl +0 -0
  63. package/skills/dsh-data-analysis-report/SKILL.md +57 -0
  64. package/skills/dsh-data-analysis-report/assets/marivo-artifact.js +113 -0
  65. package/skills/dsh-data-analysis-report/assets/marivo-session-dag.js +1201 -0
  66. package/skills/dsh-data-analysis-report/assets/report-data.js +449 -0
@@ -0,0 +1,449 @@
1
+ ;((scope) => {
2
+ const datasets = new Map()
3
+ const recordViews = new Map()
4
+ const DATASET_ID = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/
5
+ const TIMESTAMP = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})$/
6
+ const ARTIFACT_ROLES = new Set(['time', 'dimension', 'value', 'measure', 'unknown'])
7
+ const ISSUE_CATEGORIES = new Set([
8
+ 'data_quality',
9
+ 'comparability',
10
+ 'evidence_availability',
11
+ 'candidate_resolution',
12
+ ])
13
+
14
+ class ReportDataError extends Error {
15
+ constructor(code, path, message) {
16
+ super(`${path}: ${message}`)
17
+ this.name = 'ReportDataError'
18
+ this.code = code
19
+ this.path = path
20
+ }
21
+ }
22
+
23
+ function fail(path, message, code = 'dataset-invalid') {
24
+ throw new ReportDataError(code, path, message)
25
+ }
26
+
27
+ function isObject(value) {
28
+ return typeof value === 'object' && value !== null && !Array.isArray(value)
29
+ }
30
+
31
+ function object(value, path) {
32
+ if (!isObject(value)) fail(path, 'must be an object')
33
+ return value
34
+ }
35
+
36
+ function exactKeys(value, path, required, optional = []) {
37
+ const keys = Object.keys(value)
38
+ const allowed = new Set([...required, ...optional])
39
+ for (const key of required) {
40
+ if (!Object.hasOwn(value, key)) fail(`${path}.${key}`, 'is required')
41
+ }
42
+ for (const key of keys) {
43
+ if (!allowed.has(key)) fail(`${path}.${key}`, 'is not allowed')
44
+ }
45
+ }
46
+
47
+ function string(value, path, { nullable = false, maxLength = 2048 } = {}) {
48
+ if (nullable && value === null) return
49
+ if (typeof value !== 'string' || value.length === 0 || value.length > maxLength) {
50
+ fail(path, `must be a non-empty string of at most ${maxLength} characters`)
51
+ }
52
+ }
53
+
54
+ function integer(value, path, { nullable = false, maximum = Number.MAX_SAFE_INTEGER } = {}) {
55
+ if (nullable && value === null) return
56
+ if (!Number.isSafeInteger(value) || value < 0 || value > maximum) {
57
+ fail(path, `must be an integer between 0 and ${maximum}`)
58
+ }
59
+ }
60
+
61
+ function number(value, path, { nullable = false } = {}) {
62
+ if (nullable && value === null) return
63
+ if (typeof value !== 'number' || !Number.isFinite(value)) fail(path, 'must be a finite number')
64
+ }
65
+
66
+ function timestamp(value, path) {
67
+ if (
68
+ typeof value !== 'string' ||
69
+ !TIMESTAMP.test(value) ||
70
+ !Number.isFinite(Date.parse(value))
71
+ ) {
72
+ fail(path, 'must be an RFC 3339 timestamp')
73
+ }
74
+ }
75
+
76
+ function stringArray(value, path, maximum = 100) {
77
+ if (!Array.isArray(value) || value.length > maximum) {
78
+ fail(path, `must be an array with at most ${maximum} items`)
79
+ }
80
+ value.forEach((item, index) => {
81
+ string(item, `${path}[${index}]`)
82
+ })
83
+ }
84
+
85
+ function validateRepair(value, path, nullable) {
86
+ if (nullable && value === null) return
87
+ const repair = object(value, path)
88
+ exactKeys(repair, path, ['kind', 'action', 'help_target'])
89
+ if (
90
+ !['retry', 'inspect', 'user_choice', 'semantic_authoring', 'environment'].includes(
91
+ repair.kind,
92
+ )
93
+ ) {
94
+ fail(`${path}.kind`, 'is unsupported')
95
+ }
96
+ string(repair.action, `${path}.action`)
97
+ string(repair.help_target, `${path}.help_target`)
98
+ }
99
+
100
+ function validateIssue(value, path, allowEvidenceRule = false) {
101
+ const issue = object(value, path)
102
+ string(issue.category, `${path}.category`)
103
+ if (allowEvidenceRule && issue.category === 'evidence_rule') {
104
+ exactKeys(issue, path, ['category', 'kind', 'severity', 'expected', 'received', 'repair'])
105
+ string(issue.kind, `${path}.kind`)
106
+ if (!['warning', 'blocking'].includes(issue.severity))
107
+ fail(`${path}.severity`, 'is unsupported')
108
+ string(issue.expected, `${path}.expected`)
109
+ string(issue.received, `${path}.received`)
110
+ validateRepair(issue.repair, `${path}.repair`, false)
111
+ return
112
+ }
113
+ if (!ISSUE_CATEGORIES.has(issue.category)) fail(`${path}.category`, 'is unsupported')
114
+ if (issue.category === 'data_quality') {
115
+ exactKeys(issue, path, ['category', 'kind', 'severity', 'check_id', 'expectation', 'repair'])
116
+ string(issue.check_id, `${path}.check_id`)
117
+ string(issue.expectation, `${path}.expectation`)
118
+ } else if (issue.category === 'comparability') {
119
+ exactKeys(issue, path, [
120
+ 'category',
121
+ 'kind',
122
+ 'severity',
123
+ 'incompatible_fields',
124
+ 'approximation_details',
125
+ 'repair',
126
+ ])
127
+ stringArray(issue.incompatible_fields, `${path}.incompatible_fields`)
128
+ stringArray(issue.approximation_details, `${path}.approximation_details`)
129
+ } else if (issue.category === 'evidence_availability') {
130
+ exactKeys(issue, path, [
131
+ 'category',
132
+ 'kind',
133
+ 'severity',
134
+ 'failed_stage',
135
+ 'findings_available',
136
+ 'stable_error_category',
137
+ 'repair',
138
+ ])
139
+ if (!['extract', 'digest', 'store'].includes(issue.failed_stage)) {
140
+ fail(`${path}.failed_stage`, 'is unsupported')
141
+ }
142
+ if (typeof issue.findings_available !== 'boolean') {
143
+ fail(`${path}.findings_available`, 'must be boolean')
144
+ }
145
+ string(issue.stable_error_category, `${path}.stable_error_category`)
146
+ } else {
147
+ exactKeys(issue, path, ['category', 'kind', 'severity', 'historical', 'repair'])
148
+ if (issue.severity !== 'warning') fail(`${path}.severity`, 'must be warning')
149
+ if (typeof issue.historical !== 'boolean') fail(`${path}.historical`, 'must be boolean')
150
+ }
151
+ string(issue.kind, `${path}.kind`)
152
+ if (!['warning', 'blocking'].includes(issue.severity))
153
+ fail(`${path}.severity`, 'is unsupported')
154
+ validateRepair(issue.repair, `${path}.repair`, issue.category !== 'candidate_resolution')
155
+ }
156
+
157
+ function validateQuality(value, path) {
158
+ if (value === null) return
159
+ const quality = object(value, path)
160
+ const fields = [
161
+ 'coverage',
162
+ 'null_rate',
163
+ 'sample_size',
164
+ 'sample_coverage_min',
165
+ 'sample_coverage_avg',
166
+ 'sample_coverage_partial_buckets',
167
+ 'zero_denominator_rows',
168
+ 'evaluated_check_count',
169
+ 'failed_check_count',
170
+ 'warning_check_count',
171
+ ]
172
+ exactKeys(quality, path, fields)
173
+ for (const field of ['coverage', 'null_rate', 'sample_coverage_min', 'sample_coverage_avg']) {
174
+ number(quality[field], `${path}.${field}`, { nullable: true })
175
+ }
176
+ for (const field of fields
177
+ .slice(2)
178
+ .filter((field) => !['sample_coverage_min', 'sample_coverage_avg'].includes(field))) {
179
+ integer(quality[field], `${path}.${field}`, { nullable: true })
180
+ }
181
+ }
182
+
183
+ function validateRevalidation(value, path) {
184
+ const revalidation = object(value, path)
185
+ string(revalidation.status, `${path}.status`)
186
+ if (revalidation.status === 'not_checked') {
187
+ exactKeys(revalidation, path, ['status'])
188
+ return
189
+ }
190
+ if (revalidation.status !== 'checked') fail(`${path}.status`, 'is unsupported')
191
+ exactKeys(revalidation, path, [
192
+ 'status',
193
+ 'result',
194
+ 'semantic_status',
195
+ 'evidence_status',
196
+ 'dependency_status',
197
+ 'checked_at',
198
+ 'issues',
199
+ 'issues_omitted',
200
+ ])
201
+ if (!['admissible', 'stale', 'indeterminate'].includes(revalidation.result)) {
202
+ fail(`${path}.result`, 'is unsupported')
203
+ }
204
+ if (!['current', 'stale', 'indeterminate'].includes(revalidation.semantic_status)) {
205
+ fail(`${path}.semantic_status`, 'is unsupported')
206
+ }
207
+ if (!['complete', 'partial', 'unavailable'].includes(revalidation.evidence_status)) {
208
+ fail(`${path}.evidence_status`, 'is unsupported')
209
+ }
210
+ if (!['admissible', 'stale', 'indeterminate'].includes(revalidation.dependency_status)) {
211
+ fail(`${path}.dependency_status`, 'is unsupported')
212
+ }
213
+ timestamp(revalidation.checked_at, `${path}.checked_at`)
214
+ if (!Array.isArray(revalidation.issues) || revalidation.issues.length > 100) {
215
+ fail(`${path}.issues`, 'must contain at most 100 issues')
216
+ }
217
+ revalidation.issues.forEach((issue, index) => {
218
+ validateIssue(issue, `${path}.issues[${index}]`, true)
219
+ })
220
+ integer(revalidation.issues_omitted, `${path}.issues_omitted`)
221
+ }
222
+
223
+ function validateSource(value, path, table) {
224
+ const source = object(value, path)
225
+ string(source.kind, `${path}.kind`)
226
+ if (source.kind === 'computed') {
227
+ exactKeys(source, path, ['kind'])
228
+ if (Object.hasOwn(table, 'semantic_shape')) {
229
+ fail('$.table.semantic_shape', 'is not allowed for a computed dataset')
230
+ }
231
+ return 'computed'
232
+ }
233
+ if (source.kind !== 'marivo_artifact') fail(`${path}.kind`, 'is unsupported')
234
+ exactKeys(source, path, [
235
+ 'kind',
236
+ 'detail',
237
+ 'artifact',
238
+ 'quality_summary',
239
+ 'issues',
240
+ 'issues_omitted',
241
+ 'lineage',
242
+ 'revalidation',
243
+ ])
244
+ if (!['reader', 'audit'].includes(source.detail)) fail(`${path}.detail`, 'is unsupported')
245
+ const artifact = object(source.artifact, `${path}.artifact`)
246
+ exactKeys(artifact, `${path}.artifact`, [
247
+ 'session_id',
248
+ 'ref',
249
+ 'kind',
250
+ 'artifact_schema_version',
251
+ 'content_hash',
252
+ 'created_at',
253
+ 'row_count',
254
+ 'evidence_status',
255
+ 'finding_count',
256
+ ])
257
+ for (const field of ['session_id', 'ref', 'kind', 'artifact_schema_version']) {
258
+ string(artifact[field], `${path}.artifact.${field}`)
259
+ }
260
+ string(artifact.content_hash, `${path}.artifact.content_hash`, { nullable: true })
261
+ timestamp(artifact.created_at, `${path}.artifact.created_at`)
262
+ integer(artifact.row_count, `${path}.artifact.row_count`)
263
+ integer(artifact.finding_count, `${path}.artifact.finding_count`)
264
+ if (!['complete', 'partial', 'unavailable'].includes(artifact.evidence_status)) {
265
+ fail(`${path}.artifact.evidence_status`, 'is unsupported')
266
+ }
267
+ validateQuality(source.quality_summary, `${path}.quality_summary`)
268
+ if (!Array.isArray(source.issues) || source.issues.length > 100) {
269
+ fail(`${path}.issues`, 'must contain at most 100 issues')
270
+ }
271
+ source.issues.forEach((issue, index) => {
272
+ validateIssue(issue, `${path}.issues[${index}]`)
273
+ })
274
+ integer(source.issues_omitted, `${path}.issues_omitted`)
275
+ const lineage = object(source.lineage, `${path}.lineage`)
276
+ exactKeys(lineage, `${path}.lineage`, [
277
+ 'external_inputs',
278
+ 'external_inputs_omitted',
279
+ 'steps',
280
+ 'steps_omitted',
281
+ ])
282
+ stringArray(lineage.external_inputs, `${path}.lineage.external_inputs`)
283
+ integer(lineage.external_inputs_omitted, `${path}.lineage.external_inputs_omitted`)
284
+ if (!Array.isArray(lineage.steps) || lineage.steps.length > 100) {
285
+ fail(`${path}.lineage.steps`, 'must contain at most 100 steps')
286
+ }
287
+ lineage.steps.forEach((candidate, index) => {
288
+ const stepPath = `${path}.lineage.steps[${index}]`
289
+ const step = object(candidate, stepPath)
290
+ exactKeys(step, stepPath, [
291
+ 'intent',
292
+ 'job_ref',
293
+ 'inputs',
294
+ 'params_digest',
295
+ 'analysis_purpose',
296
+ ])
297
+ string(step.intent, `${stepPath}.intent`)
298
+ string(step.job_ref, `${stepPath}.job_ref`)
299
+ stringArray(step.inputs, `${stepPath}.inputs`)
300
+ string(step.params_digest, `${stepPath}.params_digest`)
301
+ string(step.analysis_purpose, `${stepPath}.analysis_purpose`, { nullable: true })
302
+ })
303
+ integer(lineage.steps_omitted, `${path}.lineage.steps_omitted`)
304
+ validateRevalidation(source.revalidation, `${path}.revalidation`)
305
+ if (!Object.hasOwn(table, 'semantic_shape')) {
306
+ fail('$.table.semantic_shape', 'is required for an Artifact dataset')
307
+ }
308
+ string(table.semantic_shape, '$.table.semantic_shape', { nullable: true })
309
+ if (artifact.row_count !== table.total_rows) {
310
+ fail(`${path}.artifact.row_count`, 'must equal $.table.total_rows')
311
+ }
312
+ return 'marivo_artifact'
313
+ }
314
+
315
+ function validateDataset(value, registrationId) {
316
+ const dataset = object(value, '$')
317
+ exactKeys(dataset, '$', ['schema', 'dataset_id', 'emitted_at', 'source', 'table'])
318
+ if (dataset.schema !== 'dsh-data-analysis-dataset/v2') fail('$.schema', 'is unsupported')
319
+ if (typeof dataset.dataset_id !== 'string' || !DATASET_ID.test(dataset.dataset_id)) {
320
+ fail('$.dataset_id', 'is invalid')
321
+ }
322
+ if (dataset.dataset_id !== registrationId) {
323
+ fail('$.dataset_id', 'must match the registered id', 'dataset-id-mismatch')
324
+ }
325
+ timestamp(dataset.emitted_at, '$.emitted_at')
326
+ const table = object(dataset.table, '$.table')
327
+ exactKeys(
328
+ table,
329
+ '$.table',
330
+ ['total_rows', 'written_rows', 'omitted_rows', 'columns', 'rows'],
331
+ ['semantic_shape'],
332
+ )
333
+ integer(table.total_rows, '$.table.total_rows')
334
+ integer(table.written_rows, '$.table.written_rows', { maximum: 100000 })
335
+ integer(table.omitted_rows, '$.table.omitted_rows')
336
+ if (table.total_rows !== table.written_rows + table.omitted_rows) {
337
+ fail('$.table.total_rows', 'must equal written_rows + omitted_rows')
338
+ }
339
+ if (!Array.isArray(table.columns) || table.columns.length > 100) {
340
+ fail('$.table.columns', 'must contain at most 100 columns')
341
+ }
342
+ const sourceKind = validateSource(dataset.source, '$.source', table)
343
+ const names = new Set()
344
+ table.columns.forEach((candidate, index) => {
345
+ const columnPath = `$.table.columns[${index}]`
346
+ const column = object(candidate, columnPath)
347
+ const computedKeys = ['name', 'dtype', 'contains_null']
348
+ const artifactKeys = [...computedKeys, 'artifact_dtype', 'nullable', 'role']
349
+ exactKeys(column, columnPath, sourceKind === 'computed' ? computedKeys : artifactKeys)
350
+ string(column.name, `${columnPath}.name`)
351
+ string(column.dtype, `${columnPath}.dtype`)
352
+ if (names.has(column.name)) fail(`${columnPath}.name`, 'must be unique')
353
+ names.add(column.name)
354
+ if (typeof column.contains_null !== 'boolean')
355
+ fail(`${columnPath}.contains_null`, 'must be boolean')
356
+ if (sourceKind === 'marivo_artifact') {
357
+ string(column.artifact_dtype, `${columnPath}.artifact_dtype`)
358
+ if (typeof column.nullable !== 'boolean') fail(`${columnPath}.nullable`, 'must be boolean')
359
+ if (!ARTIFACT_ROLES.has(column.role)) fail(`${columnPath}.role`, 'is unsupported')
360
+ }
361
+ })
362
+ if (!Array.isArray(table.rows) || table.rows.length > 100000) {
363
+ fail('$.table.rows', 'must contain at most 100000 rows')
364
+ }
365
+ if (table.rows.length !== table.written_rows) {
366
+ fail('$.table.written_rows', 'must equal rows.length')
367
+ }
368
+ table.rows.forEach((row, rowIndex) => {
369
+ const rowPath = `$.table.rows[${rowIndex}]`
370
+ if (!Array.isArray(row) || row.length !== table.columns.length) {
371
+ fail(rowPath, `must contain exactly ${table.columns.length} cells`)
372
+ }
373
+ row.forEach((cell, cellIndex) => {
374
+ if (!['string', 'number', 'boolean'].includes(typeof cell) && cell !== null) {
375
+ fail(`${rowPath}[${cellIndex}]`, 'must be a JSON scalar')
376
+ }
377
+ if (typeof cell === 'number' && !Number.isFinite(cell)) {
378
+ fail(`${rowPath}[${cellIndex}]`, 'must be finite')
379
+ }
380
+ })
381
+ })
382
+ return dataset
383
+ }
384
+
385
+ function deepFreeze(value, seen = new WeakSet()) {
386
+ if (!isObject(value) && !Array.isArray(value)) return value
387
+ if (seen.has(value)) return value
388
+ seen.add(value)
389
+ for (const child of Object.values(value)) deepFreeze(child, seen)
390
+ return Object.freeze(value)
391
+ }
392
+
393
+ function validateId(id) {
394
+ if (typeof id !== 'string' || !DATASET_ID.test(id)) {
395
+ fail('$id', 'is invalid', 'dataset-id-invalid')
396
+ }
397
+ }
398
+
399
+ function register(id, dataset) {
400
+ validateId(id)
401
+ if (datasets.has(id)) fail('$id', 'is already registered', 'dataset-id-duplicate')
402
+ datasets.set(id, deepFreeze(validateDataset(dataset, id)))
403
+ }
404
+
405
+ function get(id) {
406
+ validateId(id)
407
+ const dataset = datasets.get(id)
408
+ if (!dataset) fail('$id', 'is not registered', 'dataset-not-found')
409
+ return dataset
410
+ }
411
+
412
+ function records(id) {
413
+ if (recordViews.has(id)) return recordViews.get(id)
414
+ const dataset = get(id)
415
+ const names = dataset.table.columns.map((column) => column.name)
416
+ const recordsForId = dataset.table.rows.map((row) => {
417
+ const record = {}
418
+ names.forEach((name, index) => {
419
+ Object.defineProperty(record, name, {
420
+ configurable: false,
421
+ enumerable: true,
422
+ value: row[index],
423
+ writable: false,
424
+ })
425
+ })
426
+ return Object.freeze(record)
427
+ })
428
+ const frozen = Object.freeze(recordsForId)
429
+ recordViews.set(id, frozen)
430
+ return frozen
431
+ }
432
+
433
+ function has(id) {
434
+ validateId(id)
435
+ return datasets.has(id)
436
+ }
437
+
438
+ function list() {
439
+ return Object.freeze([...datasets.keys()])
440
+ }
441
+
442
+ const api = Object.freeze({ get, has, list, records, register })
443
+ Object.defineProperty(scope, 'ReportData', {
444
+ configurable: false,
445
+ enumerable: true,
446
+ value: api,
447
+ writable: false,
448
+ })
449
+ })(globalThis)