retold-facto 0.0.4

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 (92) hide show
  1. package/.claude/launch.json +11 -0
  2. package/.dockerignore +8 -0
  3. package/.quackage.json +19 -0
  4. package/Dockerfile +26 -0
  5. package/bin/retold-facto.js +909 -0
  6. package/examples/facto-government-data.sqlite +0 -0
  7. package/examples/government-data-catalog.json +137 -0
  8. package/examples/government-data-loader.js +1432 -0
  9. package/package.json +91 -0
  10. package/scripts/facto-download.js +425 -0
  11. package/source/Retold-Facto.js +1042 -0
  12. package/source/services/Retold-Facto-BeaconProvider.js +511 -0
  13. package/source/services/Retold-Facto-CatalogManager.js +1252 -0
  14. package/source/services/Retold-Facto-DataLakeService.js +1642 -0
  15. package/source/services/Retold-Facto-DatasetManager.js +417 -0
  16. package/source/services/Retold-Facto-IngestEngine.js +1315 -0
  17. package/source/services/Retold-Facto-ProjectionEngine.js +3960 -0
  18. package/source/services/Retold-Facto-RecordManager.js +360 -0
  19. package/source/services/Retold-Facto-SchemaManager.js +1110 -0
  20. package/source/services/Retold-Facto-SourceFolderScanner.js +2243 -0
  21. package/source/services/Retold-Facto-SourceManager.js +730 -0
  22. package/source/services/Retold-Facto-StoreConnectionManager.js +441 -0
  23. package/source/services/Retold-Facto-ThroughputMonitor.js +478 -0
  24. package/source/services/web-app/codemirror-entry.js +7 -0
  25. package/source/services/web-app/pict-app/Pict-Application-Facto-Configuration.json +9 -0
  26. package/source/services/web-app/pict-app/Pict-Application-Facto.js +70 -0
  27. package/source/services/web-app/pict-app/Pict-Facto-Bundle.js +11 -0
  28. package/source/services/web-app/pict-app/providers/Pict-Provider-Facto-UI.js +66 -0
  29. package/source/services/web-app/pict-app/providers/Pict-Provider-Facto.js +69 -0
  30. package/source/services/web-app/pict-app/providers/facto-api/Facto-API-Catalog.js +93 -0
  31. package/source/services/web-app/pict-app/providers/facto-api/Facto-API-Connections.js +42 -0
  32. package/source/services/web-app/pict-app/providers/facto-api/Facto-API-Datasets.js +605 -0
  33. package/source/services/web-app/pict-app/providers/facto-api/Facto-API-Projections.js +188 -0
  34. package/source/services/web-app/pict-app/providers/facto-api/Facto-API-Scanner.js +80 -0
  35. package/source/services/web-app/pict-app/providers/facto-api/Facto-API-Schema.js +116 -0
  36. package/source/services/web-app/pict-app/providers/facto-api/Facto-API-Sources.js +104 -0
  37. package/source/services/web-app/pict-app/views/PictView-Facto-Catalog.js +526 -0
  38. package/source/services/web-app/pict-app/views/PictView-Facto-Datasets.js +173 -0
  39. package/source/services/web-app/pict-app/views/PictView-Facto-Ingest.js +259 -0
  40. package/source/services/web-app/pict-app/views/PictView-Facto-Layout.js +191 -0
  41. package/source/services/web-app/pict-app/views/PictView-Facto-Projections.js +231 -0
  42. package/source/services/web-app/pict-app/views/PictView-Facto-Records.js +326 -0
  43. package/source/services/web-app/pict-app/views/PictView-Facto-Scanner.js +624 -0
  44. package/source/services/web-app/pict-app/views/PictView-Facto-Sources.js +201 -0
  45. package/source/services/web-app/pict-app/views/PictView-Facto-Throughput.js +456 -0
  46. package/source/services/web-app/pict-app-full/Pict-Application-Facto-Full-Configuration.json +14 -0
  47. package/source/services/web-app/pict-app-full/Pict-Application-Facto-Full.js +391 -0
  48. package/source/services/web-app/pict-app-full/providers/PictRouter-Facto-Configuration.json +56 -0
  49. package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-BottomBar.js +68 -0
  50. package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-Connections.js +340 -0
  51. package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-Dashboard.js +149 -0
  52. package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-Dashboards.js +819 -0
  53. package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-Datasets.js +178 -0
  54. package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-IngestJobs.js +99 -0
  55. package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-Layout.js +62 -0
  56. package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-MappingEditor.js +158 -0
  57. package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-ProjectionDetail.js +1120 -0
  58. package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-Projections.js +172 -0
  59. package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-QueryPanel.js +119 -0
  60. package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-RecordViewer.js +663 -0
  61. package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-Records.js +648 -0
  62. package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-Scanner.js +1017 -0
  63. package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-SchemaDetail.js +1404 -0
  64. package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-SchemaDocEditor.js +1036 -0
  65. package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-SchemaEditor.js +636 -0
  66. package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-SchemaResearch.js +357 -0
  67. package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-SourceDetail.js +822 -0
  68. package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-SourceEditor.js +1036 -0
  69. package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-SourceResearch.js +487 -0
  70. package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-Sources.js +165 -0
  71. package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-Throughput.js +439 -0
  72. package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-TopBar.js +335 -0
  73. package/source/services/web-app/pict-app-full/views/projections/Facto-Projections-Constants.js +71 -0
  74. package/source/services/web-app/web/chart.min.js +20 -0
  75. package/source/services/web-app/web/codemirror-bundle.js +30099 -0
  76. package/source/services/web-app/web/css/facto-themes.css +467 -0
  77. package/source/services/web-app/web/css/facto.css +502 -0
  78. package/source/services/web-app/web/index.html +28 -0
  79. package/source/services/web-app/web/retold-facto.js +12138 -0
  80. package/source/services/web-app/web/retold-facto.js.map +1 -0
  81. package/source/services/web-app/web/retold-facto.min.js +2 -0
  82. package/source/services/web-app/web/retold-facto.min.js.map +1 -0
  83. package/source/services/web-app/web/simple/index.html +17 -0
  84. package/test/Facto_Browser_Integration_tests.js +798 -0
  85. package/test/RetoldFacto_tests.js +4117 -0
  86. package/test/fixtures/weather-readings.csv +17 -0
  87. package/test/fixtures/weather-stations.csv +9 -0
  88. package/test/model/MeadowModel-Extended.json +8497 -0
  89. package/test/model/MeadowModel-PICT.json +1 -0
  90. package/test/model/MeadowModel.json +1355 -0
  91. package/test/model/ddl/Facto.ddl +225 -0
  92. package/test/model/fable-configuration.json +14 -0
@@ -0,0 +1,360 @@
1
+ /**
2
+ * Retold Facto - Record Manager Service
3
+ *
4
+ * Manages record ingest, versioning, and certainty index operations.
5
+ * Provides endpoints for batch ingest, version history, and certainty
6
+ * tracking beyond the auto-generated Meadow CRUD endpoints.
7
+ *
8
+ * @author Steven Velozo <steven@velozo.com>
9
+ */
10
+ const libFableServiceProviderBase = require('fable-serviceproviderbase');
11
+
12
+ const defaultRecordManagerOptions = (
13
+ {
14
+ RoutePrefix: '/facto',
15
+ DefaultCertaintyValue: 0.5
16
+ });
17
+
18
+ class RetoldFactoRecordManager extends libFableServiceProviderBase
19
+ {
20
+ constructor(pFable, pOptions, pServiceHash)
21
+ {
22
+ let tmpOptions = Object.assign({}, defaultRecordManagerOptions, pOptions);
23
+ super(pFable, tmpOptions, pServiceHash);
24
+
25
+ this.serviceType = 'RetoldFactoRecordManager';
26
+ }
27
+
28
+ /**
29
+ * Ingest a single record: create it via the DAL, then auto-create a
30
+ * CertaintyIndex entry at the default certainty value.
31
+ *
32
+ * @param {Object} pRecordData - The record fields to create
33
+ * @param {function} fCallback - (pError, pCreatedRecord)
34
+ */
35
+ ingestSingleRecord(pRecordData, fCallback)
36
+ {
37
+ if (!this.fable.DAL || !this.fable.DAL.Record)
38
+ {
39
+ return fCallback(new Error('Record DAL not initialized'));
40
+ }
41
+
42
+ // Set IngestDate to now if not provided
43
+ if (!pRecordData.IngestDate)
44
+ {
45
+ pRecordData.IngestDate = new Date().toISOString();
46
+ }
47
+
48
+ // Default Version to 1 if not provided
49
+ if (!pRecordData.Version)
50
+ {
51
+ pRecordData.Version = 1;
52
+ }
53
+
54
+ let tmpCreateQuery = this.fable.DAL.Record.query.clone()
55
+ .addRecord(pRecordData);
56
+
57
+ this.fable.DAL.Record.doCreate(tmpCreateQuery,
58
+ (pError, pQuery, pQueryRead, pRecord) =>
59
+ {
60
+ if (pError)
61
+ {
62
+ return fCallback(pError);
63
+ }
64
+
65
+ if (!pRecord || !pRecord.IDRecord)
66
+ {
67
+ return fCallback(new Error('Record creation returned no record'));
68
+ }
69
+
70
+ // Auto-create a CertaintyIndex entry
71
+ if (!this.fable.DAL.CertaintyIndex)
72
+ {
73
+ // No CertaintyIndex DAL; return the record without certainty
74
+ return fCallback(null, pRecord);
75
+ }
76
+
77
+ let tmpCertaintyQuery = this.fable.DAL.CertaintyIndex.query.clone()
78
+ .addRecord(
79
+ {
80
+ IDRecord: pRecord.IDRecord,
81
+ CertaintyValue: this.options.DefaultCertaintyValue,
82
+ Dimension: 'overall',
83
+ Justification: 'Default certainty assigned at ingest'
84
+ });
85
+
86
+ this.fable.DAL.CertaintyIndex.doCreate(tmpCertaintyQuery,
87
+ (pCertaintyError, pCertaintyQuery, pCertaintyQueryRead, pCertaintyRecord) =>
88
+ {
89
+ if (pCertaintyError)
90
+ {
91
+ this.fable.log.warn(`RecordManager warning: Record ${pRecord.IDRecord} created but CertaintyIndex creation failed: ${pCertaintyError}`);
92
+ }
93
+ return fCallback(null, pRecord);
94
+ });
95
+ });
96
+ }
97
+
98
+ /**
99
+ * Connect REST API routes for record management.
100
+ *
101
+ * @param {object} pOratorServiceServer - The Orator service server instance
102
+ */
103
+ connectRoutes(pOratorServiceServer)
104
+ {
105
+ let tmpRoutePrefix = this.options.RoutePrefix;
106
+
107
+ // POST /facto/record/ingest -- ingest a batch of records
108
+ pOratorServiceServer.doPost(`${tmpRoutePrefix}/record/ingest`,
109
+ (pRequest, pResponse, fNext) =>
110
+ {
111
+ let tmpBody = pRequest.body || {};
112
+ let tmpRecords = tmpBody.Records || tmpBody.records || [];
113
+ let tmpIDDataset = tmpBody.IDDataset || 0;
114
+ let tmpIDSource = tmpBody.IDSource || 0;
115
+
116
+ if (!Array.isArray(tmpRecords) || tmpRecords.length === 0)
117
+ {
118
+ pResponse.send(
119
+ {
120
+ Error: 'No records provided. Send { Records: [...], IDDataset: N, IDSource: N }',
121
+ Ingested: 0,
122
+ DefaultCertainty: this.options.DefaultCertaintyValue
123
+ });
124
+ return fNext();
125
+ }
126
+
127
+ let tmpIngested = 0;
128
+ let tmpErrors = 0;
129
+ let tmpCreatedRecords = [];
130
+
131
+ let tmpAnticipate = this.fable.newAnticipate();
132
+
133
+ for (let i = 0; i < tmpRecords.length; i++)
134
+ {
135
+ let tmpRecordData = tmpRecords[i];
136
+ tmpAnticipate.anticipate(
137
+ (fStep) =>
138
+ {
139
+ // Apply batch-level defaults
140
+ if (tmpIDDataset && !tmpRecordData.IDDataset)
141
+ {
142
+ tmpRecordData.IDDataset = tmpIDDataset;
143
+ }
144
+ if (tmpIDSource && !tmpRecordData.IDSource)
145
+ {
146
+ tmpRecordData.IDSource = tmpIDSource;
147
+ }
148
+
149
+ this.ingestSingleRecord(tmpRecordData,
150
+ (pError, pRecord) =>
151
+ {
152
+ if (pError)
153
+ {
154
+ tmpErrors++;
155
+ this.fable.log.error(`RecordManager ingest error: ${pError}`);
156
+ }
157
+ else
158
+ {
159
+ tmpIngested++;
160
+ tmpCreatedRecords.push(pRecord);
161
+ }
162
+ return fStep();
163
+ });
164
+ });
165
+ }
166
+
167
+ tmpAnticipate.wait(
168
+ (pError) =>
169
+ {
170
+ pResponse.send(
171
+ {
172
+ Ingested: tmpIngested,
173
+ Errors: tmpErrors,
174
+ Total: tmpRecords.length,
175
+ DefaultCertainty: this.options.DefaultCertaintyValue,
176
+ Records: tmpCreatedRecords
177
+ });
178
+ return fNext();
179
+ });
180
+ });
181
+
182
+ // GET /facto/record/:IDRecord/certainty -- get all certainty indices for a record
183
+ pOratorServiceServer.doGet(`${tmpRoutePrefix}/record/:IDRecord/certainty`,
184
+ (pRequest, pResponse, fNext) =>
185
+ {
186
+ let tmpIDRecord = parseInt(pRequest.params.IDRecord, 10);
187
+ if (isNaN(tmpIDRecord) || tmpIDRecord < 1)
188
+ {
189
+ pResponse.send({ Error: 'Invalid IDRecord parameter', CertaintyIndices: [] });
190
+ return fNext();
191
+ }
192
+
193
+ if (!this.fable.DAL || !this.fable.DAL.CertaintyIndex)
194
+ {
195
+ pResponse.send({ Error: 'CertaintyIndex DAL not initialized', CertaintyIndices: [] });
196
+ return fNext();
197
+ }
198
+
199
+ let tmpQuery = this.fable.DAL.CertaintyIndex.query.clone()
200
+ .addFilter('IDRecord', tmpIDRecord)
201
+ .addFilter('Deleted', 0);
202
+
203
+ this.fable.DAL.CertaintyIndex.doReads(tmpQuery,
204
+ (pError, pQuery, pRecords) =>
205
+ {
206
+ if (pError)
207
+ {
208
+ this.fable.log.error(`RecordManager error reading certainty for record ${tmpIDRecord}: ${pError}`);
209
+ pResponse.send({ Error: pError.message || pError, CertaintyIndices: [] });
210
+ return fNext();
211
+ }
212
+ pResponse.send({ IDRecord: tmpIDRecord, Count: pRecords.length, CertaintyIndices: pRecords });
213
+ return fNext();
214
+ });
215
+ });
216
+
217
+ // POST /facto/record/:IDRecord/certainty -- add a new certainty index entry
218
+ pOratorServiceServer.doPost(`${tmpRoutePrefix}/record/:IDRecord/certainty`,
219
+ (pRequest, pResponse, fNext) =>
220
+ {
221
+ let tmpIDRecord = parseInt(pRequest.params.IDRecord, 10);
222
+ if (isNaN(tmpIDRecord) || tmpIDRecord < 1)
223
+ {
224
+ pResponse.send({ Error: 'Invalid IDRecord parameter' });
225
+ return fNext();
226
+ }
227
+
228
+ if (!this.fable.DAL || !this.fable.DAL.CertaintyIndex)
229
+ {
230
+ pResponse.send({ Error: 'CertaintyIndex DAL not initialized' });
231
+ return fNext();
232
+ }
233
+
234
+ let tmpBody = pRequest.body || {};
235
+ let tmpCertaintyValue = parseFloat(tmpBody.CertaintyValue);
236
+ if (isNaN(tmpCertaintyValue) || tmpCertaintyValue < 0 || tmpCertaintyValue > 1)
237
+ {
238
+ tmpCertaintyValue = this.options.DefaultCertaintyValue;
239
+ }
240
+
241
+ let tmpCertaintyData = {
242
+ IDRecord: tmpIDRecord,
243
+ CertaintyValue: tmpCertaintyValue,
244
+ Dimension: tmpBody.Dimension || 'overall',
245
+ Justification: tmpBody.Justification || ''
246
+ };
247
+
248
+ let tmpQuery = this.fable.DAL.CertaintyIndex.query.clone()
249
+ .addRecord(tmpCertaintyData);
250
+
251
+ this.fable.DAL.CertaintyIndex.doCreate(tmpQuery,
252
+ (pError, pQuery, pQueryRead, pRecord) =>
253
+ {
254
+ if (pError)
255
+ {
256
+ this.fable.log.error(`RecordManager error creating certainty for record ${tmpIDRecord}: ${pError}`);
257
+ pResponse.send({ Error: pError.message || pError });
258
+ return fNext();
259
+ }
260
+ pResponse.send({ Success: true, CertaintyIndex: pRecord });
261
+ return fNext();
262
+ });
263
+ });
264
+
265
+ // GET /facto/record/:IDRecord/versions -- list all versions of a record (matching GUIDRecord)
266
+ pOratorServiceServer.doGet(`${tmpRoutePrefix}/record/:IDRecord/versions`,
267
+ (pRequest, pResponse, fNext) =>
268
+ {
269
+ let tmpIDRecord = parseInt(pRequest.params.IDRecord, 10);
270
+ if (isNaN(tmpIDRecord) || tmpIDRecord < 1)
271
+ {
272
+ pResponse.send({ Error: 'Invalid IDRecord parameter', Versions: [] });
273
+ return fNext();
274
+ }
275
+
276
+ if (!this.fable.DAL || !this.fable.DAL.Record)
277
+ {
278
+ pResponse.send({ Error: 'Record DAL not initialized', Versions: [] });
279
+ return fNext();
280
+ }
281
+
282
+ // First read the record to get its GUIDRecord
283
+ let tmpReadQuery = this.fable.DAL.Record.query.clone()
284
+ .addFilter('IDRecord', tmpIDRecord);
285
+
286
+ this.fable.DAL.Record.doRead(tmpReadQuery,
287
+ (pError, pQuery, pRecord) =>
288
+ {
289
+ if (pError || !pRecord)
290
+ {
291
+ pResponse.send({ Error: pError ? (pError.message || pError) : 'Record not found', Versions: [] });
292
+ return fNext();
293
+ }
294
+
295
+ // Find all records sharing the same GUIDRecord (version chain)
296
+ let tmpVersionQuery = this.fable.DAL.Record.query.clone()
297
+ .addFilter('GUIDRecord', pRecord.GUIDRecord)
298
+ .addFilter('Deleted', 0);
299
+
300
+ this.fable.DAL.Record.doReads(tmpVersionQuery,
301
+ (pVersionError, pVersionQuery, pVersionRecords) =>
302
+ {
303
+ if (pVersionError)
304
+ {
305
+ pResponse.send({ Error: pVersionError.message || pVersionError, Versions: [] });
306
+ return fNext();
307
+ }
308
+ pResponse.send(
309
+ {
310
+ IDRecord: tmpIDRecord,
311
+ GUIDRecord: pRecord.GUIDRecord,
312
+ Count: pVersionRecords.length,
313
+ Versions: pVersionRecords
314
+ });
315
+ return fNext();
316
+ });
317
+ });
318
+ });
319
+
320
+ // GET /facto/record/:IDRecord/binary -- list binary attachments for a record
321
+ pOratorServiceServer.doGet(`${tmpRoutePrefix}/record/:IDRecord/binary`,
322
+ (pRequest, pResponse, fNext) =>
323
+ {
324
+ let tmpIDRecord = parseInt(pRequest.params.IDRecord, 10);
325
+ if (isNaN(tmpIDRecord) || tmpIDRecord < 1)
326
+ {
327
+ pResponse.send({ Error: 'Invalid IDRecord parameter', Binaries: [] });
328
+ return fNext();
329
+ }
330
+
331
+ if (!this.fable.DAL || !this.fable.DAL.RecordBinary)
332
+ {
333
+ pResponse.send({ Error: 'RecordBinary DAL not initialized', Binaries: [] });
334
+ return fNext();
335
+ }
336
+
337
+ let tmpQuery = this.fable.DAL.RecordBinary.query.clone()
338
+ .addFilter('IDRecord', tmpIDRecord)
339
+ .addFilter('Deleted', 0);
340
+
341
+ this.fable.DAL.RecordBinary.doReads(tmpQuery,
342
+ (pError, pQuery, pRecords) =>
343
+ {
344
+ if (pError)
345
+ {
346
+ pResponse.send({ Error: pError.message || pError, Binaries: [] });
347
+ return fNext();
348
+ }
349
+ pResponse.send({ IDRecord: tmpIDRecord, Count: pRecords.length, Binaries: pRecords });
350
+ return fNext();
351
+ });
352
+ });
353
+
354
+ this.fable.log.info(`RecordManager routes connected at ${tmpRoutePrefix}/record/*`);
355
+ }
356
+ }
357
+
358
+ module.exports = RetoldFactoRecordManager;
359
+ module.exports.serviceType = 'RetoldFactoRecordManager';
360
+ module.exports.default_configuration = defaultRecordManagerOptions;