@geotechcli/core 0.4.39 → 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.map +1 -1
- package/dist/ingest/geotech-document.js +4 -1
- package/dist/ingest/geotech-document.js.map +1 -1
- package/dist/ingest/job-worker.d.ts.map +1 -1
- package/dist/ingest/job-worker.js +5 -1
- package/dist/ingest/job-worker.js.map +1 -1
- package/dist/meta/metadata.json +1 -1
- package/dist/report/html.d.ts.map +1 -1
- package/dist/report/html.js +476 -4
- package/dist/report/html.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 `
|
|
@@ -278,6 +416,11 @@ function renderSourceEvidence(dossier) {
|
|
|
278
416
|
].filter(Boolean).join(' | '))}</p>
|
|
279
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>'}
|
|
280
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>
|
|
281
424
|
</article>
|
|
282
425
|
`).join('')}
|
|
283
426
|
</div>
|
|
@@ -336,6 +479,28 @@ function renderProcessingAudit(dossier, auditTables) {
|
|
|
336
479
|
</details>
|
|
337
480
|
`;
|
|
338
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
|
+
}
|
|
339
504
|
function renderActionBar() {
|
|
340
505
|
return `
|
|
341
506
|
<div class="action-bar" aria-label="Review actions">
|
|
@@ -398,6 +563,7 @@ export function renderIngestDossierAsHtml(dossier) {
|
|
|
398
563
|
background: var(--bg);
|
|
399
564
|
font-variant-numeric: tabular-nums;
|
|
400
565
|
overflow-x: hidden;
|
|
566
|
+
padding-bottom: 98px;
|
|
401
567
|
}
|
|
402
568
|
|
|
403
569
|
.layout {
|
|
@@ -538,6 +704,37 @@ export function renderIngestDossierAsHtml(dossier) {
|
|
|
538
704
|
|
|
539
705
|
.hero-meta strong { color: var(--text); }
|
|
540
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
|
+
|
|
541
738
|
.executive-grid, .metric-grid, .card-grid, .evidence-grid, .audit-page-grid, .footer-grid {
|
|
542
739
|
display: grid;
|
|
543
740
|
gap: 16px;
|
|
@@ -640,6 +837,67 @@ export function renderIngestDossierAsHtml(dossier) {
|
|
|
640
837
|
color: #ffffff;
|
|
641
838
|
}
|
|
642
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
|
+
|
|
643
901
|
.data-section {
|
|
644
902
|
display: grid;
|
|
645
903
|
gap: 16px;
|
|
@@ -705,6 +963,27 @@ export function renderIngestDossierAsHtml(dossier) {
|
|
|
705
963
|
height: auto;
|
|
706
964
|
}
|
|
707
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
|
+
|
|
708
987
|
.profile-title { font: 800 15px Inter, Segoe UI, sans-serif; fill: #101828; }
|
|
709
988
|
.profile-label { font: 800 13px Inter, Segoe UI, sans-serif; fill: #101828; }
|
|
710
989
|
.profile-small { font: 11px Inter, Segoe UI, sans-serif; fill: #475467; }
|
|
@@ -757,6 +1036,71 @@ export function renderIngestDossierAsHtml(dossier) {
|
|
|
757
1036
|
line-height: 1;
|
|
758
1037
|
}
|
|
759
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
|
+
|
|
760
1104
|
.evidence-grid {
|
|
761
1105
|
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
|
|
762
1106
|
}
|
|
@@ -785,6 +1129,38 @@ export function renderIngestDossierAsHtml(dossier) {
|
|
|
785
1129
|
line-height: 1.45;
|
|
786
1130
|
}
|
|
787
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
|
+
|
|
788
1164
|
.empty-state {
|
|
789
1165
|
padding: 18px;
|
|
790
1166
|
border: 1px dashed var(--border);
|
|
@@ -898,6 +1274,30 @@ export function renderIngestDossierAsHtml(dossier) {
|
|
|
898
1274
|
line-height: 1.58;
|
|
899
1275
|
}
|
|
900
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
|
+
|
|
901
1301
|
@media (max-width: 980px) {
|
|
902
1302
|
.layout { grid-template-columns: minmax(0, 1fr); padding: 18px; max-width: 100%; }
|
|
903
1303
|
.sidebar { position: static; min-height: auto; }
|
|
@@ -906,15 +1306,23 @@ export function renderIngestDossierAsHtml(dossier) {
|
|
|
906
1306
|
}
|
|
907
1307
|
|
|
908
1308
|
@media (max-width: 620px) {
|
|
1309
|
+
body { padding-bottom: 0; }
|
|
909
1310
|
.layout { display: block; width: 100%; max-width: 390px; margin: 0; padding: 12px; }
|
|
910
1311
|
.sidebar, .content, .hero, .data-section, .audit-drawer, .footer-grid {
|
|
911
1312
|
width: 100%;
|
|
912
1313
|
max-width: 100%;
|
|
913
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; }
|
|
914
1320
|
.content { margin-top: 18px; }
|
|
915
1321
|
.hero { padding: 22px; }
|
|
916
1322
|
.executive-grid, .metric-grid, .card-grid, .evidence-grid, .footer-grid { grid-template-columns: 1fr; }
|
|
917
|
-
.
|
|
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; }
|
|
918
1326
|
h1 { font-size: 1.72rem; max-width: 100%; }
|
|
919
1327
|
}
|
|
920
1328
|
</style>
|
|
@@ -929,6 +1337,7 @@ export function renderIngestDossierAsHtml(dossier) {
|
|
|
929
1337
|
<nav class="nav-list">
|
|
930
1338
|
<a href="#overview">Overview</a>
|
|
931
1339
|
<a href="#ground-model">Ground Model</a>
|
|
1340
|
+
<a href="#ground-cross-section">Cross-Section</a>
|
|
932
1341
|
<a href="#boreholes">Boreholes</a>
|
|
933
1342
|
<a href="#parameters">Engineering Parameters</a>
|
|
934
1343
|
<a href="#risks">Risks and Limitations</a>
|
|
@@ -945,6 +1354,7 @@ export function renderIngestDossierAsHtml(dossier) {
|
|
|
945
1354
|
<h1>${escapeHtml(dossier.title)}</h1>
|
|
946
1355
|
<p class="hero-subtitle">${escapeHtml(subtitle)}</p>
|
|
947
1356
|
<p class="hero-summary">${escapeHtml(dossier.summary)}</p>
|
|
1357
|
+
${renderStatusBadges(dossier)}
|
|
948
1358
|
</div>
|
|
949
1359
|
<div class="hero-meta">
|
|
950
1360
|
<strong>${escapeHtml(dossier.sourceLabel)}</strong>
|
|
@@ -984,6 +1394,7 @@ export function renderIngestDossierAsHtml(dossier) {
|
|
|
984
1394
|
</div>
|
|
985
1395
|
</section>
|
|
986
1396
|
|
|
1397
|
+
${renderGroundModelCrossSection(dossier.boreholeProfile)}
|
|
987
1398
|
${renderBoreholeProfile(dossier.boreholeProfile)}
|
|
988
1399
|
${renderTrustTable(dossier)}
|
|
989
1400
|
${mainTables.map((table) => renderTable(table)).join('')}
|
|
@@ -1017,6 +1428,67 @@ export function renderIngestDossierAsHtml(dossier) {
|
|
|
1017
1428
|
</section>
|
|
1018
1429
|
</main>
|
|
1019
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>
|
|
1020
1492
|
</body>
|
|
1021
1493
|
</html>`;
|
|
1022
1494
|
}
|