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.
- package/.claude/launch.json +11 -0
- package/.dockerignore +8 -0
- package/.quackage.json +19 -0
- package/Dockerfile +26 -0
- package/bin/retold-facto.js +909 -0
- package/examples/facto-government-data.sqlite +0 -0
- package/examples/government-data-catalog.json +137 -0
- package/examples/government-data-loader.js +1432 -0
- package/package.json +91 -0
- package/scripts/facto-download.js +425 -0
- package/source/Retold-Facto.js +1042 -0
- package/source/services/Retold-Facto-BeaconProvider.js +511 -0
- package/source/services/Retold-Facto-CatalogManager.js +1252 -0
- package/source/services/Retold-Facto-DataLakeService.js +1642 -0
- package/source/services/Retold-Facto-DatasetManager.js +417 -0
- package/source/services/Retold-Facto-IngestEngine.js +1315 -0
- package/source/services/Retold-Facto-ProjectionEngine.js +3960 -0
- package/source/services/Retold-Facto-RecordManager.js +360 -0
- package/source/services/Retold-Facto-SchemaManager.js +1110 -0
- package/source/services/Retold-Facto-SourceFolderScanner.js +2243 -0
- package/source/services/Retold-Facto-SourceManager.js +730 -0
- package/source/services/Retold-Facto-StoreConnectionManager.js +441 -0
- package/source/services/Retold-Facto-ThroughputMonitor.js +478 -0
- package/source/services/web-app/codemirror-entry.js +7 -0
- package/source/services/web-app/pict-app/Pict-Application-Facto-Configuration.json +9 -0
- package/source/services/web-app/pict-app/Pict-Application-Facto.js +70 -0
- package/source/services/web-app/pict-app/Pict-Facto-Bundle.js +11 -0
- package/source/services/web-app/pict-app/providers/Pict-Provider-Facto-UI.js +66 -0
- package/source/services/web-app/pict-app/providers/Pict-Provider-Facto.js +69 -0
- package/source/services/web-app/pict-app/providers/facto-api/Facto-API-Catalog.js +93 -0
- package/source/services/web-app/pict-app/providers/facto-api/Facto-API-Connections.js +42 -0
- package/source/services/web-app/pict-app/providers/facto-api/Facto-API-Datasets.js +605 -0
- package/source/services/web-app/pict-app/providers/facto-api/Facto-API-Projections.js +188 -0
- package/source/services/web-app/pict-app/providers/facto-api/Facto-API-Scanner.js +80 -0
- package/source/services/web-app/pict-app/providers/facto-api/Facto-API-Schema.js +116 -0
- package/source/services/web-app/pict-app/providers/facto-api/Facto-API-Sources.js +104 -0
- package/source/services/web-app/pict-app/views/PictView-Facto-Catalog.js +526 -0
- package/source/services/web-app/pict-app/views/PictView-Facto-Datasets.js +173 -0
- package/source/services/web-app/pict-app/views/PictView-Facto-Ingest.js +259 -0
- package/source/services/web-app/pict-app/views/PictView-Facto-Layout.js +191 -0
- package/source/services/web-app/pict-app/views/PictView-Facto-Projections.js +231 -0
- package/source/services/web-app/pict-app/views/PictView-Facto-Records.js +326 -0
- package/source/services/web-app/pict-app/views/PictView-Facto-Scanner.js +624 -0
- package/source/services/web-app/pict-app/views/PictView-Facto-Sources.js +201 -0
- package/source/services/web-app/pict-app/views/PictView-Facto-Throughput.js +456 -0
- package/source/services/web-app/pict-app-full/Pict-Application-Facto-Full-Configuration.json +14 -0
- package/source/services/web-app/pict-app-full/Pict-Application-Facto-Full.js +391 -0
- package/source/services/web-app/pict-app-full/providers/PictRouter-Facto-Configuration.json +56 -0
- package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-BottomBar.js +68 -0
- package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-Connections.js +340 -0
- package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-Dashboard.js +149 -0
- package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-Dashboards.js +819 -0
- package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-Datasets.js +178 -0
- package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-IngestJobs.js +99 -0
- package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-Layout.js +62 -0
- package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-MappingEditor.js +158 -0
- package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-ProjectionDetail.js +1120 -0
- package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-Projections.js +172 -0
- package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-QueryPanel.js +119 -0
- package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-RecordViewer.js +663 -0
- package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-Records.js +648 -0
- package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-Scanner.js +1017 -0
- package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-SchemaDetail.js +1404 -0
- package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-SchemaDocEditor.js +1036 -0
- package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-SchemaEditor.js +636 -0
- package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-SchemaResearch.js +357 -0
- package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-SourceDetail.js +822 -0
- package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-SourceEditor.js +1036 -0
- package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-SourceResearch.js +487 -0
- package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-Sources.js +165 -0
- package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-Throughput.js +439 -0
- package/source/services/web-app/pict-app-full/views/PictView-Facto-Full-TopBar.js +335 -0
- package/source/services/web-app/pict-app-full/views/projections/Facto-Projections-Constants.js +71 -0
- package/source/services/web-app/web/chart.min.js +20 -0
- package/source/services/web-app/web/codemirror-bundle.js +30099 -0
- package/source/services/web-app/web/css/facto-themes.css +467 -0
- package/source/services/web-app/web/css/facto.css +502 -0
- package/source/services/web-app/web/index.html +28 -0
- package/source/services/web-app/web/retold-facto.js +12138 -0
- package/source/services/web-app/web/retold-facto.js.map +1 -0
- package/source/services/web-app/web/retold-facto.min.js +2 -0
- package/source/services/web-app/web/retold-facto.min.js.map +1 -0
- package/source/services/web-app/web/simple/index.html +17 -0
- package/test/Facto_Browser_Integration_tests.js +798 -0
- package/test/RetoldFacto_tests.js +4117 -0
- package/test/fixtures/weather-readings.csv +17 -0
- package/test/fixtures/weather-stations.csv +9 -0
- package/test/model/MeadowModel-Extended.json +8497 -0
- package/test/model/MeadowModel-PICT.json +1 -0
- package/test/model/MeadowModel.json +1355 -0
- package/test/model/ddl/Facto.ddl +225 -0
- package/test/model/fable-configuration.json +14 -0
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
const libPictView = require('pict-view');
|
|
2
|
+
|
|
3
|
+
class FactoProjectionsView extends libPictView
|
|
4
|
+
{
|
|
5
|
+
constructor(pFable, pOptions, pServiceHash)
|
|
6
|
+
{
|
|
7
|
+
super(pFable, pOptions, pServiceHash);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
onAfterRender()
|
|
11
|
+
{
|
|
12
|
+
this.loadSummary();
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
loadSummary()
|
|
16
|
+
{
|
|
17
|
+
this.pict.providers.Facto.loadProjectionSummary().then(
|
|
18
|
+
(pResponse) =>
|
|
19
|
+
{
|
|
20
|
+
let tmpContainer = document.getElementById('facto-projections-summary');
|
|
21
|
+
if (!tmpContainer || !pResponse) return;
|
|
22
|
+
|
|
23
|
+
let tmpHtml = '<table><tbody>';
|
|
24
|
+
tmpHtml += '<tr><td style="font-weight:600;">Sources</td><td>' + (pResponse.Sources || 0) + '</td>';
|
|
25
|
+
tmpHtml += '<td style="font-weight:600;">Datasets</td><td>' + (pResponse.Datasets || 0) + '</td></tr>';
|
|
26
|
+
tmpHtml += '<tr><td style="font-weight:600;">Records</td><td>' + (pResponse.Records || 0) + '</td>';
|
|
27
|
+
tmpHtml += '<td style="font-weight:600;">Certainty Indices</td><td>' + (pResponse.CertaintyIndices || 0) + '</td></tr>';
|
|
28
|
+
tmpHtml += '<tr><td style="font-weight:600;">Ingest Jobs</td><td>' + (pResponse.IngestJobs || 0) + '</td>';
|
|
29
|
+
tmpHtml += '<td></td><td></td></tr>';
|
|
30
|
+
|
|
31
|
+
if (pResponse.DatasetsByType)
|
|
32
|
+
{
|
|
33
|
+
tmpHtml += '<tr><td colspan="4" style="padding-top:12px; font-weight:600; border-bottom:2px solid #ddd;">Datasets by Type</td></tr>';
|
|
34
|
+
tmpHtml += '<tr>';
|
|
35
|
+
tmpHtml += '<td><span class="badge badge-raw">Raw</span></td><td>' + (pResponse.DatasetsByType.Raw || 0) + '</td>';
|
|
36
|
+
tmpHtml += '<td><span class="badge badge-compositional">Compositional</span></td><td>' + (pResponse.DatasetsByType.Compositional || 0) + '</td>';
|
|
37
|
+
tmpHtml += '</tr><tr>';
|
|
38
|
+
tmpHtml += '<td><span class="badge badge-projection">Projection</span></td><td>' + (pResponse.DatasetsByType.Projection || 0) + '</td>';
|
|
39
|
+
tmpHtml += '<td><span class="badge badge-derived">Derived</span></td><td>' + (pResponse.DatasetsByType.Derived || 0) + '</td>';
|
|
40
|
+
tmpHtml += '</tr>';
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
tmpHtml += '</tbody></table>';
|
|
44
|
+
tmpContainer.innerHTML = tmpHtml;
|
|
45
|
+
}).catch(
|
|
46
|
+
(pError) =>
|
|
47
|
+
{
|
|
48
|
+
this.pict.views['Pict-Section-Modal'].toast('Error loading summary: ' + pError.message, {type: 'error'});
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
runQuery()
|
|
53
|
+
{
|
|
54
|
+
let tmpDatasetIDsRaw = this.pict.providers.FactoUI.getVal('facto-proj-dataset-ids');
|
|
55
|
+
let tmpType = this.pict.providers.FactoUI.getVal('facto-proj-type');
|
|
56
|
+
let tmpCertaintyThreshold = parseFloat(this.pict.providers.FactoUI.getVal('facto-proj-certainty')) || 0;
|
|
57
|
+
let tmpTimeStart = this.pict.providers.FactoUI.getVal('facto-proj-time-start');
|
|
58
|
+
let tmpTimeStop = this.pict.providers.FactoUI.getVal('facto-proj-time-stop');
|
|
59
|
+
|
|
60
|
+
let tmpDatasetIDs = tmpDatasetIDsRaw.split(',').map(function(s) { return parseInt(s.trim(), 10); }).filter(function(n) { return !isNaN(n) && n > 0; });
|
|
61
|
+
if (tmpDatasetIDs.length === 0)
|
|
62
|
+
{
|
|
63
|
+
this.pict.views['Pict-Section-Modal'].toast('Enter at least one Dataset ID', {type: 'warning'});
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
let tmpParams = { DatasetIDs: tmpDatasetIDs, Begin: 0, Cap: 100 };
|
|
68
|
+
if (tmpType) tmpParams.Type = tmpType;
|
|
69
|
+
if (tmpCertaintyThreshold > 0) tmpParams.CertaintyThreshold = tmpCertaintyThreshold;
|
|
70
|
+
if (tmpTimeStart) tmpParams.TimeRangeStart = parseInt(tmpTimeStart, 10) || 0;
|
|
71
|
+
if (tmpTimeStop) tmpParams.TimeRangeStop = parseInt(tmpTimeStop, 10) || 0;
|
|
72
|
+
|
|
73
|
+
this.pict.views['Pict-Section-Modal'].toast('Querying...', {type: 'info'});
|
|
74
|
+
|
|
75
|
+
this.pict.providers.Facto.queryRecords(tmpParams).then(
|
|
76
|
+
(pResponse) =>
|
|
77
|
+
{
|
|
78
|
+
this.refreshResults(pResponse);
|
|
79
|
+
}).catch(
|
|
80
|
+
(pError) =>
|
|
81
|
+
{
|
|
82
|
+
this.pict.views['Pict-Section-Modal'].toast('Query error: ' + pError.message, {type: 'error'});
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
runCompare()
|
|
87
|
+
{
|
|
88
|
+
let tmpDatasetIDsRaw = this.pict.providers.FactoUI.getVal('facto-proj-dataset-ids');
|
|
89
|
+
let tmpDatasetIDs = tmpDatasetIDsRaw.split(',').map(function(s) { return parseInt(s.trim(), 10); }).filter(function(n) { return !isNaN(n) && n > 0; });
|
|
90
|
+
|
|
91
|
+
if (tmpDatasetIDs.length === 0)
|
|
92
|
+
{
|
|
93
|
+
this.pict.views['Pict-Section-Modal'].toast('Enter at least one Dataset ID', {type: 'warning'});
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
this.pict.views['Pict-Section-Modal'].toast('Comparing...', {type: 'info'});
|
|
98
|
+
|
|
99
|
+
this.pict.providers.Facto.compareDatasets(tmpDatasetIDs).then(
|
|
100
|
+
(pResponse) =>
|
|
101
|
+
{
|
|
102
|
+
let tmpContainer = document.getElementById('facto-projections-results');
|
|
103
|
+
if (!tmpContainer || !pResponse || !pResponse.Datasets) return;
|
|
104
|
+
|
|
105
|
+
let tmpHtml = '<h4 style="margin:12px 0 8px; font-size:0.95em;">Dataset Comparison</h4>';
|
|
106
|
+
tmpHtml += '<table><thead><tr><th>ID</th><th>Name</th><th>Type</th><th>Records</th><th>Sources</th></tr></thead><tbody>';
|
|
107
|
+
for (let i = 0; i < pResponse.Datasets.length; i++)
|
|
108
|
+
{
|
|
109
|
+
let tmpDS = pResponse.Datasets[i];
|
|
110
|
+
let tmpTypeLower = (tmpDS.Type || '').toLowerCase();
|
|
111
|
+
tmpHtml += '<tr>';
|
|
112
|
+
tmpHtml += '<td>' + (tmpDS.IDDataset || '') + '</td>';
|
|
113
|
+
tmpHtml += '<td>' + (tmpDS.Name || '') + '</td>';
|
|
114
|
+
tmpHtml += '<td><span class="badge badge-' + tmpTypeLower + '">' + (tmpDS.Type || '') + '</span></td>';
|
|
115
|
+
tmpHtml += '<td>' + (tmpDS.RecordCount || 0) + '</td>';
|
|
116
|
+
tmpHtml += '<td>' + (tmpDS.SourceCount || 0) + '</td>';
|
|
117
|
+
tmpHtml += '</tr>';
|
|
118
|
+
}
|
|
119
|
+
tmpHtml += '</tbody></table>';
|
|
120
|
+
tmpContainer.innerHTML = tmpHtml;
|
|
121
|
+
}).catch(
|
|
122
|
+
(pError) =>
|
|
123
|
+
{
|
|
124
|
+
this.pict.views['Pict-Section-Modal'].toast('Compare error: ' + pError.message, {type: 'error'});
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
refreshResults(pResponse)
|
|
129
|
+
{
|
|
130
|
+
let tmpContainer = document.getElementById('facto-projections-results');
|
|
131
|
+
if (!tmpContainer) return;
|
|
132
|
+
|
|
133
|
+
if (!pResponse || !pResponse.Records || pResponse.Records.length === 0)
|
|
134
|
+
{
|
|
135
|
+
tmpContainer.innerHTML = '<p style="color:#888; font-style:italic;">No records match the query.</p>';
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
let tmpHtml = '<h4 style="margin:12px 0 8px; font-size:0.95em;">Query Results (' + (pResponse.Count || pResponse.Records.length) + ' records)</h4>';
|
|
140
|
+
tmpHtml += '<table><thead><tr><th>ID</th><th>Dataset</th><th>Source</th><th>Type</th><th>Version</th><th>Ingest Date</th><th>Content</th></tr></thead><tbody>';
|
|
141
|
+
for (let i = 0; i < pResponse.Records.length; i++)
|
|
142
|
+
{
|
|
143
|
+
let tmpRecord = pResponse.Records[i];
|
|
144
|
+
let tmpContent = tmpRecord.Content || '';
|
|
145
|
+
if (tmpContent.length > 80) tmpContent = tmpContent.substring(0, 80) + '...';
|
|
146
|
+
tmpHtml += '<tr>';
|
|
147
|
+
tmpHtml += '<td>' + (tmpRecord.IDRecord || '') + '</td>';
|
|
148
|
+
tmpHtml += '<td>' + (tmpRecord.IDDataset || '') + '</td>';
|
|
149
|
+
tmpHtml += '<td>' + (tmpRecord.IDSource || '') + '</td>';
|
|
150
|
+
tmpHtml += '<td>' + (tmpRecord.Type || '') + '</td>';
|
|
151
|
+
tmpHtml += '<td>' + (tmpRecord.Version || 1) + '</td>';
|
|
152
|
+
tmpHtml += '<td>' + (tmpRecord.IngestDate || '-') + '</td>';
|
|
153
|
+
tmpHtml += '<td style="max-width:200px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap;">' + tmpContent + '</td>';
|
|
154
|
+
tmpHtml += '</tr>';
|
|
155
|
+
}
|
|
156
|
+
tmpHtml += '</tbody></table>';
|
|
157
|
+
tmpContainer.innerHTML = tmpHtml;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
module.exports = FactoProjectionsView;
|
|
162
|
+
|
|
163
|
+
module.exports.default_configuration =
|
|
164
|
+
{
|
|
165
|
+
ViewIdentifier: 'Facto-Projections',
|
|
166
|
+
DefaultRenderable: 'Facto-Projections',
|
|
167
|
+
DefaultDestinationAddress: '#Facto-Section-Projections',
|
|
168
|
+
Templates:
|
|
169
|
+
[
|
|
170
|
+
{
|
|
171
|
+
Hash: 'Facto-Projections',
|
|
172
|
+
Template: /*html*/`
|
|
173
|
+
<div class="accordion-row">
|
|
174
|
+
<div class="accordion-number">5</div>
|
|
175
|
+
<div class="accordion-card" id="facto-card-projections">
|
|
176
|
+
<div class="accordion-header" onclick="pict.views['Facto-Layout'].toggleSection('facto-card-projections')">
|
|
177
|
+
<span class="accordion-title">Projections</span>
|
|
178
|
+
<span class="accordion-preview">Cross-dataset queries and warehouse statistics</span>
|
|
179
|
+
<span class="accordion-toggle">▼</span>
|
|
180
|
+
</div>
|
|
181
|
+
<div class="accordion-body">
|
|
182
|
+
<p style="margin-bottom:12px; color:#666; font-size:0.9em;">Query across datasets, compare data, and view warehouse-wide statistics.</p>
|
|
183
|
+
|
|
184
|
+
<h3 style="margin-bottom:8px; font-size:1em; color:#444;">Warehouse Summary</h3>
|
|
185
|
+
<div id="facto-projections-summary" style="margin-bottom:16px;"><p style="color:#888; font-style:italic;">Loading...</p></div>
|
|
186
|
+
|
|
187
|
+
<h3 style="margin-top:16px; margin-bottom:8px; font-size:1em; color:#444;">Cross-Dataset Query</h3>
|
|
188
|
+
<div class="inline-group">
|
|
189
|
+
<div>
|
|
190
|
+
<label for="facto-proj-dataset-ids">Dataset IDs (comma-separated)</label>
|
|
191
|
+
<input type="text" id="facto-proj-dataset-ids" placeholder="1,2,3">
|
|
192
|
+
</div>
|
|
193
|
+
<div>
|
|
194
|
+
<label for="facto-proj-type">Record Type (optional)</label>
|
|
195
|
+
<input type="text" id="facto-proj-type" placeholder="e.g. enrollment">
|
|
196
|
+
</div>
|
|
197
|
+
</div>
|
|
198
|
+
<div class="inline-group">
|
|
199
|
+
<div>
|
|
200
|
+
<label for="facto-proj-certainty">Min Certainty (0-1)</label>
|
|
201
|
+
<input type="text" id="facto-proj-certainty" placeholder="0.0">
|
|
202
|
+
</div>
|
|
203
|
+
<div>
|
|
204
|
+
<label for="facto-proj-time-start">Time Range Start</label>
|
|
205
|
+
<input type="text" id="facto-proj-time-start" placeholder="timestamp">
|
|
206
|
+
</div>
|
|
207
|
+
<div>
|
|
208
|
+
<label for="facto-proj-time-stop">Time Range Stop</label>
|
|
209
|
+
<input type="text" id="facto-proj-time-stop" placeholder="timestamp">
|
|
210
|
+
</div>
|
|
211
|
+
</div>
|
|
212
|
+
<button class="primary" onclick="pict.views['Facto-Projections'].runQuery()">Query</button>
|
|
213
|
+
<button class="secondary" onclick="pict.views['Facto-Projections'].runCompare()">Compare Datasets</button>
|
|
214
|
+
<button class="secondary" onclick="pict.views['Facto-Projections'].loadSummary()">Refresh Summary</button>
|
|
215
|
+
|
|
216
|
+
<div id="facto-projections-results" style="margin-top:16px;"></div>
|
|
217
|
+
</div>
|
|
218
|
+
</div>
|
|
219
|
+
</div>
|
|
220
|
+
`
|
|
221
|
+
}
|
|
222
|
+
],
|
|
223
|
+
Renderables:
|
|
224
|
+
[
|
|
225
|
+
{
|
|
226
|
+
RenderableHash: 'Facto-Projections',
|
|
227
|
+
TemplateHash: 'Facto-Projections',
|
|
228
|
+
DestinationAddress: '#Facto-Section-Projections'
|
|
229
|
+
}
|
|
230
|
+
]
|
|
231
|
+
};
|
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
const libPictView = require('pict-view');
|
|
2
|
+
|
|
3
|
+
class FactoRecordsView extends libPictView
|
|
4
|
+
{
|
|
5
|
+
constructor(pFable, pOptions, pServiceHash)
|
|
6
|
+
{
|
|
7
|
+
super(pFable, pOptions, pServiceHash);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
onAfterRender()
|
|
11
|
+
{
|
|
12
|
+
this.pict.providers.Facto.loadRecords(0).then(
|
|
13
|
+
() =>
|
|
14
|
+
{
|
|
15
|
+
this.refreshList();
|
|
16
|
+
}).catch(
|
|
17
|
+
(pError) =>
|
|
18
|
+
{
|
|
19
|
+
this.pict.views['Pict-Section-Modal'].toast('Error loading records: ' + pError.message, {type: 'error'});
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
refreshList()
|
|
24
|
+
{
|
|
25
|
+
let tmpContainer = document.getElementById('facto-records-list');
|
|
26
|
+
if (!tmpContainer) return;
|
|
27
|
+
|
|
28
|
+
let tmpRecords = this.pict.AppData.Facto.Records;
|
|
29
|
+
if (!tmpRecords || tmpRecords.length === 0)
|
|
30
|
+
{
|
|
31
|
+
tmpContainer.innerHTML = '<p style="color:#888; font-style:italic;">No records ingested yet.</p>';
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
let tmpHtml = '<table><thead><tr><th>ID</th><th>Type</th><th>Dataset</th><th>Source</th><th>Version</th><th>Ingest Date</th><th>Content Preview</th><th>Actions</th></tr></thead><tbody>';
|
|
36
|
+
for (let i = 0; i < tmpRecords.length; i++)
|
|
37
|
+
{
|
|
38
|
+
let tmpRecord = tmpRecords[i];
|
|
39
|
+
let tmpPreview = (tmpRecord.Content || '').substring(0, 60);
|
|
40
|
+
if ((tmpRecord.Content || '').length > 60) tmpPreview += '...';
|
|
41
|
+
tmpHtml += '<tr>';
|
|
42
|
+
tmpHtml += '<td>' + (tmpRecord.IDRecord || '') + '</td>';
|
|
43
|
+
tmpHtml += '<td>' + (tmpRecord.Type || '') + '</td>';
|
|
44
|
+
tmpHtml += '<td>' + (tmpRecord.IDDataset || '') + '</td>';
|
|
45
|
+
tmpHtml += '<td>' + (tmpRecord.IDSource || '') + '</td>';
|
|
46
|
+
tmpHtml += '<td>' + (tmpRecord.Version || '1') + '</td>';
|
|
47
|
+
tmpHtml += '<td>' + (tmpRecord.IngestDate || '') + '</td>';
|
|
48
|
+
tmpHtml += '<td style="max-width:250px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; font-family:monospace; font-size:0.85em;">' + tmpPreview + '</td>';
|
|
49
|
+
tmpHtml += '<td><button class="secondary" style="padding:4px 8px; font-size:0.8em;" onclick="pict.views[\'Facto-Records\'].viewCertainty(' + tmpRecord.IDRecord + ')">Certainty</button></td>';
|
|
50
|
+
tmpHtml += '</tr>';
|
|
51
|
+
}
|
|
52
|
+
tmpHtml += '</tbody></table>';
|
|
53
|
+
|
|
54
|
+
// Pagination controls
|
|
55
|
+
let tmpPage = this.pict.AppData.Facto.RecordPage || 0;
|
|
56
|
+
tmpHtml += '<div style="margin-top:12px; display:flex; gap:8px; align-items:center;">';
|
|
57
|
+
if (tmpPage > 0)
|
|
58
|
+
{
|
|
59
|
+
tmpHtml += '<button class="secondary" style="padding:4px 12px; font-size:0.85em;" onclick="pict.views[\'Facto-Records\'].changePage(' + (tmpPage - 1) + ')">◀ Previous</button>';
|
|
60
|
+
}
|
|
61
|
+
tmpHtml += '<span style="color:#888; font-size:0.85em;">Page ' + (tmpPage + 1) + '</span>';
|
|
62
|
+
if (tmpRecords.length >= this.pict.AppData.Facto.RecordPageSize)
|
|
63
|
+
{
|
|
64
|
+
tmpHtml += '<button class="secondary" style="padding:4px 12px; font-size:0.85em;" onclick="pict.views[\'Facto-Records\'].changePage(' + (tmpPage + 1) + ')">Next ▶</button>';
|
|
65
|
+
}
|
|
66
|
+
tmpHtml += '</div>';
|
|
67
|
+
|
|
68
|
+
tmpContainer.innerHTML = tmpHtml;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
changePage(pPage)
|
|
72
|
+
{
|
|
73
|
+
this.pict.AppData.Facto.RecordPage = pPage;
|
|
74
|
+
this.pict.providers.Facto.loadRecords(pPage).then(
|
|
75
|
+
() =>
|
|
76
|
+
{
|
|
77
|
+
this.refreshList();
|
|
78
|
+
}).catch(
|
|
79
|
+
(pError) =>
|
|
80
|
+
{
|
|
81
|
+
this.pict.views['Pict-Section-Modal'].toast('Error: ' + pError.message, {type: 'error'});
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
viewCertainty(pIDRecord)
|
|
86
|
+
{
|
|
87
|
+
this.pict.providers.Facto.loadRecordCertainty(pIDRecord).then(
|
|
88
|
+
(pResponse) =>
|
|
89
|
+
{
|
|
90
|
+
let tmpPanel = document.getElementById('facto-certainty-panel');
|
|
91
|
+
|
|
92
|
+
if (!pResponse || !pResponse.CertaintyIndices || pResponse.CertaintyIndices.length === 0)
|
|
93
|
+
{
|
|
94
|
+
if (tmpPanel) tmpPanel.innerHTML = this._renderCertaintyEmpty(pIDRecord);
|
|
95
|
+
else this.pict.views['Pict-Section-Modal'].toast('No certainty data for record #' + pIDRecord, {type: 'info'});
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
let tmpHtml = this._renderCertaintyPanel(pIDRecord, pResponse.CertaintyIndices);
|
|
100
|
+
if (tmpPanel)
|
|
101
|
+
{
|
|
102
|
+
tmpPanel.innerHTML = tmpHtml;
|
|
103
|
+
tmpPanel.style.display = 'block';
|
|
104
|
+
}
|
|
105
|
+
else
|
|
106
|
+
{
|
|
107
|
+
// Insert panel after the records list
|
|
108
|
+
let tmpList = document.getElementById('facto-records-list');
|
|
109
|
+
if (tmpList)
|
|
110
|
+
{
|
|
111
|
+
let tmpDiv = document.createElement('div');
|
|
112
|
+
tmpDiv.id = 'facto-certainty-panel';
|
|
113
|
+
tmpDiv.innerHTML = tmpHtml;
|
|
114
|
+
tmpList.parentNode.insertBefore(tmpDiv, tmpList.nextSibling);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}).catch(
|
|
118
|
+
(pError) =>
|
|
119
|
+
{
|
|
120
|
+
this.pict.views['Pict-Section-Modal'].toast('Error: ' + pError.message, {type: 'error'});
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
closeCertaintyPanel()
|
|
125
|
+
{
|
|
126
|
+
let tmpPanel = document.getElementById('facto-certainty-panel');
|
|
127
|
+
if (tmpPanel)
|
|
128
|
+
{
|
|
129
|
+
tmpPanel.style.display = 'none';
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
_renderCertaintyEmpty(pIDRecord)
|
|
134
|
+
{
|
|
135
|
+
return /*html*/`
|
|
136
|
+
<div style="border:1px solid var(--facto-border); border-radius:6px; padding:16px; margin-top:16px; background:var(--facto-bg-surface);">
|
|
137
|
+
<div style="display:flex; justify-content:space-between; align-items:center; margin-bottom:8px;">
|
|
138
|
+
<h4 style="margin:0; color:var(--facto-text-heading);">Certainty — Record #${pIDRecord}</h4>
|
|
139
|
+
<button class="secondary" style="padding:2px 10px; font-size:0.8em;" onclick="pict.views['Facto-Records'].closeCertaintyPanel()">Close</button>
|
|
140
|
+
</div>
|
|
141
|
+
<p style="color:var(--facto-text-secondary); font-style:italic; margin:0;">No certainty indices recorded for this record. Run a multi-set projection merge to generate field-level certainty scores.</p>
|
|
142
|
+
</div>`;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
_renderCertaintyPanel(pIDRecord, pIndices)
|
|
146
|
+
{
|
|
147
|
+
// Group indices by field name
|
|
148
|
+
let tmpFields = {};
|
|
149
|
+
for (let i = 0; i < pIndices.length; i++)
|
|
150
|
+
{
|
|
151
|
+
let tmpCI = pIndices[i];
|
|
152
|
+
let tmpDimension = tmpCI.Dimension || '';
|
|
153
|
+
|
|
154
|
+
// Parse dimension format: "field:{FieldName}:{dimension}"
|
|
155
|
+
let tmpMatch = tmpDimension.match(/^field:(.+):(presence|ratio|composite)$/);
|
|
156
|
+
if (tmpMatch)
|
|
157
|
+
{
|
|
158
|
+
let tmpFieldName = tmpMatch[1];
|
|
159
|
+
let tmpDimType = tmpMatch[2];
|
|
160
|
+
if (!tmpFields[tmpFieldName])
|
|
161
|
+
{
|
|
162
|
+
tmpFields[tmpFieldName] = { presence: 0, ratio: 0, composite: 0, justification: null };
|
|
163
|
+
}
|
|
164
|
+
tmpFields[tmpFieldName][tmpDimType] = tmpCI.CertaintyValue;
|
|
165
|
+
if (tmpCI.Justification && !tmpFields[tmpFieldName].justification)
|
|
166
|
+
{
|
|
167
|
+
try { tmpFields[tmpFieldName].justification = JSON.parse(tmpCI.Justification); }
|
|
168
|
+
catch (e) { /* ignore parse errors */ }
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
let tmpFieldNames = Object.keys(tmpFields);
|
|
174
|
+
if (tmpFieldNames.length === 0)
|
|
175
|
+
{
|
|
176
|
+
// Fall back to raw display for non-field-level indices
|
|
177
|
+
return this._renderCertaintyRaw(pIDRecord, pIndices);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Compute record-level composite
|
|
181
|
+
let tmpCompositeSum = 0;
|
|
182
|
+
for (let i = 0; i < tmpFieldNames.length; i++)
|
|
183
|
+
{
|
|
184
|
+
tmpCompositeSum += tmpFields[tmpFieldNames[i]].composite;
|
|
185
|
+
}
|
|
186
|
+
let tmpRecordComposite = tmpFieldNames.length > 0 ? tmpCompositeSum / tmpFieldNames.length : 0.5;
|
|
187
|
+
|
|
188
|
+
// Build the panel HTML
|
|
189
|
+
let tmpHtml = /*html*/`
|
|
190
|
+
<div style="border:1px solid var(--facto-border); border-radius:6px; padding:16px; margin-top:16px; background:var(--facto-bg-surface);">
|
|
191
|
+
<div style="display:flex; justify-content:space-between; align-items:center; margin-bottom:12px;">
|
|
192
|
+
<h4 style="margin:0; color:var(--facto-text-heading);">Certainty — Record #${pIDRecord}</h4>
|
|
193
|
+
<button class="secondary" style="padding:2px 10px; font-size:0.8em;" onclick="pict.views['Facto-Records'].closeCertaintyPanel()">Close</button>
|
|
194
|
+
</div>
|
|
195
|
+
<div style="display:flex; align-items:center; gap:12px; margin-bottom:16px; padding:10px; background:var(--facto-bg-elevated); border-radius:4px;">
|
|
196
|
+
<span style="font-weight:600; color:var(--facto-text-heading);">Record Composite</span>
|
|
197
|
+
${this._renderBar(tmpRecordComposite, 200)}
|
|
198
|
+
<span style="font-weight:600; font-size:1.1em; color:${this._certaintyColor(tmpRecordComposite)};">${(tmpRecordComposite * 100).toFixed(1)}%</span>
|
|
199
|
+
</div>
|
|
200
|
+
<table style="width:100%; border-collapse:collapse;">
|
|
201
|
+
<thead>
|
|
202
|
+
<tr style="border-bottom:2px solid var(--facto-border);">
|
|
203
|
+
<th style="text-align:left; padding:6px 8px; color:var(--facto-text-heading);">Field</th>
|
|
204
|
+
<th style="text-align:left; padding:6px 8px; color:var(--facto-text-heading);">Presence</th>
|
|
205
|
+
<th style="text-align:left; padding:6px 8px; color:var(--facto-text-heading);">Ratio</th>
|
|
206
|
+
<th style="text-align:left; padding:6px 8px; color:var(--facto-text-heading);">Composite</th>
|
|
207
|
+
<th style="text-align:center; padding:6px 8px; color:var(--facto-text-heading);">Sources</th>
|
|
208
|
+
</tr>
|
|
209
|
+
</thead>
|
|
210
|
+
<tbody>`;
|
|
211
|
+
|
|
212
|
+
for (let i = 0; i < tmpFieldNames.length; i++)
|
|
213
|
+
{
|
|
214
|
+
let tmpFN = tmpFieldNames[i];
|
|
215
|
+
let tmpF = tmpFields[tmpFN];
|
|
216
|
+
let tmpJ = tmpF.justification || {};
|
|
217
|
+
let tmpSources = (tmpJ.agreeing || 0) + '✓';
|
|
218
|
+
if (tmpJ.conflicting > 0)
|
|
219
|
+
{
|
|
220
|
+
tmpSources += ' ' + tmpJ.conflicting + '✗';
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
tmpHtml += /*html*/`
|
|
224
|
+
<tr style="border-bottom:1px solid var(--facto-border-subtle);">
|
|
225
|
+
<td style="padding:6px 8px; font-family:monospace; font-size:0.9em; color:var(--facto-text);">${tmpFN}</td>
|
|
226
|
+
<td style="padding:6px 8px;">${this._renderBar(tmpF.presence, 100)} <span style="font-size:0.85em; color:var(--facto-text-secondary);">${(tmpF.presence * 100).toFixed(0)}%</span></td>
|
|
227
|
+
<td style="padding:6px 8px;">${this._renderBar(tmpF.ratio, 100)} <span style="font-size:0.85em; color:var(--facto-text-secondary);">${(tmpF.ratio * 100).toFixed(0)}%</span></td>
|
|
228
|
+
<td style="padding:6px 8px;">${this._renderBar(tmpF.composite, 100)} <span style="font-weight:600; font-size:0.85em; color:${this._certaintyColor(tmpF.composite)};">${(tmpF.composite * 100).toFixed(0)}%</span></td>
|
|
229
|
+
<td style="padding:6px 8px; text-align:center; font-size:0.85em; color:var(--facto-text-secondary);">${tmpSources}</td>
|
|
230
|
+
</tr>`;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
tmpHtml += /*html*/`
|
|
234
|
+
</tbody>
|
|
235
|
+
</table>
|
|
236
|
+
<div style="margin-top:10px; font-size:0.8em; color:var(--facto-text-tertiary);">
|
|
237
|
+
<strong>Presence</strong> = cross-dataset corroboration |
|
|
238
|
+
<strong>Ratio</strong> = within-dataset frequency |
|
|
239
|
+
<strong>Composite</strong> = weighted (${((tmpFields[tmpFieldNames[0]] && tmpFields[tmpFieldNames[0]].justification && tmpFields[tmpFieldNames[0]].justification.weights) ? (tmpFields[tmpFieldNames[0]].justification.weights.presence * 100).toFixed(0) + '/' + (tmpFields[tmpFieldNames[0]].justification.weights.ratio * 100).toFixed(0) : '70/30')}) |
|
|
240
|
+
✓ = agreeing sources ✗ = conflicting sources
|
|
241
|
+
</div>
|
|
242
|
+
</div>`;
|
|
243
|
+
|
|
244
|
+
return tmpHtml;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
_renderCertaintyRaw(pIDRecord, pIndices)
|
|
248
|
+
{
|
|
249
|
+
let tmpHtml = /*html*/`
|
|
250
|
+
<div style="border:1px solid var(--facto-border); border-radius:6px; padding:16px; margin-top:16px; background:var(--facto-bg-surface);">
|
|
251
|
+
<div style="display:flex; justify-content:space-between; align-items:center; margin-bottom:12px;">
|
|
252
|
+
<h4 style="margin:0; color:var(--facto-text-heading);">Certainty — Record #${pIDRecord}</h4>
|
|
253
|
+
<button class="secondary" style="padding:2px 10px; font-size:0.8em;" onclick="pict.views['Facto-Records'].closeCertaintyPanel()">Close</button>
|
|
254
|
+
</div>`;
|
|
255
|
+
|
|
256
|
+
for (let i = 0; i < pIndices.length; i++)
|
|
257
|
+
{
|
|
258
|
+
let tmpCI = pIndices[i];
|
|
259
|
+
tmpHtml += /*html*/`
|
|
260
|
+
<div style="display:flex; align-items:center; gap:8px; margin-bottom:6px;">
|
|
261
|
+
<span style="font-family:monospace; font-size:0.85em; min-width:180px; color:var(--facto-text);">${tmpCI.Dimension}</span>
|
|
262
|
+
${this._renderBar(tmpCI.CertaintyValue, 150)}
|
|
263
|
+
<span style="font-size:0.85em; font-weight:600; color:${this._certaintyColor(tmpCI.CertaintyValue)};">${(tmpCI.CertaintyValue * 100).toFixed(1)}%</span>
|
|
264
|
+
</div>`;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
tmpHtml += '</div>';
|
|
268
|
+
return tmpHtml;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
_renderBar(pValue, pWidth)
|
|
272
|
+
{
|
|
273
|
+
let tmpPct = Math.max(0, Math.min(1, pValue)) * 100;
|
|
274
|
+
let tmpColor = this._certaintyColor(pValue);
|
|
275
|
+
let tmpBgColor = 'var(--facto-bg-elevated)';
|
|
276
|
+
return '<div style="display:inline-block; width:' + pWidth + 'px; height:14px; background:' + tmpBgColor + '; border-radius:3px; overflow:hidden; vertical-align:middle;">'
|
|
277
|
+
+ '<div style="width:' + tmpPct + '%; height:100%; background:' + tmpColor + '; border-radius:3px; transition:width 0.3s;"></div>'
|
|
278
|
+
+ '</div>';
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
_certaintyColor(pValue)
|
|
282
|
+
{
|
|
283
|
+
if (pValue >= 0.7) return 'var(--facto-success)';
|
|
284
|
+
if (pValue >= 0.4) return 'var(--facto-warning)';
|
|
285
|
+
return 'var(--facto-error)';
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
module.exports = FactoRecordsView;
|
|
290
|
+
|
|
291
|
+
module.exports.default_configuration =
|
|
292
|
+
{
|
|
293
|
+
ViewIdentifier: 'Facto-Records',
|
|
294
|
+
DefaultRenderable: 'Facto-Records',
|
|
295
|
+
DefaultDestinationAddress: '#Facto-Section-Records',
|
|
296
|
+
Templates:
|
|
297
|
+
[
|
|
298
|
+
{
|
|
299
|
+
Hash: 'Facto-Records',
|
|
300
|
+
Template: /*html*/`
|
|
301
|
+
<div class="accordion-row">
|
|
302
|
+
<div class="accordion-number">3</div>
|
|
303
|
+
<div class="accordion-card" id="facto-card-records">
|
|
304
|
+
<div class="accordion-header" onclick="pict.views['Facto-Layout'].toggleSection('facto-card-records')">
|
|
305
|
+
<span class="accordion-title">Records</span>
|
|
306
|
+
<span class="accordion-preview">Browse ingested records</span>
|
|
307
|
+
<span class="accordion-toggle">▼</span>
|
|
308
|
+
</div>
|
|
309
|
+
<div class="accordion-body">
|
|
310
|
+
<p style="margin-bottom:12px; color:#666; font-size:0.9em;">Individual data records with versioning, certainty indices, and temporal metadata.</p>
|
|
311
|
+
<div id="facto-records-list"></div>
|
|
312
|
+
</div>
|
|
313
|
+
</div>
|
|
314
|
+
</div>
|
|
315
|
+
`
|
|
316
|
+
}
|
|
317
|
+
],
|
|
318
|
+
Renderables:
|
|
319
|
+
[
|
|
320
|
+
{
|
|
321
|
+
RenderableHash: 'Facto-Records',
|
|
322
|
+
TemplateHash: 'Facto-Records',
|
|
323
|
+
DestinationAddress: '#Facto-Section-Records'
|
|
324
|
+
}
|
|
325
|
+
]
|
|
326
|
+
};
|