@geotechcli/core 0.4.37 → 0.4.40
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/ingest/geotech-document.d.ts +16 -0
- package/dist/ingest/geotech-document.d.ts.map +1 -1
- package/dist/ingest/geotech-document.js +227 -16
- package/dist/ingest/geotech-document.js.map +1 -1
- package/dist/ingest/index.d.ts +2 -1
- package/dist/ingest/index.d.ts.map +1 -1
- package/dist/ingest/index.js +1 -0
- package/dist/ingest/index.js.map +1 -1
- package/dist/ingest/job-store.d.ts +2 -1
- package/dist/ingest/job-store.d.ts.map +1 -1
- package/dist/ingest/job-store.js +41 -0
- package/dist/ingest/job-store.js.map +1 -1
- package/dist/ingest/job-worker.d.ts.map +1 -1
- package/dist/ingest/job-worker.js +191 -12
- package/dist/ingest/job-worker.js.map +1 -1
- package/dist/ingest/page-evidence-cache.d.ts +47 -0
- package/dist/ingest/page-evidence-cache.d.ts.map +1 -0
- package/dist/ingest/page-evidence-cache.js +205 -0
- package/dist/ingest/page-evidence-cache.js.map +1 -0
- package/dist/meta/metadata.json +1 -1
- package/dist/report/html.d.ts.map +1 -1
- package/dist/report/html.js +484 -5
- package/dist/report/html.js.map +1 -1
- package/dist/report/ingest-dossier.d.ts +2 -0
- package/dist/report/ingest-dossier.d.ts.map +1 -1
- package/dist/report/ingest-dossier.js +32 -2
- package/dist/report/ingest-dossier.js.map +1 -1
- package/package.json +1 -1
package/dist/report/html.js
CHANGED
|
@@ -68,6 +68,15 @@ function sourceModeLabel(sourceHint) {
|
|
|
68
68
|
return 'Extraction evidence retained';
|
|
69
69
|
}
|
|
70
70
|
}
|
|
71
|
+
function reviewFilterValue(item) {
|
|
72
|
+
if (/not extracted|missing/i.test(item.value) || /required/i.test(item.review)) {
|
|
73
|
+
return 'missing';
|
|
74
|
+
}
|
|
75
|
+
if (/recommended|verify|manual|visual|review/i.test(item.review) && !/ready/i.test(item.review)) {
|
|
76
|
+
return 'needs_review';
|
|
77
|
+
}
|
|
78
|
+
return 'verified';
|
|
79
|
+
}
|
|
71
80
|
function compactSvgText(value, maxLength = 28) {
|
|
72
81
|
const normalized = (value ?? '').replace(/\s+/g, ' ').trim();
|
|
73
82
|
if (normalized.length <= maxLength) {
|
|
@@ -90,6 +99,21 @@ function renderMetric(metric) {
|
|
|
90
99
|
</article>
|
|
91
100
|
`;
|
|
92
101
|
}
|
|
102
|
+
function renderStatusBadges(dossier) {
|
|
103
|
+
if (dossier.badges.length === 0) {
|
|
104
|
+
return '';
|
|
105
|
+
}
|
|
106
|
+
return `
|
|
107
|
+
<div class="status-badge-row" aria-label="Document status">
|
|
108
|
+
${dossier.badges.map((badge) => `
|
|
109
|
+
<span class="status-badge ${toneClass(badge.tone)}">
|
|
110
|
+
<span>${escapeHtml(badge.label)}</span>
|
|
111
|
+
<strong>${escapeHtml(badge.value)}</strong>
|
|
112
|
+
</span>
|
|
113
|
+
`).join('')}
|
|
114
|
+
</div>
|
|
115
|
+
`;
|
|
116
|
+
}
|
|
93
117
|
function renderTable(table, className = 'data-section') {
|
|
94
118
|
const tableId = escapeHtml(idFromTitle(table.title));
|
|
95
119
|
const body = table.rows.length === 0
|
|
@@ -119,8 +143,18 @@ function renderTable(table, className = 'data-section') {
|
|
|
119
143
|
function renderTrustTable(dossier) {
|
|
120
144
|
const rows = dossier.trustItems.length === 0
|
|
121
145
|
? '<tr><td colspan="6">No trust-layer rows were retained.</td></tr>'
|
|
122
|
-
: dossier.trustItems.map((item) =>
|
|
123
|
-
|
|
146
|
+
: dossier.trustItems.map((item) => {
|
|
147
|
+
const reviewFilter = reviewFilterValue(item);
|
|
148
|
+
const searchText = [
|
|
149
|
+
item.item,
|
|
150
|
+
item.value,
|
|
151
|
+
item.sourcePage,
|
|
152
|
+
item.confidence,
|
|
153
|
+
item.review,
|
|
154
|
+
item.evidence,
|
|
155
|
+
].join(' ').toLowerCase();
|
|
156
|
+
return `
|
|
157
|
+
<tr data-trust-row data-review="${escapeHtml(reviewFilter)}" data-search="${escapeHtml(searchText)}">
|
|
124
158
|
<td><strong>${escapeHtml(item.item)}</strong></td>
|
|
125
159
|
<td>${escapeHtml(item.value)}</td>
|
|
126
160
|
<td>${escapeHtml(item.sourcePage)}</td>
|
|
@@ -128,13 +162,26 @@ function renderTrustTable(dossier) {
|
|
|
128
162
|
<td>${escapeHtml(item.review)}</td>
|
|
129
163
|
<td>${escapeHtml(item.evidence)}</td>
|
|
130
164
|
</tr>
|
|
131
|
-
|
|
165
|
+
`;
|
|
166
|
+
}).join('');
|
|
132
167
|
return `
|
|
133
168
|
<section class="data-section" id="parameters">
|
|
134
169
|
<div class="section-heading">
|
|
135
170
|
<h2>Engineering Parameters</h2>
|
|
136
171
|
<p>Evidence-first review table. Every retained or missing item is shown with source, confidence, review posture, and an evidence snippet.</p>
|
|
137
172
|
</div>
|
|
173
|
+
<div class="trust-controls" aria-label="Parameter table controls">
|
|
174
|
+
<label>
|
|
175
|
+
<span>Search parameters</span>
|
|
176
|
+
<input id="trust-search" type="search" placeholder="Filter by parameter, value, source, or evidence" />
|
|
177
|
+
</label>
|
|
178
|
+
<div class="filter-row" aria-label="Review status filters">
|
|
179
|
+
<button type="button" class="filter-button active" data-trust-filter="all">All</button>
|
|
180
|
+
<button type="button" class="filter-button" data-trust-filter="needs_review">Needs review</button>
|
|
181
|
+
<button type="button" class="filter-button" data-trust-filter="missing">Missing</button>
|
|
182
|
+
<button type="button" class="filter-button" data-trust-filter="verified">Verified</button>
|
|
183
|
+
</div>
|
|
184
|
+
</div>
|
|
138
185
|
<div class="table-shell trust-table">
|
|
139
186
|
<table>
|
|
140
187
|
<thead>
|
|
@@ -153,6 +200,97 @@ function renderTrustTable(dossier) {
|
|
|
153
200
|
</section>
|
|
154
201
|
`;
|
|
155
202
|
}
|
|
203
|
+
function renderGroundModelCrossSection(profile) {
|
|
204
|
+
if (!profile || profile.columns.length < 2 || profile.maxDepth <= 0) {
|
|
205
|
+
return `
|
|
206
|
+
<section class="data-section" id="ground-cross-section">
|
|
207
|
+
<div class="section-heading">
|
|
208
|
+
<h2>Ground Model Cross-Section</h2>
|
|
209
|
+
<p>A schematic cross-section needs at least two boreholes with retained depth evidence.</p>
|
|
210
|
+
</div>
|
|
211
|
+
<div class="empty-state">Ground-model bands were not drawn because borehole depth and layer evidence were incomplete.</div>
|
|
212
|
+
</section>
|
|
213
|
+
`;
|
|
214
|
+
}
|
|
215
|
+
const width = 940;
|
|
216
|
+
const height = 310;
|
|
217
|
+
const plotTop = 58;
|
|
218
|
+
const plotHeight = 182;
|
|
219
|
+
const left = 84;
|
|
220
|
+
const right = width - 44;
|
|
221
|
+
const usableWidth = right - left;
|
|
222
|
+
const referenceLayers = profile.columns
|
|
223
|
+
.flatMap((column) => column.layers)
|
|
224
|
+
.sort((leftLayer, rightLayer) => leftLayer.depthFrom - rightLayer.depthFrom)
|
|
225
|
+
.slice(0, 8);
|
|
226
|
+
const layers = referenceLayers.length > 0
|
|
227
|
+
? referenceLayers
|
|
228
|
+
: [{
|
|
229
|
+
depthFrom: 0,
|
|
230
|
+
depthTo: profile.maxDepth,
|
|
231
|
+
label: 'Ground profile',
|
|
232
|
+
description: 'Layer boundaries were not available in structured form.',
|
|
233
|
+
tone: 'neutral',
|
|
234
|
+
uncertain: true,
|
|
235
|
+
}];
|
|
236
|
+
const yForDepth = (depth) => plotTop + (Math.max(0, Math.min(profile.maxDepth, depth)) / profile.maxDepth) * plotHeight;
|
|
237
|
+
const ticks = Array.from({ length: 6 }, (_value, index) => Number((profile.maxDepth * index / 5).toFixed(2)));
|
|
238
|
+
const columnX = (index) => profile.columns.length === 1
|
|
239
|
+
? left + usableWidth / 2
|
|
240
|
+
: left + (usableWidth * index / (profile.columns.length - 1));
|
|
241
|
+
return `
|
|
242
|
+
<section class="data-section" id="ground-cross-section">
|
|
243
|
+
<div class="section-heading">
|
|
244
|
+
<h2>Ground Model Cross-Section</h2>
|
|
245
|
+
<p>Conceptual cross-section connecting retained borehole evidence. Bands are schematic and should be verified against source logs before design use.</p>
|
|
246
|
+
</div>
|
|
247
|
+
<div class="profile-shell cross-section-shell">
|
|
248
|
+
<svg class="ground-cross-section" viewBox="0 0 ${width} ${height}" role="img" aria-label="AI-assisted ground model cross-section">
|
|
249
|
+
<rect x="0" y="0" width="${width}" height="${height}" rx="18" fill="#ffffff" />
|
|
250
|
+
${ticks.map((tick) => {
|
|
251
|
+
const y = yForDepth(tick);
|
|
252
|
+
return `
|
|
253
|
+
<line x1="${left - 34}" y1="${y.toFixed(2)}" x2="${right + 12}" y2="${y.toFixed(2)}" stroke="#d8e0ea" stroke-width="1" />
|
|
254
|
+
<text x="14" y="${(y + 4).toFixed(2)}" class="profile-axis">${escapeHtml(tick.toFixed(tick % 1 === 0 ? 0 : 1))} ${escapeHtml(profile.depthUnit)}</text>
|
|
255
|
+
`;
|
|
256
|
+
}).join('')}
|
|
257
|
+
${layers.map((layer) => {
|
|
258
|
+
const y1 = yForDepth(layer.depthFrom);
|
|
259
|
+
const y2 = Math.max(y1 + 18, yForDepth(layer.depthTo));
|
|
260
|
+
return `
|
|
261
|
+
<g>
|
|
262
|
+
<title>${escapeHtml(`${layer.label}: ${layer.description}`)}</title>
|
|
263
|
+
<path d="M ${left} ${y1.toFixed(2)} L ${right} ${y1.toFixed(2)} L ${right} ${y2.toFixed(2)} L ${left} ${y2.toFixed(2)} Z"
|
|
264
|
+
fill="${toneColor(layer.tone)}" stroke="#7a8aa0" stroke-width="1.2" ${layer.uncertain ? 'stroke-dasharray="8 6"' : ''} opacity="0.88" />
|
|
265
|
+
<text x="${left + 18}" y="${(y1 + Math.min(34, (y2 - y1) / 2 + 5)).toFixed(2)}" class="profile-label">${escapeHtml(compactSvgText(layer.label, 32))}</text>
|
|
266
|
+
<text x="${left + 18}" y="${(y1 + Math.min(52, (y2 - y1) / 2 + 23)).toFixed(2)}" class="profile-small">${escapeHtml(compactSvgText(layer.description, 64))}</text>
|
|
267
|
+
</g>
|
|
268
|
+
`;
|
|
269
|
+
}).join('')}
|
|
270
|
+
${profile.columns.map((column, index) => {
|
|
271
|
+
const x = columnX(index);
|
|
272
|
+
const depth = column.totalDepth ?? profile.maxDepth;
|
|
273
|
+
const bottomY = yForDepth(depth);
|
|
274
|
+
const waterY = column.waterTableDepth != null ? yForDepth(column.waterTableDepth) : null;
|
|
275
|
+
return `
|
|
276
|
+
<g>
|
|
277
|
+
<line x1="${x.toFixed(2)}" y1="${plotTop - 10}" x2="${x.toFixed(2)}" y2="${bottomY.toFixed(2)}" stroke="#0b4f8a" stroke-width="3" />
|
|
278
|
+
<circle cx="${x.toFixed(2)}" cy="${plotTop - 12}" r="6" fill="#0b4f8a" />
|
|
279
|
+
<text x="${(x - 24).toFixed(2)}" y="28" class="profile-title">${escapeHtml(column.boreholeId)}</text>
|
|
280
|
+
<text x="${(x - 36).toFixed(2)}" y="${height - 32}" class="profile-small">TD ${escapeHtml(depth.toFixed(2))} ${escapeHtml(profile.depthUnit)}</text>
|
|
281
|
+
${waterY != null ? `
|
|
282
|
+
<line x1="${(x - 34).toFixed(2)}" y1="${waterY.toFixed(2)}" x2="${(x + 34).toFixed(2)}" y2="${waterY.toFixed(2)}" stroke="#0891b2" stroke-width="2" />
|
|
283
|
+
<text x="${(x - 40).toFixed(2)}" y="${(waterY - 7).toFixed(2)}" class="profile-water">GW</text>
|
|
284
|
+
` : ''}
|
|
285
|
+
</g>
|
|
286
|
+
`;
|
|
287
|
+
}).join('')}
|
|
288
|
+
</svg>
|
|
289
|
+
</div>
|
|
290
|
+
<p class="verification-note">AI-assisted ground model. Use source logs and engineering judgment before adopting layer continuity, groundwater, or design parameters.</p>
|
|
291
|
+
</section>
|
|
292
|
+
`;
|
|
293
|
+
}
|
|
156
294
|
function renderBoreholeProfile(profile) {
|
|
157
295
|
if (!profile || profile.columns.length === 0 || profile.maxDepth <= 0) {
|
|
158
296
|
return `
|
|
@@ -268,9 +406,21 @@ function renderSourceEvidence(dossier) {
|
|
|
268
406
|
<span>${escapeHtml(card.parseStatus)} | ${escapeHtml(`${card.confidence}%`)}</span>
|
|
269
407
|
</div>
|
|
270
408
|
<h3>${escapeHtml(card.title)}</h3>
|
|
271
|
-
<p>${escapeHtml(
|
|
409
|
+
<p>${escapeHtml([
|
|
410
|
+
sourceModeLabel(card.sourceHint),
|
|
411
|
+
card.cacheStatus === 'hit'
|
|
412
|
+
? 'Evidence cache reused'
|
|
413
|
+
: card.cacheStatus === 'stored'
|
|
414
|
+
? 'Evidence cached for reruns'
|
|
415
|
+
: null,
|
|
416
|
+
].filter(Boolean).join(' | '))}</p>
|
|
272
417
|
${card.highlights.length > 0 ? `<ul>${card.highlights.map((item) => `<li>${escapeHtml(item)}</li>`).join('')}</ul>` : '<p>No strong structured highlight was retained for this page.</p>'}
|
|
273
418
|
${card.warnings.length > 0 ? `<small>Human verification recommended: ${escapeHtml(String(card.warnings.length))} retained warning(s).</small>` : ''}
|
|
419
|
+
<div class="evidence-actions" aria-label="${escapeHtml(`${card.pageLabel} review actions`)}">
|
|
420
|
+
<a href="#processing-audit">Open source page</a>
|
|
421
|
+
<button type="button" data-dossier-action="verified">Mark verified</button>
|
|
422
|
+
<button type="button" data-dossier-action="flagged">Flag issue</button>
|
|
423
|
+
</div>
|
|
274
424
|
</article>
|
|
275
425
|
`).join('')}
|
|
276
426
|
</div>
|
|
@@ -329,6 +479,28 @@ function renderProcessingAudit(dossier, auditTables) {
|
|
|
329
479
|
</details>
|
|
330
480
|
`;
|
|
331
481
|
}
|
|
482
|
+
function renderReviewBar(dossier) {
|
|
483
|
+
const missingCount = dossier.trustItems.filter((item) => reviewFilterValue(item) === 'missing').length;
|
|
484
|
+
const needsReviewCount = dossier.trustItems.filter((item) => reviewFilterValue(item) === 'needs_review').length;
|
|
485
|
+
const reviewBadgeValue = dossier.badges.find((badge) => /review/i.test(badge.label))?.value ?? 'Yes';
|
|
486
|
+
const reviewRequired = /^no$/i.test(reviewBadgeValue) ? 'Review ready' : 'Review required';
|
|
487
|
+
return `
|
|
488
|
+
<div class="review-bar" role="region" aria-label="Human review workflow">
|
|
489
|
+
<div>
|
|
490
|
+
<strong>${escapeHtml(reviewRequired)}</strong>
|
|
491
|
+
<span>${escapeHtml(`${dossier.pageCards.length} page(s), ${dossier.trustItems.length} review item(s), ${needsReviewCount} need review, ${missingCount} missing`)}</span>
|
|
492
|
+
</div>
|
|
493
|
+
<div class="review-actions">
|
|
494
|
+
<a href="#parameters" class="primary-action">Approve Extraction</a>
|
|
495
|
+
<a href="#risks">Flag for Review</a>
|
|
496
|
+
<button type="button" onclick="window.print()">Export Dossier</button>
|
|
497
|
+
<a href="#ground-cross-section">Generate Ground Model</a>
|
|
498
|
+
<a href="#source-evidence">Ask Geotech Agent</a>
|
|
499
|
+
<a href="#processing-audit">Open Audit Trail</a>
|
|
500
|
+
</div>
|
|
501
|
+
</div>
|
|
502
|
+
`;
|
|
503
|
+
}
|
|
332
504
|
function renderActionBar() {
|
|
333
505
|
return `
|
|
334
506
|
<div class="action-bar" aria-label="Review actions">
|
|
@@ -391,6 +563,7 @@ export function renderIngestDossierAsHtml(dossier) {
|
|
|
391
563
|
background: var(--bg);
|
|
392
564
|
font-variant-numeric: tabular-nums;
|
|
393
565
|
overflow-x: hidden;
|
|
566
|
+
padding-bottom: 98px;
|
|
394
567
|
}
|
|
395
568
|
|
|
396
569
|
.layout {
|
|
@@ -531,6 +704,37 @@ export function renderIngestDossierAsHtml(dossier) {
|
|
|
531
704
|
|
|
532
705
|
.hero-meta strong { color: var(--text); }
|
|
533
706
|
|
|
707
|
+
.status-badge-row {
|
|
708
|
+
display: flex;
|
|
709
|
+
flex-wrap: wrap;
|
|
710
|
+
gap: 9px;
|
|
711
|
+
margin-top: 16px;
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
.status-badge {
|
|
715
|
+
display: inline-grid;
|
|
716
|
+
gap: 4px;
|
|
717
|
+
min-width: 126px;
|
|
718
|
+
padding: 10px 12px;
|
|
719
|
+
border: 1px solid rgba(102, 112, 133, 0.18);
|
|
720
|
+
border-radius: 14px;
|
|
721
|
+
line-height: 1.2;
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
.status-badge span {
|
|
725
|
+
color: currentColor;
|
|
726
|
+
opacity: 0.72;
|
|
727
|
+
font-size: 0.68rem;
|
|
728
|
+
text-transform: uppercase;
|
|
729
|
+
font-weight: 900;
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
.status-badge strong {
|
|
733
|
+
color: var(--text);
|
|
734
|
+
font-size: 0.86rem;
|
|
735
|
+
overflow-wrap: anywhere;
|
|
736
|
+
}
|
|
737
|
+
|
|
534
738
|
.executive-grid, .metric-grid, .card-grid, .evidence-grid, .audit-page-grid, .footer-grid {
|
|
535
739
|
display: grid;
|
|
536
740
|
gap: 16px;
|
|
@@ -633,6 +837,67 @@ export function renderIngestDossierAsHtml(dossier) {
|
|
|
633
837
|
color: #ffffff;
|
|
634
838
|
}
|
|
635
839
|
|
|
840
|
+
.review-bar {
|
|
841
|
+
position: fixed;
|
|
842
|
+
left: 50%;
|
|
843
|
+
bottom: 0;
|
|
844
|
+
z-index: 45;
|
|
845
|
+
display: flex;
|
|
846
|
+
align-items: center;
|
|
847
|
+
justify-content: space-between;
|
|
848
|
+
gap: 18px;
|
|
849
|
+
width: min(1180px, calc(100vw - 36px));
|
|
850
|
+
margin: 10px auto 0;
|
|
851
|
+
padding: 13px 14px;
|
|
852
|
+
border: 1px solid #263244;
|
|
853
|
+
border-radius: 18px 18px 0 0;
|
|
854
|
+
background: rgba(11, 18, 32, 0.94);
|
|
855
|
+
color: #f8fafc;
|
|
856
|
+
box-shadow: 0 -18px 38px rgba(11, 18, 32, 0.2);
|
|
857
|
+
backdrop-filter: blur(14px);
|
|
858
|
+
transform: translate(-50%, 120%);
|
|
859
|
+
transition: transform 180ms ease;
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
.review-bar.visible {
|
|
863
|
+
transform: translate(-50%, 0);
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
.review-bar > div:first-child {
|
|
867
|
+
display: grid;
|
|
868
|
+
gap: 3px;
|
|
869
|
+
min-width: 220px;
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
.review-bar strong { font-size: 0.95rem; }
|
|
873
|
+
.review-bar span { color: #94a3b8; font-size: 0.84rem; line-height: 1.4; }
|
|
874
|
+
|
|
875
|
+
.review-actions {
|
|
876
|
+
display: flex;
|
|
877
|
+
flex-wrap: wrap;
|
|
878
|
+
justify-content: flex-end;
|
|
879
|
+
gap: 8px;
|
|
880
|
+
}
|
|
881
|
+
|
|
882
|
+
.review-actions a, .review-actions button {
|
|
883
|
+
border: 1px solid #334155;
|
|
884
|
+
border-radius: 10px;
|
|
885
|
+
background: #111827;
|
|
886
|
+
color: #dbeafe;
|
|
887
|
+
padding: 8px 10px;
|
|
888
|
+
font: inherit;
|
|
889
|
+
font-size: 0.78rem;
|
|
890
|
+
font-weight: 850;
|
|
891
|
+
text-decoration: none;
|
|
892
|
+
cursor: pointer;
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
.review-actions .primary-action {
|
|
896
|
+
background: #38bdf8;
|
|
897
|
+
border-color: #38bdf8;
|
|
898
|
+
color: #0b1220;
|
|
899
|
+
}
|
|
900
|
+
|
|
636
901
|
.data-section {
|
|
637
902
|
display: grid;
|
|
638
903
|
gap: 16px;
|
|
@@ -698,6 +963,27 @@ export function renderIngestDossierAsHtml(dossier) {
|
|
|
698
963
|
height: auto;
|
|
699
964
|
}
|
|
700
965
|
|
|
966
|
+
.cross-section-shell {
|
|
967
|
+
background:
|
|
968
|
+
linear-gradient(180deg, rgba(11, 79, 138, 0.04), rgba(22, 135, 93, 0.04)),
|
|
969
|
+
var(--surface);
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
.ground-cross-section {
|
|
973
|
+
display: block;
|
|
974
|
+
min-width: 840px;
|
|
975
|
+
width: 100%;
|
|
976
|
+
height: auto;
|
|
977
|
+
}
|
|
978
|
+
|
|
979
|
+
.verification-note {
|
|
980
|
+
color: var(--muted);
|
|
981
|
+
font-weight: 700;
|
|
982
|
+
line-height: 1.55;
|
|
983
|
+
padding-left: 14px;
|
|
984
|
+
border-left: 3px solid var(--warning);
|
|
985
|
+
}
|
|
986
|
+
|
|
701
987
|
.profile-title { font: 800 15px Inter, Segoe UI, sans-serif; fill: #101828; }
|
|
702
988
|
.profile-label { font: 800 13px Inter, Segoe UI, sans-serif; fill: #101828; }
|
|
703
989
|
.profile-small { font: 11px Inter, Segoe UI, sans-serif; fill: #475467; }
|
|
@@ -750,6 +1036,71 @@ export function renderIngestDossierAsHtml(dossier) {
|
|
|
750
1036
|
line-height: 1;
|
|
751
1037
|
}
|
|
752
1038
|
|
|
1039
|
+
.trust-controls {
|
|
1040
|
+
display: flex;
|
|
1041
|
+
align-items: end;
|
|
1042
|
+
justify-content: space-between;
|
|
1043
|
+
gap: 12px;
|
|
1044
|
+
flex-wrap: wrap;
|
|
1045
|
+
padding: 14px;
|
|
1046
|
+
border: 1px solid var(--border);
|
|
1047
|
+
border-radius: var(--radius);
|
|
1048
|
+
background: var(--surface);
|
|
1049
|
+
}
|
|
1050
|
+
|
|
1051
|
+
.trust-controls label {
|
|
1052
|
+
display: grid;
|
|
1053
|
+
gap: 7px;
|
|
1054
|
+
flex: 1 1 320px;
|
|
1055
|
+
color: var(--muted);
|
|
1056
|
+
font-size: 0.75rem;
|
|
1057
|
+
text-transform: uppercase;
|
|
1058
|
+
font-weight: 900;
|
|
1059
|
+
}
|
|
1060
|
+
|
|
1061
|
+
.trust-controls input {
|
|
1062
|
+
width: 100%;
|
|
1063
|
+
border: 1px solid var(--border);
|
|
1064
|
+
border-radius: 12px;
|
|
1065
|
+
background: #ffffff;
|
|
1066
|
+
color: var(--text);
|
|
1067
|
+
padding: 11px 12px;
|
|
1068
|
+
font: inherit;
|
|
1069
|
+
font-size: 0.92rem;
|
|
1070
|
+
outline: none;
|
|
1071
|
+
text-transform: none;
|
|
1072
|
+
font-weight: 600;
|
|
1073
|
+
}
|
|
1074
|
+
|
|
1075
|
+
.trust-controls input:focus {
|
|
1076
|
+
border-color: #93c5fd;
|
|
1077
|
+
box-shadow: 0 0 0 4px rgba(11, 79, 138, 0.08);
|
|
1078
|
+
}
|
|
1079
|
+
|
|
1080
|
+
.filter-row {
|
|
1081
|
+
display: flex;
|
|
1082
|
+
gap: 8px;
|
|
1083
|
+
flex-wrap: wrap;
|
|
1084
|
+
}
|
|
1085
|
+
|
|
1086
|
+
.filter-button {
|
|
1087
|
+
border: 1px solid var(--border);
|
|
1088
|
+
border-radius: 999px;
|
|
1089
|
+
background: var(--surface-soft);
|
|
1090
|
+
color: var(--muted);
|
|
1091
|
+
padding: 9px 12px;
|
|
1092
|
+
font: inherit;
|
|
1093
|
+
font-size: 0.8rem;
|
|
1094
|
+
font-weight: 850;
|
|
1095
|
+
cursor: pointer;
|
|
1096
|
+
}
|
|
1097
|
+
|
|
1098
|
+
.filter-button.active {
|
|
1099
|
+
background: var(--primary);
|
|
1100
|
+
color: #ffffff;
|
|
1101
|
+
border-color: var(--primary);
|
|
1102
|
+
}
|
|
1103
|
+
|
|
753
1104
|
.evidence-grid {
|
|
754
1105
|
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
|
|
755
1106
|
}
|
|
@@ -778,6 +1129,38 @@ export function renderIngestDossierAsHtml(dossier) {
|
|
|
778
1129
|
line-height: 1.45;
|
|
779
1130
|
}
|
|
780
1131
|
|
|
1132
|
+
.evidence-actions {
|
|
1133
|
+
display: flex;
|
|
1134
|
+
flex-wrap: wrap;
|
|
1135
|
+
gap: 8px;
|
|
1136
|
+
padding-top: 4px;
|
|
1137
|
+
}
|
|
1138
|
+
|
|
1139
|
+
.evidence-actions a, .evidence-actions button {
|
|
1140
|
+
border: 1px solid var(--border);
|
|
1141
|
+
border-radius: 10px;
|
|
1142
|
+
background: rgba(255, 255, 255, 0.62);
|
|
1143
|
+
color: var(--primary);
|
|
1144
|
+
padding: 8px 9px;
|
|
1145
|
+
font: inherit;
|
|
1146
|
+
font-size: 0.78rem;
|
|
1147
|
+
font-weight: 850;
|
|
1148
|
+
text-decoration: none;
|
|
1149
|
+
cursor: pointer;
|
|
1150
|
+
}
|
|
1151
|
+
|
|
1152
|
+
.evidence-actions button[data-state="verified"] {
|
|
1153
|
+
color: var(--success);
|
|
1154
|
+
border-color: rgba(22, 135, 93, 0.28);
|
|
1155
|
+
background: var(--success-soft);
|
|
1156
|
+
}
|
|
1157
|
+
|
|
1158
|
+
.evidence-actions button[data-state="flagged"] {
|
|
1159
|
+
color: var(--warning);
|
|
1160
|
+
border-color: rgba(183, 121, 31, 0.28);
|
|
1161
|
+
background: var(--warning-soft);
|
|
1162
|
+
}
|
|
1163
|
+
|
|
781
1164
|
.empty-state {
|
|
782
1165
|
padding: 18px;
|
|
783
1166
|
border: 1px dashed var(--border);
|
|
@@ -891,6 +1274,30 @@ export function renderIngestDossierAsHtml(dossier) {
|
|
|
891
1274
|
line-height: 1.58;
|
|
892
1275
|
}
|
|
893
1276
|
|
|
1277
|
+
tr[hidden] { display: none; }
|
|
1278
|
+
|
|
1279
|
+
.toast-region {
|
|
1280
|
+
position: fixed;
|
|
1281
|
+
top: 18px;
|
|
1282
|
+
right: 18px;
|
|
1283
|
+
z-index: 60;
|
|
1284
|
+
display: grid;
|
|
1285
|
+
gap: 8px;
|
|
1286
|
+
pointer-events: none;
|
|
1287
|
+
}
|
|
1288
|
+
|
|
1289
|
+
.toast {
|
|
1290
|
+
max-width: 320px;
|
|
1291
|
+
padding: 11px 14px;
|
|
1292
|
+
border: 1px solid #334155;
|
|
1293
|
+
border-radius: 12px;
|
|
1294
|
+
background: #0b1220;
|
|
1295
|
+
color: #f8fafc;
|
|
1296
|
+
box-shadow: 0 18px 38px rgba(11, 18, 32, 0.22);
|
|
1297
|
+
font-size: 0.86rem;
|
|
1298
|
+
font-weight: 700;
|
|
1299
|
+
}
|
|
1300
|
+
|
|
894
1301
|
@media (max-width: 980px) {
|
|
895
1302
|
.layout { grid-template-columns: minmax(0, 1fr); padding: 18px; max-width: 100%; }
|
|
896
1303
|
.sidebar { position: static; min-height: auto; }
|
|
@@ -899,15 +1306,23 @@ export function renderIngestDossierAsHtml(dossier) {
|
|
|
899
1306
|
}
|
|
900
1307
|
|
|
901
1308
|
@media (max-width: 620px) {
|
|
1309
|
+
body { padding-bottom: 0; }
|
|
902
1310
|
.layout { display: block; width: 100%; max-width: 390px; margin: 0; padding: 12px; }
|
|
903
1311
|
.sidebar, .content, .hero, .data-section, .audit-drawer, .footer-grid {
|
|
904
1312
|
width: 100%;
|
|
905
1313
|
max-width: 100%;
|
|
906
1314
|
}
|
|
1315
|
+
.sidebar { padding: 16px; min-height: auto; border-radius: 14px; }
|
|
1316
|
+
.brand { gap: 5px; padding-bottom: 12px; margin-bottom: 10px; }
|
|
1317
|
+
.brand span { display: none; }
|
|
1318
|
+
.nav-list { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 6px; }
|
|
1319
|
+
.nav-list a { padding: 8px 9px; font-size: 0.82rem; }
|
|
907
1320
|
.content { margin-top: 18px; }
|
|
908
1321
|
.hero { padding: 22px; }
|
|
909
1322
|
.executive-grid, .metric-grid, .card-grid, .evidence-grid, .footer-grid { grid-template-columns: 1fr; }
|
|
910
|
-
.
|
|
1323
|
+
.review-bar { position: static; transform: none; width: 100%; margin-top: 18px; border-radius: 18px; align-items: stretch; }
|
|
1324
|
+
.review-bar.visible { transform: none; }
|
|
1325
|
+
.review-actions { justify-content: flex-start; }
|
|
911
1326
|
h1 { font-size: 1.72rem; max-width: 100%; }
|
|
912
1327
|
}
|
|
913
1328
|
</style>
|
|
@@ -922,6 +1337,7 @@ export function renderIngestDossierAsHtml(dossier) {
|
|
|
922
1337
|
<nav class="nav-list">
|
|
923
1338
|
<a href="#overview">Overview</a>
|
|
924
1339
|
<a href="#ground-model">Ground Model</a>
|
|
1340
|
+
<a href="#ground-cross-section">Cross-Section</a>
|
|
925
1341
|
<a href="#boreholes">Boreholes</a>
|
|
926
1342
|
<a href="#parameters">Engineering Parameters</a>
|
|
927
1343
|
<a href="#risks">Risks and Limitations</a>
|
|
@@ -938,6 +1354,7 @@ export function renderIngestDossierAsHtml(dossier) {
|
|
|
938
1354
|
<h1>${escapeHtml(dossier.title)}</h1>
|
|
939
1355
|
<p class="hero-subtitle">${escapeHtml(subtitle)}</p>
|
|
940
1356
|
<p class="hero-summary">${escapeHtml(dossier.summary)}</p>
|
|
1357
|
+
${renderStatusBadges(dossier)}
|
|
941
1358
|
</div>
|
|
942
1359
|
<div class="hero-meta">
|
|
943
1360
|
<strong>${escapeHtml(dossier.sourceLabel)}</strong>
|
|
@@ -977,6 +1394,7 @@ export function renderIngestDossierAsHtml(dossier) {
|
|
|
977
1394
|
</div>
|
|
978
1395
|
</section>
|
|
979
1396
|
|
|
1397
|
+
${renderGroundModelCrossSection(dossier.boreholeProfile)}
|
|
980
1398
|
${renderBoreholeProfile(dossier.boreholeProfile)}
|
|
981
1399
|
${renderTrustTable(dossier)}
|
|
982
1400
|
${mainTables.map((table) => renderTable(table)).join('')}
|
|
@@ -1010,6 +1428,67 @@ export function renderIngestDossierAsHtml(dossier) {
|
|
|
1010
1428
|
</section>
|
|
1011
1429
|
</main>
|
|
1012
1430
|
</div>
|
|
1431
|
+
${renderReviewBar(dossier)}
|
|
1432
|
+
<div class="toast-region" aria-live="polite" aria-atomic="true"></div>
|
|
1433
|
+
<script>
|
|
1434
|
+
(() => {
|
|
1435
|
+
const search = document.getElementById('trust-search');
|
|
1436
|
+
const rows = Array.from(document.querySelectorAll('[data-trust-row]'));
|
|
1437
|
+
const buttons = Array.from(document.querySelectorAll('[data-trust-filter]'));
|
|
1438
|
+
const toastRegion = document.querySelector('.toast-region');
|
|
1439
|
+
const reviewBar = document.querySelector('.review-bar');
|
|
1440
|
+
let activeFilter = 'all';
|
|
1441
|
+
|
|
1442
|
+
const showToast = (message) => {
|
|
1443
|
+
if (!toastRegion) return;
|
|
1444
|
+
const toast = document.createElement('div');
|
|
1445
|
+
toast.className = 'toast';
|
|
1446
|
+
toast.textContent = message;
|
|
1447
|
+
toastRegion.appendChild(toast);
|
|
1448
|
+
window.setTimeout(() => toast.remove(), 2600);
|
|
1449
|
+
};
|
|
1450
|
+
|
|
1451
|
+
const applyTrustFilter = () => {
|
|
1452
|
+
const query = String(search?.value ?? '').trim().toLowerCase();
|
|
1453
|
+
rows.forEach((row) => {
|
|
1454
|
+
const text = row.getAttribute('data-search') ?? '';
|
|
1455
|
+
const review = row.getAttribute('data-review') ?? '';
|
|
1456
|
+
const queryMatch = !query || text.includes(query);
|
|
1457
|
+
const filterMatch = activeFilter === 'all' || review === activeFilter;
|
|
1458
|
+
row.hidden = !(queryMatch && filterMatch);
|
|
1459
|
+
});
|
|
1460
|
+
};
|
|
1461
|
+
|
|
1462
|
+
const syncReviewBar = () => {
|
|
1463
|
+
if (!reviewBar) return;
|
|
1464
|
+
reviewBar.classList.toggle('visible', window.innerWidth > 620 && window.scrollY > 420);
|
|
1465
|
+
};
|
|
1466
|
+
|
|
1467
|
+
syncReviewBar();
|
|
1468
|
+
window.addEventListener('scroll', syncReviewBar, { passive: true });
|
|
1469
|
+
window.addEventListener('resize', syncReviewBar);
|
|
1470
|
+
|
|
1471
|
+
search?.addEventListener('input', applyTrustFilter);
|
|
1472
|
+
buttons.forEach((button) => {
|
|
1473
|
+
button.addEventListener('click', () => {
|
|
1474
|
+
activeFilter = button.getAttribute('data-trust-filter') ?? 'all';
|
|
1475
|
+
buttons.forEach((candidate) => candidate.classList.toggle('active', candidate === button));
|
|
1476
|
+
applyTrustFilter();
|
|
1477
|
+
});
|
|
1478
|
+
});
|
|
1479
|
+
|
|
1480
|
+
document.querySelectorAll('[data-dossier-action]').forEach((control) => {
|
|
1481
|
+
control.addEventListener('click', () => {
|
|
1482
|
+
const state = control.getAttribute('data-dossier-action') ?? '';
|
|
1483
|
+
control.setAttribute('data-state', state);
|
|
1484
|
+
control.textContent = state === 'verified' ? 'Verified' : 'Issue flagged';
|
|
1485
|
+
showToast(state === 'verified'
|
|
1486
|
+
? 'Evidence item marked verified in this local dossier view.'
|
|
1487
|
+
: 'Evidence item flagged for engineering review in this local dossier view.');
|
|
1488
|
+
});
|
|
1489
|
+
});
|
|
1490
|
+
})();
|
|
1491
|
+
</script>
|
|
1013
1492
|
</body>
|
|
1014
1493
|
</html>`;
|
|
1015
1494
|
}
|