@geotechcli/core 0.4.34 → 0.4.36
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 +19 -2
- package/dist/ingest/geotech-document.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 +696 -477
- package/dist/report/html.js.map +1 -1
- package/dist/report/ingest-dossier.d.ts +47 -0
- package/dist/report/ingest-dossier.d.ts.map +1 -1
- package/dist/report/ingest-dossier.js +347 -0
- package/dist/report/ingest-dossier.js.map +1 -1
- package/package.json +1 -1
package/dist/report/html.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { GEOTECHCLI_VERSION } from '../meta/index.js';
|
|
2
2
|
function escapeHtml(value) {
|
|
3
|
-
return (value ?? '')
|
|
3
|
+
return String(value ?? '')
|
|
4
4
|
.replace(/&/g, '&')
|
|
5
5
|
.replace(/</g, '<')
|
|
6
6
|
.replace(/>/g, '>')
|
|
@@ -28,6 +28,20 @@ function toneClass(tone) {
|
|
|
28
28
|
return 'tone-neutral';
|
|
29
29
|
}
|
|
30
30
|
}
|
|
31
|
+
function toneColor(tone) {
|
|
32
|
+
switch (tone) {
|
|
33
|
+
case 'good':
|
|
34
|
+
return '#d9f5e8';
|
|
35
|
+
case 'warning':
|
|
36
|
+
return '#f7e4bd';
|
|
37
|
+
case 'danger':
|
|
38
|
+
return '#f9d3cf';
|
|
39
|
+
case 'accent':
|
|
40
|
+
return '#d8e9f7';
|
|
41
|
+
default:
|
|
42
|
+
return '#e8edf3';
|
|
43
|
+
}
|
|
44
|
+
}
|
|
31
45
|
function parsePercent(value) {
|
|
32
46
|
const match = value.match(/^(\d+(?:\.\d+)?)%$/);
|
|
33
47
|
if (!match) {
|
|
@@ -39,65 +53,39 @@ function parsePercent(value) {
|
|
|
39
53
|
function isOperationalAuditTable(title) {
|
|
40
54
|
return title === 'Page audit matrix' || title === 'Segment execution';
|
|
41
55
|
}
|
|
56
|
+
function sourceModeLabel(sourceHint) {
|
|
57
|
+
switch (sourceHint) {
|
|
58
|
+
case 'native-text':
|
|
59
|
+
case 'pdfjs-text':
|
|
60
|
+
return 'Text extraction used';
|
|
61
|
+
case 'local-ocr':
|
|
62
|
+
case 'glm-ocr':
|
|
63
|
+
return 'Layout/OCR extraction used';
|
|
64
|
+
case 'vision-ocr':
|
|
65
|
+
case 'vision-visual':
|
|
66
|
+
return 'Visual extraction used';
|
|
67
|
+
default:
|
|
68
|
+
return 'Extraction evidence retained';
|
|
69
|
+
}
|
|
70
|
+
}
|
|
42
71
|
function renderMetric(metric) {
|
|
43
72
|
const percent = parsePercent(metric.value);
|
|
44
73
|
return `
|
|
45
|
-
<article class="metric ${toneClass(metric.tone)}">
|
|
46
|
-
<span
|
|
47
|
-
<strong
|
|
74
|
+
<article class="metric-card ${toneClass(metric.tone)}">
|
|
75
|
+
<span>${escapeHtml(metric.label)}</span>
|
|
76
|
+
<strong>${escapeHtml(metric.value)}</strong>
|
|
48
77
|
${percent != null ? `
|
|
49
78
|
<span class="meter" aria-label="${escapeHtml(metric.label)} ${escapeHtml(metric.value)}">
|
|
50
|
-
<span style="width: ${escapeHtml(
|
|
79
|
+
<span style="width: ${escapeHtml(percent)}%"></span>
|
|
51
80
|
</span>
|
|
52
81
|
` : ''}
|
|
53
|
-
${metric.detail ? `<
|
|
82
|
+
${metric.detail ? `<small>${escapeHtml(metric.detail)}</small>` : ''}
|
|
54
83
|
</article>
|
|
55
84
|
`;
|
|
56
85
|
}
|
|
57
|
-
function
|
|
58
|
-
if (dossier.pageCards.length === 0) {
|
|
59
|
-
return '';
|
|
60
|
-
}
|
|
61
|
-
const counts = dossier.pageCards.reduce((acc, page) => {
|
|
62
|
-
if (page.parseStatus === 'parsed')
|
|
63
|
-
acc.parsed += 1;
|
|
64
|
-
else if (page.parseStatus === 'partial')
|
|
65
|
-
acc.partial += 1;
|
|
66
|
-
else
|
|
67
|
-
acc.failed += 1;
|
|
68
|
-
return acc;
|
|
69
|
-
}, { parsed: 0, partial: 0, failed: 0 });
|
|
70
|
-
return `
|
|
71
|
-
<section class="panel section-card overview" id="extraction-overview">
|
|
72
|
-
<div class="section-head">
|
|
73
|
-
<h2>Extraction overview</h2>
|
|
74
|
-
<p>Page outcomes at a glance. Each cell links the dossier confidence to the exact page-level status.</p>
|
|
75
|
-
</div>
|
|
76
|
-
<div class="outcome-strip" aria-label="Page extraction outcomes">
|
|
77
|
-
${dossier.pageCards.map((card) => `
|
|
78
|
-
<span
|
|
79
|
-
class="outcome-cell ${toneClass(card.tone)}"
|
|
80
|
-
title="${escapeHtml(`${card.pageLabel}: ${card.parseStatus}, ${card.confidence}% confidence`)}"
|
|
81
|
-
>${escapeHtml(card.pageLabel.replace(/^Page\s+/i, ''))}</span>
|
|
82
|
-
`).join('')}
|
|
83
|
-
</div>
|
|
84
|
-
<div class="legend-row">
|
|
85
|
-
<span><strong>${escapeHtml(String(counts.parsed))}</strong> parsed</span>
|
|
86
|
-
<span><strong>${escapeHtml(String(counts.partial))}</strong> partial</span>
|
|
87
|
-
<span><strong>${escapeHtml(String(counts.failed))}</strong> failed</span>
|
|
88
|
-
</div>
|
|
89
|
-
</section>
|
|
90
|
-
`;
|
|
91
|
-
}
|
|
92
|
-
function renderTable(table) {
|
|
86
|
+
function renderTable(table, className = 'data-section') {
|
|
93
87
|
const tableId = escapeHtml(idFromTitle(table.title));
|
|
94
|
-
const
|
|
95
|
-
<div class="section-head">
|
|
96
|
-
<h2>${escapeHtml(table.title)}</h2>
|
|
97
|
-
${table.description ? `<p>${escapeHtml(table.description)}</p>` : ''}
|
|
98
|
-
</div>
|
|
99
|
-
`;
|
|
100
|
-
const tableBody = table.rows.length === 0
|
|
88
|
+
const body = table.rows.length === 0
|
|
101
89
|
? `<div class="empty-state">${escapeHtml(table.emptyState ?? 'No data available.')}</div>`
|
|
102
90
|
: `
|
|
103
91
|
<div class="table-shell">
|
|
@@ -111,208 +99,479 @@ function renderTable(table) {
|
|
|
111
99
|
</table>
|
|
112
100
|
</div>
|
|
113
101
|
`;
|
|
114
|
-
|
|
115
|
-
|
|
102
|
+
return `
|
|
103
|
+
<section class="${className}" id="${tableId}">
|
|
104
|
+
<div class="section-heading">
|
|
105
|
+
<h2>${escapeHtml(table.title)}</h2>
|
|
106
|
+
${table.description ? `<p>${escapeHtml(table.description)}</p>` : ''}
|
|
107
|
+
</div>
|
|
108
|
+
${body}
|
|
109
|
+
</section>
|
|
110
|
+
`;
|
|
111
|
+
}
|
|
112
|
+
function renderTrustTable(dossier) {
|
|
113
|
+
const rows = dossier.trustItems.length === 0
|
|
114
|
+
? '<tr><td colspan="6">No trust-layer rows were retained.</td></tr>'
|
|
115
|
+
: dossier.trustItems.map((item) => `
|
|
116
|
+
<tr>
|
|
117
|
+
<td><strong>${escapeHtml(item.item)}</strong></td>
|
|
118
|
+
<td>${escapeHtml(item.value)}</td>
|
|
119
|
+
<td>${escapeHtml(item.sourcePage)}</td>
|
|
120
|
+
<td><span class="status-pill ${toneClass(item.tone)}">${escapeHtml(item.confidence)}</span></td>
|
|
121
|
+
<td>${escapeHtml(item.review)}</td>
|
|
122
|
+
<td>${escapeHtml(item.evidence)}</td>
|
|
123
|
+
</tr>
|
|
124
|
+
`).join('');
|
|
125
|
+
return `
|
|
126
|
+
<section class="data-section" id="parameters">
|
|
127
|
+
<div class="section-heading">
|
|
128
|
+
<h2>Engineering Parameters</h2>
|
|
129
|
+
<p>Evidence-first review table. Every retained or missing item is shown with source, confidence, review posture, and an evidence snippet.</p>
|
|
130
|
+
</div>
|
|
131
|
+
<div class="table-shell trust-table">
|
|
132
|
+
<table>
|
|
133
|
+
<thead>
|
|
134
|
+
<tr>
|
|
135
|
+
<th>Item</th>
|
|
136
|
+
<th>Value</th>
|
|
137
|
+
<th>Source page</th>
|
|
138
|
+
<th>Confidence</th>
|
|
139
|
+
<th>Needs review?</th>
|
|
140
|
+
<th>Evidence snippet</th>
|
|
141
|
+
</tr>
|
|
142
|
+
</thead>
|
|
143
|
+
<tbody>${rows}</tbody>
|
|
144
|
+
</table>
|
|
145
|
+
</div>
|
|
146
|
+
</section>
|
|
147
|
+
`;
|
|
148
|
+
}
|
|
149
|
+
function renderBoreholeProfile(profile) {
|
|
150
|
+
if (!profile || profile.columns.length === 0 || profile.maxDepth <= 0) {
|
|
116
151
|
return `
|
|
117
|
-
<
|
|
118
|
-
<
|
|
119
|
-
|
|
120
|
-
<
|
|
121
|
-
</
|
|
122
|
-
|
|
123
|
-
</
|
|
152
|
+
<section class="data-section" id="boreholes">
|
|
153
|
+
<div class="section-heading">
|
|
154
|
+
<h2>Borehole Stratigraphy Visualization</h2>
|
|
155
|
+
<p>No scaled borehole profile could be built from the retained evidence.</p>
|
|
156
|
+
</div>
|
|
157
|
+
<div class="empty-state">Borehole IDs, layer depths, or material intervals were not available in structured form.</div>
|
|
158
|
+
</section>
|
|
124
159
|
`;
|
|
125
160
|
}
|
|
126
|
-
|
|
161
|
+
const plotTop = 44;
|
|
162
|
+
const plotHeight = 420;
|
|
163
|
+
const columnWidth = 132;
|
|
164
|
+
const gap = 42;
|
|
165
|
+
const axisWidth = 78;
|
|
166
|
+
const width = axisWidth + profile.columns.length * columnWidth + Math.max(0, profile.columns.length - 1) * gap + 42;
|
|
167
|
+
const height = plotTop + plotHeight + 74;
|
|
168
|
+
const ticks = Array.from({ length: 6 }, (_value, index) => Number((profile.maxDepth * index / 5).toFixed(2)));
|
|
169
|
+
const layerRect = (layer, columnIndex) => {
|
|
170
|
+
const x = axisWidth + columnIndex * (columnWidth + gap);
|
|
171
|
+
const y = plotTop + (Math.max(0, layer.depthFrom) / profile.maxDepth) * plotHeight;
|
|
172
|
+
const rectHeight = Math.max(18, ((Math.min(profile.maxDepth, layer.depthTo) - Math.max(0, layer.depthFrom)) / profile.maxDepth) * plotHeight);
|
|
173
|
+
const midY = y + rectHeight / 2;
|
|
174
|
+
return `
|
|
175
|
+
<rect x="${x}" y="${y.toFixed(2)}" width="${columnWidth}" height="${rectHeight.toFixed(2)}" rx="8"
|
|
176
|
+
fill="${toneColor(layer.tone)}" stroke="#7a8aa0" stroke-width="1.2" ${layer.uncertain ? 'stroke-dasharray="6 5"' : ''} />
|
|
177
|
+
<text x="${x + 10}" y="${midY.toFixed(2)}" class="profile-label">${escapeHtml(layer.label)}</text>
|
|
178
|
+
<text x="${x + 10}" y="${(midY + 16).toFixed(2)}" class="profile-small">${escapeHtml(layer.description)}</text>
|
|
179
|
+
`;
|
|
180
|
+
};
|
|
181
|
+
return `
|
|
182
|
+
<section class="data-section" id="boreholes">
|
|
183
|
+
<div class="section-heading">
|
|
184
|
+
<h2>${escapeHtml(profile.title)}</h2>
|
|
185
|
+
<p>Depth-scaled borehole columns with colored stratigraphy blocks. Dashed boundaries indicate missing or uncertain intervals that need source-page verification.</p>
|
|
186
|
+
</div>
|
|
187
|
+
<div class="profile-shell">
|
|
188
|
+
<svg class="borehole-profile" viewBox="0 0 ${width} ${height}" role="img" aria-label="Borehole stratigraphy profile">
|
|
189
|
+
<rect x="0" y="0" width="${width}" height="${height}" rx="18" fill="#ffffff" />
|
|
190
|
+
${ticks.map((tick) => {
|
|
191
|
+
const y = plotTop + (tick / profile.maxDepth) * plotHeight;
|
|
192
|
+
return `
|
|
193
|
+
<line x1="48" y1="${y.toFixed(2)}" x2="${width - 24}" y2="${y.toFixed(2)}" stroke="#d8e0ea" stroke-width="1" />
|
|
194
|
+
<text x="8" y="${(y + 4).toFixed(2)}" class="profile-axis">${escapeHtml(tick.toFixed(tick % 1 === 0 ? 0 : 1))} ${escapeHtml(profile.depthUnit)}</text>
|
|
195
|
+
`;
|
|
196
|
+
}).join('')}
|
|
197
|
+
${profile.columns.map((column, columnIndex) => {
|
|
198
|
+
const x = axisWidth + columnIndex * (columnWidth + gap);
|
|
199
|
+
const waterY = column.waterTableDepth != null
|
|
200
|
+
? plotTop + (column.waterTableDepth / profile.maxDepth) * plotHeight
|
|
201
|
+
: null;
|
|
202
|
+
return `
|
|
203
|
+
<text x="${x}" y="24" class="profile-title">${escapeHtml(column.boreholeId)}</text>
|
|
204
|
+
${column.layers.map((layer) => layerRect(layer, columnIndex)).join('')}
|
|
205
|
+
${waterY != null ? `
|
|
206
|
+
<line x1="${x - 8}" y1="${waterY.toFixed(2)}" x2="${x + columnWidth + 8}" y2="${waterY.toFixed(2)}" stroke="#0b4f8a" stroke-width="2" />
|
|
207
|
+
<text x="${x + 8}" y="${(waterY - 6).toFixed(2)}" class="profile-water">Groundwater</text>
|
|
208
|
+
` : ''}
|
|
209
|
+
<text x="${x}" y="${height - 24}" class="profile-small">TD ${escapeHtml(column.totalDepth != null ? `${column.totalDepth.toFixed(2)} m` : 'unavailable')}</text>
|
|
210
|
+
`;
|
|
211
|
+
}).join('')}
|
|
212
|
+
</svg>
|
|
213
|
+
</div>
|
|
214
|
+
${profile.notes.length > 0 ? `<ul class="profile-notes">${profile.notes.map((note) => `<li>${escapeHtml(note)}</li>`).join('')}</ul>` : ''}
|
|
215
|
+
</section>
|
|
216
|
+
`;
|
|
217
|
+
}
|
|
218
|
+
function renderFindingGroups(dossier) {
|
|
219
|
+
if (dossier.findings.length === 0) {
|
|
127
220
|
return `
|
|
128
|
-
<section class="
|
|
129
|
-
|
|
221
|
+
<section class="data-section" id="risks">
|
|
222
|
+
<div class="section-heading">
|
|
223
|
+
<h2>Risks and Limitations</h2>
|
|
224
|
+
<p>No blocking review finding was retained. Source verification is still recommended before engineering reuse.</p>
|
|
225
|
+
</div>
|
|
130
226
|
</section>
|
|
131
227
|
`;
|
|
132
228
|
}
|
|
133
229
|
return `
|
|
134
|
-
<section class="
|
|
135
|
-
|
|
230
|
+
<section class="data-section" id="risks">
|
|
231
|
+
<div class="section-heading">
|
|
232
|
+
<h2>Risks and Limitations</h2>
|
|
233
|
+
<p>Review gates and limitations are grouped for engineering decision-making, not as raw extraction logs.</p>
|
|
234
|
+
</div>
|
|
235
|
+
<div class="card-grid">
|
|
236
|
+
${dossier.findings.map((group) => `
|
|
237
|
+
<article class="insight-card ${toneClass(group.tone)}">
|
|
238
|
+
<h3>${escapeHtml(group.label)}</h3>
|
|
239
|
+
<ul>${group.items.map((item) => `<li>${escapeHtml(item)}</li>`).join('')}</ul>
|
|
240
|
+
</article>
|
|
241
|
+
`).join('')}
|
|
242
|
+
</div>
|
|
243
|
+
</section>
|
|
244
|
+
`;
|
|
245
|
+
}
|
|
246
|
+
function renderSourceEvidence(dossier) {
|
|
247
|
+
return `
|
|
248
|
+
<section class="data-section" id="source-evidence">
|
|
249
|
+
<div class="section-heading">
|
|
250
|
+
<h2>Source Evidence</h2>
|
|
251
|
+
<p>Page-level status with product-facing extraction posture. Technical model stages are kept in the processing audit below.</p>
|
|
252
|
+
</div>
|
|
253
|
+
<div class="evidence-grid">
|
|
254
|
+
${dossier.pageCards.map((card) => `
|
|
255
|
+
<article class="evidence-card ${toneClass(card.tone)}">
|
|
256
|
+
<div>
|
|
257
|
+
<strong>${escapeHtml(card.pageLabel)}</strong>
|
|
258
|
+
<span>${escapeHtml(card.parseStatus)} | ${escapeHtml(`${card.confidence}%`)}</span>
|
|
259
|
+
</div>
|
|
260
|
+
<h3>${escapeHtml(card.title)}</h3>
|
|
261
|
+
<p>${escapeHtml(sourceModeLabel(card.sourceHint))}</p>
|
|
262
|
+
${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>'}
|
|
263
|
+
${card.warnings.length > 0 ? `<small>Human verification recommended: ${escapeHtml(String(card.warnings.length))} retained warning(s).</small>` : ''}
|
|
264
|
+
</article>
|
|
265
|
+
`).join('')}
|
|
266
|
+
</div>
|
|
136
267
|
</section>
|
|
137
268
|
`;
|
|
138
269
|
}
|
|
270
|
+
function renderProcessingAudit(dossier, auditTables) {
|
|
271
|
+
const counts = dossier.pageCards.reduce((acc, page) => {
|
|
272
|
+
if (page.parseStatus === 'parsed')
|
|
273
|
+
acc.parsed += 1;
|
|
274
|
+
else if (page.parseStatus === 'partial')
|
|
275
|
+
acc.partial += 1;
|
|
276
|
+
else
|
|
277
|
+
acc.failed += 1;
|
|
278
|
+
return acc;
|
|
279
|
+
}, { parsed: 0, partial: 0, failed: 0 });
|
|
280
|
+
return `
|
|
281
|
+
<details class="audit-drawer" id="processing-audit">
|
|
282
|
+
<summary>
|
|
283
|
+
<span>
|
|
284
|
+
<strong>Processing Audit</strong>
|
|
285
|
+
<small>Model stages, page audit matrix, warnings, and operational details</small>
|
|
286
|
+
</span>
|
|
287
|
+
<span class="disclosure-hint">Show audit</span>
|
|
288
|
+
</summary>
|
|
289
|
+
<section class="audit-content">
|
|
290
|
+
<div class="outcome-strip" aria-label="Page extraction outcomes">
|
|
291
|
+
${dossier.pageCards.map((card) => `
|
|
292
|
+
<span class="outcome-cell ${toneClass(card.tone)}" title="${escapeHtml(`${card.pageLabel}: ${card.parseStatus}, ${card.confidence}% confidence`)}">${escapeHtml(card.pageLabel.replace(/^Page\s+/i, ''))}</span>
|
|
293
|
+
`).join('')}
|
|
294
|
+
</div>
|
|
295
|
+
<div class="legend-row">
|
|
296
|
+
<span><strong>${escapeHtml(counts.parsed)}</strong> parsed</span>
|
|
297
|
+
<span><strong>${escapeHtml(counts.partial)}</strong> partial</span>
|
|
298
|
+
<span><strong>${escapeHtml(counts.failed)}</strong> failed</span>
|
|
299
|
+
</div>
|
|
300
|
+
${auditTables.map((table) => renderTable(table, 'audit-table')).join('')}
|
|
301
|
+
<div class="audit-page-grid">
|
|
302
|
+
${dossier.pageCards.map((card) => `
|
|
303
|
+
<article class="audit-page-card ${toneClass(card.tone)}">
|
|
304
|
+
<div>
|
|
305
|
+
<strong>${escapeHtml(card.pageLabel)}</strong>
|
|
306
|
+
<span>${escapeHtml(card.classification)} | ${escapeHtml(card.parseStatus)} | ${escapeHtml(`${card.confidence}%`)}</span>
|
|
307
|
+
</div>
|
|
308
|
+
<div class="chip-row">
|
|
309
|
+
${card.sourceHint ? `<span class="chip">${escapeHtml(card.sourceHint)}</span>` : ''}
|
|
310
|
+
${card.sectionType ? `<span class="chip">${escapeHtml(card.sectionType)}</span>` : ''}
|
|
311
|
+
${card.scope ? `<span class="chip">${escapeHtml(card.scope)}</span>` : ''}
|
|
312
|
+
${(card.stageBadges ?? []).map((stage) => `<span class="chip stage-chip">${escapeHtml(stage)}</span>`).join('')}
|
|
313
|
+
</div>
|
|
314
|
+
${card.warnings.length > 0 ? `<ul>${card.warnings.map((warning) => `<li>${escapeHtml(warning)}</li>`).join('')}</ul>` : ''}
|
|
315
|
+
</article>
|
|
316
|
+
`).join('')}
|
|
317
|
+
</div>
|
|
318
|
+
</section>
|
|
319
|
+
</details>
|
|
320
|
+
`;
|
|
321
|
+
}
|
|
322
|
+
function renderActionBar() {
|
|
323
|
+
return `
|
|
324
|
+
<div class="action-bar" aria-label="Review actions">
|
|
325
|
+
<a href="#parameters" class="primary-action">Approve Extraction</a>
|
|
326
|
+
<a href="#risks">Flag for Review</a>
|
|
327
|
+
<a href="#source-evidence">Open Source Page</a>
|
|
328
|
+
<button type="button" onclick="window.print()">Export PDF</button>
|
|
329
|
+
<a href="#ground-model">Generate Ground Model</a>
|
|
330
|
+
<a href="#boreholes">Create Borehole Profile</a>
|
|
331
|
+
<a href="#source-evidence">Ask Geotech Agent</a>
|
|
332
|
+
</div>
|
|
333
|
+
`;
|
|
334
|
+
}
|
|
139
335
|
export function renderIngestDossierAsHtml(dossier) {
|
|
140
336
|
const generatedDate = new Date(dossier.generatedAt).toLocaleString('en-CA', {
|
|
141
337
|
dateStyle: 'medium',
|
|
142
338
|
timeStyle: 'short',
|
|
143
339
|
});
|
|
340
|
+
const auditTables = dossier.tables.filter((table) => isOperationalAuditTable(table.title));
|
|
341
|
+
const mainTables = dossier.tables.filter((table) => !isOperationalAuditTable(table.title));
|
|
342
|
+
const subtitle = /borehole/i.test(`${dossier.subtitle} ${dossier.title}`)
|
|
343
|
+
? 'Borehole Log Interpretation and Review Summary'
|
|
344
|
+
: 'AI-Assisted Geotechnical Review Summary';
|
|
144
345
|
return `<!doctype html>
|
|
145
346
|
<html lang="en">
|
|
146
347
|
<head>
|
|
147
348
|
<meta charset="utf-8" />
|
|
148
349
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
149
|
-
<title
|
|
350
|
+
<title>Geotechnical Intelligence Dossier - ${escapeHtml(dossier.title)}</title>
|
|
150
351
|
<style>
|
|
151
352
|
:root {
|
|
152
|
-
--
|
|
153
|
-
--
|
|
154
|
-
--
|
|
155
|
-
--
|
|
156
|
-
--muted
|
|
157
|
-
--
|
|
158
|
-
--primary: #
|
|
159
|
-
--
|
|
160
|
-
--
|
|
161
|
-
--
|
|
162
|
-
--
|
|
163
|
-
--
|
|
164
|
-
--
|
|
165
|
-
--
|
|
166
|
-
--
|
|
167
|
-
--
|
|
168
|
-
--
|
|
169
|
-
--
|
|
170
|
-
--
|
|
171
|
-
--warning: #b26b00;
|
|
172
|
-
--warning-soft: rgba(178, 107, 0, 0.14);
|
|
173
|
-
--danger: #ba2d3f;
|
|
174
|
-
--danger-soft: rgba(186, 45, 63, 0.12);
|
|
175
|
-
--neutral: #526072;
|
|
176
|
-
--neutral-soft: rgba(82, 96, 114, 0.12);
|
|
177
|
-
--shadow: none;
|
|
178
|
-
--radius: 8px;
|
|
179
|
-
--radius-sm: 6px;
|
|
353
|
+
--bg: #f6f8fb;
|
|
354
|
+
--surface: #ffffff;
|
|
355
|
+
--surface-soft: #eef3f8;
|
|
356
|
+
--text: #101828;
|
|
357
|
+
--muted: #667085;
|
|
358
|
+
--primary: #0b4f8a;
|
|
359
|
+
--primary-soft: #e6f0fa;
|
|
360
|
+
--success: #16875d;
|
|
361
|
+
--success-soft: #e4f6ee;
|
|
362
|
+
--warning: #b7791f;
|
|
363
|
+
--warning-soft: #fff3d6;
|
|
364
|
+
--danger: #b42318;
|
|
365
|
+
--danger-soft: #fde5e2;
|
|
366
|
+
--neutral: #475467;
|
|
367
|
+
--neutral-soft: #edf1f6;
|
|
368
|
+
--border: #d8e0ea;
|
|
369
|
+
--shadow: 0 18px 45px rgba(16, 24, 40, 0.08);
|
|
370
|
+
--radius: 16px;
|
|
371
|
+
--radius-sm: 12px;
|
|
180
372
|
}
|
|
181
373
|
|
|
182
374
|
* { box-sizing: border-box; }
|
|
183
|
-
|
|
375
|
+
html { scroll-behavior: smooth; }
|
|
184
376
|
html, body { margin: 0; min-height: 100%; }
|
|
185
377
|
|
|
186
378
|
body {
|
|
187
|
-
font-family: "Segoe UI Variable", "Segoe UI",
|
|
379
|
+
font-family: Inter, "Segoe UI Variable", "Segoe UI", Arial, sans-serif;
|
|
188
380
|
color: var(--text);
|
|
189
381
|
background: var(--bg);
|
|
190
382
|
font-variant-numeric: tabular-nums;
|
|
383
|
+
overflow-x: hidden;
|
|
191
384
|
}
|
|
192
385
|
|
|
193
|
-
.
|
|
194
|
-
|
|
386
|
+
.layout {
|
|
387
|
+
display: grid;
|
|
388
|
+
grid-template-columns: 280px minmax(0, 1fr);
|
|
389
|
+
gap: 28px;
|
|
390
|
+
width: 100%;
|
|
391
|
+
max-width: 1580px;
|
|
195
392
|
margin: 0 auto;
|
|
196
393
|
padding: 28px;
|
|
197
|
-
|
|
198
|
-
gap: 22px;
|
|
394
|
+
min-width: 0;
|
|
199
395
|
}
|
|
200
396
|
|
|
201
|
-
.
|
|
202
|
-
|
|
397
|
+
.sidebar {
|
|
398
|
+
position: sticky;
|
|
399
|
+
top: 28px;
|
|
400
|
+
align-self: start;
|
|
401
|
+
min-height: calc(100vh - 56px);
|
|
402
|
+
min-width: 0;
|
|
403
|
+
padding: 22px;
|
|
203
404
|
border: 1px solid var(--border);
|
|
204
405
|
border-radius: var(--radius);
|
|
406
|
+
background: #0b1220;
|
|
407
|
+
color: #f8fafc;
|
|
205
408
|
box-shadow: var(--shadow);
|
|
206
|
-
color: var(--card-foreground);
|
|
207
409
|
}
|
|
208
410
|
|
|
209
|
-
.
|
|
210
|
-
padding: 28px;
|
|
411
|
+
.brand {
|
|
211
412
|
display: grid;
|
|
212
|
-
gap:
|
|
413
|
+
gap: 8px;
|
|
414
|
+
padding-bottom: 22px;
|
|
415
|
+
border-bottom: 1px solid #263244;
|
|
416
|
+
margin-bottom: 18px;
|
|
213
417
|
}
|
|
214
418
|
|
|
215
|
-
.
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
419
|
+
.brand strong { font-size: 1.05rem; }
|
|
420
|
+
.brand span { color: #94a3b8; line-height: 1.5; font-size: 0.9rem; }
|
|
421
|
+
|
|
422
|
+
.nav-list {
|
|
423
|
+
display: grid;
|
|
424
|
+
gap: 6px;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
.nav-list a {
|
|
428
|
+
color: #dbeafe;
|
|
429
|
+
text-decoration: none;
|
|
430
|
+
padding: 10px 12px;
|
|
431
|
+
border-radius: 10px;
|
|
432
|
+
font-weight: 650;
|
|
433
|
+
font-size: 0.94rem;
|
|
434
|
+
overflow-wrap: anywhere;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
.nav-list a:hover, .nav-list a:focus {
|
|
438
|
+
background: #1f2937;
|
|
439
|
+
outline: none;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
.content {
|
|
443
|
+
display: grid;
|
|
444
|
+
gap: 26px;
|
|
445
|
+
min-width: 0;
|
|
446
|
+
max-width: 100%;
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
.hero {
|
|
450
|
+
padding: 32px;
|
|
451
|
+
border: 1px solid var(--border);
|
|
452
|
+
border-radius: 18px;
|
|
453
|
+
background: linear-gradient(135deg, #ffffff 0%, #eef6ff 100%);
|
|
454
|
+
box-shadow: var(--shadow);
|
|
455
|
+
display: grid;
|
|
456
|
+
gap: 24px;
|
|
457
|
+
min-width: 0;
|
|
221
458
|
}
|
|
222
459
|
|
|
223
460
|
.eyebrow {
|
|
224
461
|
display: inline-flex;
|
|
225
|
-
|
|
226
|
-
gap: 10px;
|
|
462
|
+
width: fit-content;
|
|
227
463
|
padding: 8px 12px;
|
|
228
|
-
border-radius:
|
|
229
|
-
background: var(--
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
font-size: 0.
|
|
233
|
-
letter-spacing: 0;
|
|
464
|
+
border-radius: 999px;
|
|
465
|
+
background: var(--primary-soft);
|
|
466
|
+
color: var(--primary);
|
|
467
|
+
font-weight: 800;
|
|
468
|
+
font-size: 0.78rem;
|
|
234
469
|
text-transform: uppercase;
|
|
235
470
|
}
|
|
236
471
|
|
|
237
|
-
.hero
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
472
|
+
.hero-main {
|
|
473
|
+
display: grid;
|
|
474
|
+
grid-template-columns: minmax(0, 1fr) 250px;
|
|
475
|
+
gap: 24px;
|
|
476
|
+
align-items: start;
|
|
477
|
+
min-width: 0;
|
|
242
478
|
}
|
|
243
479
|
|
|
244
|
-
.hero
|
|
245
|
-
|
|
480
|
+
.hero-main > div {
|
|
481
|
+
min-width: 0;
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
h1, h2, h3, p { margin: 0; }
|
|
485
|
+
|
|
486
|
+
h1 {
|
|
487
|
+
margin-top: 12px;
|
|
488
|
+
font-size: clamp(2rem, 4vw, 3rem);
|
|
489
|
+
line-height: 1.04;
|
|
490
|
+
max-width: 22ch;
|
|
491
|
+
overflow-wrap: anywhere;
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
.hero-subtitle {
|
|
495
|
+
margin-top: 12px;
|
|
496
|
+
color: var(--primary);
|
|
497
|
+
font-weight: 800;
|
|
498
|
+
font-size: 1.12rem;
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
.hero-summary {
|
|
502
|
+
margin-top: 14px;
|
|
246
503
|
color: var(--muted);
|
|
504
|
+
line-height: 1.7;
|
|
247
505
|
max-width: 92ch;
|
|
248
|
-
|
|
249
|
-
font-size: 1rem;
|
|
506
|
+
overflow-wrap: anywhere;
|
|
250
507
|
}
|
|
251
508
|
|
|
252
509
|
.hero-meta {
|
|
253
510
|
display: grid;
|
|
254
|
-
gap:
|
|
255
|
-
|
|
256
|
-
|
|
511
|
+
gap: 10px;
|
|
512
|
+
padding: 18px;
|
|
513
|
+
border: 1px solid var(--border);
|
|
514
|
+
border-radius: var(--radius-sm);
|
|
515
|
+
background: rgba(255, 255, 255, 0.72);
|
|
257
516
|
color: var(--muted);
|
|
258
|
-
font-size: 0.
|
|
517
|
+
font-size: 0.92rem;
|
|
518
|
+
min-width: 0;
|
|
519
|
+
overflow-wrap: anywhere;
|
|
259
520
|
}
|
|
260
521
|
|
|
261
|
-
.
|
|
522
|
+
.hero-meta strong { color: var(--text); }
|
|
523
|
+
|
|
524
|
+
.executive-grid, .metric-grid, .card-grid, .evidence-grid, .audit-page-grid, .footer-grid {
|
|
262
525
|
display: grid;
|
|
263
526
|
gap: 16px;
|
|
264
527
|
}
|
|
265
528
|
|
|
266
|
-
.
|
|
267
|
-
grid-template-columns: repeat(auto-fit, minmax(
|
|
529
|
+
.executive-grid {
|
|
530
|
+
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
|
|
268
531
|
}
|
|
269
532
|
|
|
270
|
-
.
|
|
271
|
-
border-radius: var(--radius-sm);
|
|
533
|
+
.fact-card, .metric-card, .insight-card, .evidence-card, .footer-card, .audit-page-card {
|
|
272
534
|
border: 1px solid var(--border);
|
|
273
|
-
|
|
274
|
-
background: var(--
|
|
275
|
-
|
|
276
|
-
gap: 6px;
|
|
535
|
+
border-radius: var(--radius);
|
|
536
|
+
background: var(--surface);
|
|
537
|
+
padding: 18px;
|
|
277
538
|
min-width: 0;
|
|
278
539
|
}
|
|
279
540
|
|
|
280
|
-
.
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
541
|
+
.fact-card {
|
|
542
|
+
display: grid;
|
|
543
|
+
gap: 8px;
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
.fact-card span, .metric-card span {
|
|
284
547
|
color: var(--muted);
|
|
548
|
+
font-size: 0.78rem;
|
|
549
|
+
text-transform: uppercase;
|
|
550
|
+
font-weight: 800;
|
|
285
551
|
}
|
|
286
552
|
|
|
287
|
-
.
|
|
553
|
+
.fact-card strong {
|
|
288
554
|
font-size: 1rem;
|
|
289
|
-
|
|
555
|
+
line-height: 1.45;
|
|
556
|
+
overflow-wrap: anywhere;
|
|
290
557
|
}
|
|
291
558
|
|
|
292
|
-
.metric-
|
|
293
|
-
|
|
559
|
+
.fact-card small, .metric-card small {
|
|
560
|
+
color: var(--muted);
|
|
561
|
+
line-height: 1.5;
|
|
294
562
|
}
|
|
295
563
|
|
|
296
|
-
.metric {
|
|
297
|
-
|
|
298
|
-
border-radius: var(--radius-sm);
|
|
299
|
-
border: 1px solid var(--border);
|
|
300
|
-
background: var(--panel);
|
|
301
|
-
display: grid;
|
|
302
|
-
gap: 8px;
|
|
303
|
-
min-width: 0;
|
|
564
|
+
.metric-grid {
|
|
565
|
+
grid-template-columns: repeat(auto-fit, minmax(165px, 1fr));
|
|
304
566
|
}
|
|
305
567
|
|
|
306
|
-
.metric-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
letter-spacing: 0;
|
|
310
|
-
color: var(--muted);
|
|
568
|
+
.metric-card {
|
|
569
|
+
display: grid;
|
|
570
|
+
gap: 10px;
|
|
311
571
|
}
|
|
312
572
|
|
|
313
|
-
.metric-
|
|
314
|
-
font-size: 1.
|
|
315
|
-
font-weight: 800;
|
|
573
|
+
.metric-card strong {
|
|
574
|
+
font-size: 1.65rem;
|
|
316
575
|
line-height: 1;
|
|
317
576
|
}
|
|
318
577
|
|
|
@@ -321,7 +580,7 @@ export function renderIngestDossierAsHtml(dossier) {
|
|
|
321
580
|
height: 8px;
|
|
322
581
|
overflow: hidden;
|
|
323
582
|
border-radius: 999px;
|
|
324
|
-
background: rgba(
|
|
583
|
+
background: rgba(102, 112, 133, 0.18);
|
|
325
584
|
}
|
|
326
585
|
|
|
327
586
|
.meter span {
|
|
@@ -329,286 +588,283 @@ export function renderIngestDossierAsHtml(dossier) {
|
|
|
329
588
|
height: 100%;
|
|
330
589
|
border-radius: inherit;
|
|
331
590
|
background: currentColor;
|
|
332
|
-
opacity: 0.
|
|
591
|
+
opacity: 0.75;
|
|
333
592
|
}
|
|
334
593
|
|
|
335
|
-
.
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
594
|
+
.tone-accent { background: var(--primary-soft); color: var(--primary); }
|
|
595
|
+
.tone-good { background: var(--success-soft); color: var(--success); }
|
|
596
|
+
.tone-warning { background: var(--warning-soft); color: var(--warning); }
|
|
597
|
+
.tone-danger { background: var(--danger-soft); color: var(--danger); }
|
|
598
|
+
.tone-neutral { background: var(--neutral-soft); color: var(--neutral); }
|
|
599
|
+
|
|
600
|
+
.action-bar {
|
|
601
|
+
display: flex;
|
|
602
|
+
flex-wrap: wrap;
|
|
603
|
+
gap: 10px;
|
|
339
604
|
}
|
|
340
605
|
|
|
341
|
-
.
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
606
|
+
.action-bar button, .action-bar a {
|
|
607
|
+
border: 1px solid var(--border);
|
|
608
|
+
border-radius: 12px;
|
|
609
|
+
background: var(--surface);
|
|
610
|
+
color: var(--text);
|
|
611
|
+
padding: 10px 13px;
|
|
612
|
+
font: inherit;
|
|
613
|
+
font-weight: 800;
|
|
614
|
+
text-decoration: none;
|
|
615
|
+
box-shadow: 0 1px 2px rgba(16, 24, 40, 0.04);
|
|
616
|
+
cursor: pointer;
|
|
617
|
+
overflow-wrap: anywhere;
|
|
618
|
+
}
|
|
346
619
|
|
|
347
|
-
.
|
|
348
|
-
|
|
620
|
+
.action-bar .primary-action {
|
|
621
|
+
background: var(--primary);
|
|
622
|
+
border-color: var(--primary);
|
|
623
|
+
color: #ffffff;
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
.data-section {
|
|
349
627
|
display: grid;
|
|
350
|
-
gap:
|
|
628
|
+
gap: 16px;
|
|
629
|
+
scroll-margin-top: 24px;
|
|
630
|
+
min-width: 0;
|
|
351
631
|
}
|
|
352
632
|
|
|
353
|
-
.section-
|
|
633
|
+
.section-heading {
|
|
354
634
|
display: grid;
|
|
355
635
|
gap: 8px;
|
|
356
636
|
}
|
|
357
637
|
|
|
358
|
-
.section-
|
|
359
|
-
|
|
360
|
-
|
|
638
|
+
.section-heading h2 {
|
|
639
|
+
font-size: 1.35rem;
|
|
640
|
+
letter-spacing: 0;
|
|
361
641
|
}
|
|
362
642
|
|
|
363
|
-
.section-
|
|
364
|
-
margin: 0;
|
|
643
|
+
.section-heading p {
|
|
365
644
|
color: var(--muted);
|
|
366
|
-
line-height: 1.
|
|
645
|
+
line-height: 1.65;
|
|
646
|
+
max-width: 96ch;
|
|
367
647
|
}
|
|
368
648
|
|
|
369
|
-
.
|
|
649
|
+
.card-grid {
|
|
370
650
|
grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
|
|
371
651
|
}
|
|
372
652
|
|
|
373
|
-
.
|
|
374
|
-
gap: 16px;
|
|
375
|
-
}
|
|
376
|
-
|
|
377
|
-
.outcome-strip {
|
|
653
|
+
.insight-card {
|
|
378
654
|
display: grid;
|
|
379
|
-
|
|
380
|
-
gap: 6px;
|
|
655
|
+
gap: 12px;
|
|
381
656
|
}
|
|
382
657
|
|
|
383
|
-
.
|
|
384
|
-
min-height: 30px;
|
|
385
|
-
border: 1px solid var(--border);
|
|
386
|
-
border-radius: var(--radius-sm);
|
|
387
|
-
display: inline-flex;
|
|
388
|
-
align-items: center;
|
|
389
|
-
justify-content: center;
|
|
390
|
-
font-size: 0.78rem;
|
|
391
|
-
font-weight: 700;
|
|
658
|
+
.insight-card h3 {
|
|
392
659
|
color: var(--text);
|
|
660
|
+
font-size: 1.06rem;
|
|
393
661
|
}
|
|
394
662
|
|
|
395
|
-
.
|
|
396
|
-
display: flex;
|
|
397
|
-
gap: 16px;
|
|
398
|
-
flex-wrap: wrap;
|
|
663
|
+
.insight-card p, .insight-card li, .evidence-card p, .evidence-card li {
|
|
399
664
|
color: var(--muted);
|
|
400
|
-
|
|
665
|
+
line-height: 1.62;
|
|
401
666
|
}
|
|
402
667
|
|
|
403
|
-
.
|
|
404
|
-
|
|
668
|
+
.insight-card ul, .evidence-card ul, .profile-notes, .footer-card ul, .audit-page-card ul {
|
|
669
|
+
margin: 0;
|
|
670
|
+
padding-left: 18px;
|
|
671
|
+
display: grid;
|
|
672
|
+
gap: 7px;
|
|
405
673
|
}
|
|
406
674
|
|
|
407
|
-
.
|
|
408
|
-
|
|
409
|
-
border-radius: var(--radius-sm);
|
|
675
|
+
.profile-shell, .table-shell {
|
|
676
|
+
overflow-x: auto;
|
|
410
677
|
border: 1px solid var(--border);
|
|
411
|
-
|
|
412
|
-
|
|
678
|
+
border-radius: var(--radius);
|
|
679
|
+
background: var(--surface);
|
|
680
|
+
min-width: 0;
|
|
681
|
+
max-width: 100%;
|
|
413
682
|
}
|
|
414
683
|
|
|
415
|
-
.
|
|
416
|
-
|
|
417
|
-
|
|
684
|
+
.borehole-profile {
|
|
685
|
+
display: block;
|
|
686
|
+
min-width: 760px;
|
|
687
|
+
width: 100%;
|
|
688
|
+
height: auto;
|
|
418
689
|
}
|
|
419
690
|
|
|
420
|
-
.
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
line-height: 1.55;
|
|
426
|
-
}
|
|
691
|
+
.profile-title { font: 800 15px Inter, Segoe UI, sans-serif; fill: #101828; }
|
|
692
|
+
.profile-label { font: 800 13px Inter, Segoe UI, sans-serif; fill: #101828; }
|
|
693
|
+
.profile-small { font: 11px Inter, Segoe UI, sans-serif; fill: #475467; }
|
|
694
|
+
.profile-axis { font: 11px Inter, Segoe UI, sans-serif; fill: #667085; }
|
|
695
|
+
.profile-water { font: 800 11px Inter, Segoe UI, sans-serif; fill: #0b4f8a; }
|
|
427
696
|
|
|
428
|
-
.
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
border: 1px solid var(--border);
|
|
432
|
-
background: var(--panel);
|
|
697
|
+
.profile-notes {
|
|
698
|
+
color: var(--muted);
|
|
699
|
+
line-height: 1.55;
|
|
433
700
|
}
|
|
434
701
|
|
|
435
702
|
table {
|
|
436
703
|
width: 100%;
|
|
437
704
|
border-collapse: collapse;
|
|
438
|
-
min-width:
|
|
705
|
+
min-width: 920px;
|
|
439
706
|
}
|
|
440
707
|
|
|
441
708
|
th, td {
|
|
442
|
-
padding:
|
|
443
|
-
border-bottom: 1px solid rgba(
|
|
709
|
+
padding: 13px 14px;
|
|
710
|
+
border-bottom: 1px solid rgba(11, 79, 138, 0.09);
|
|
444
711
|
text-align: left;
|
|
445
712
|
vertical-align: top;
|
|
446
713
|
font-size: 0.92rem;
|
|
447
|
-
line-height: 1.
|
|
714
|
+
line-height: 1.48;
|
|
448
715
|
overflow-wrap: anywhere;
|
|
449
716
|
}
|
|
450
717
|
|
|
451
718
|
th {
|
|
452
|
-
font-size: 0.78rem;
|
|
453
|
-
text-transform: uppercase;
|
|
454
|
-
letter-spacing: 0;
|
|
455
|
-
color: var(--muted);
|
|
456
|
-
background: var(--muted-surface);
|
|
457
719
|
position: sticky;
|
|
458
720
|
top: 0;
|
|
459
721
|
z-index: 1;
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
.empty-state {
|
|
463
|
-
padding: 18px;
|
|
464
|
-
border-radius: var(--radius-sm);
|
|
465
|
-
background: var(--muted-surface);
|
|
722
|
+
background: var(--surface-soft);
|
|
466
723
|
color: var(--muted);
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
}
|
|
473
|
-
|
|
474
|
-
.narrative {
|
|
475
|
-
padding: 22px;
|
|
476
|
-
border-radius: var(--radius-sm);
|
|
477
|
-
border: 1px solid var(--border);
|
|
478
|
-
background: var(--panel);
|
|
479
|
-
display: grid;
|
|
480
|
-
gap: 12px;
|
|
481
|
-
}
|
|
482
|
-
|
|
483
|
-
.narrative h3 {
|
|
484
|
-
margin: 0;
|
|
485
|
-
font-size: 1.04rem;
|
|
724
|
+
font-size: 0.76rem;
|
|
725
|
+
text-transform: uppercase;
|
|
726
|
+
font-weight: 900;
|
|
727
|
+
white-space: nowrap;
|
|
728
|
+
overflow-wrap: normal;
|
|
486
729
|
}
|
|
487
730
|
|
|
488
|
-
.
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
731
|
+
.status-pill, .chip {
|
|
732
|
+
display: inline-flex;
|
|
733
|
+
align-items: center;
|
|
734
|
+
width: fit-content;
|
|
735
|
+
padding: 6px 10px;
|
|
736
|
+
border-radius: 999px;
|
|
737
|
+
border: 1px solid rgba(102, 112, 133, 0.2);
|
|
738
|
+
font-size: 0.78rem;
|
|
739
|
+
font-weight: 800;
|
|
740
|
+
line-height: 1;
|
|
492
741
|
}
|
|
493
742
|
|
|
494
|
-
.
|
|
495
|
-
grid-template-columns: repeat(auto-fit, minmax(
|
|
743
|
+
.evidence-grid {
|
|
744
|
+
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
|
|
496
745
|
}
|
|
497
746
|
|
|
498
|
-
.
|
|
499
|
-
padding: 18px;
|
|
500
|
-
border-radius: var(--radius-sm);
|
|
501
|
-
border: 1px solid var(--border);
|
|
747
|
+
.evidence-card {
|
|
502
748
|
display: grid;
|
|
503
|
-
gap:
|
|
504
|
-
background: var(--panel);
|
|
505
|
-
min-width: 0;
|
|
749
|
+
gap: 11px;
|
|
506
750
|
}
|
|
507
751
|
|
|
508
|
-
.
|
|
752
|
+
.evidence-card > div {
|
|
509
753
|
display: flex;
|
|
510
754
|
justify-content: space-between;
|
|
511
755
|
gap: 12px;
|
|
512
|
-
|
|
513
|
-
|
|
756
|
+
color: var(--muted);
|
|
757
|
+
font-size: 0.86rem;
|
|
514
758
|
}
|
|
515
759
|
|
|
516
|
-
.
|
|
517
|
-
|
|
760
|
+
.evidence-card h3 {
|
|
761
|
+
color: var(--text);
|
|
762
|
+
font-size: 1.02rem;
|
|
518
763
|
}
|
|
519
764
|
|
|
520
|
-
.
|
|
765
|
+
.evidence-card small {
|
|
766
|
+
color: var(--warning);
|
|
767
|
+
font-weight: 800;
|
|
768
|
+
line-height: 1.45;
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
.empty-state {
|
|
772
|
+
padding: 18px;
|
|
773
|
+
border: 1px dashed var(--border);
|
|
774
|
+
border-radius: var(--radius);
|
|
775
|
+
background: var(--surface);
|
|
521
776
|
color: var(--muted);
|
|
522
|
-
|
|
777
|
+
line-height: 1.6;
|
|
523
778
|
}
|
|
524
779
|
|
|
525
|
-
.
|
|
526
|
-
margin:
|
|
527
|
-
|
|
780
|
+
.audit-drawer {
|
|
781
|
+
scroll-margin-top: 24px;
|
|
782
|
+
border: 1px solid var(--border);
|
|
783
|
+
border-radius: var(--radius);
|
|
784
|
+
background: var(--surface);
|
|
785
|
+
overflow: hidden;
|
|
528
786
|
}
|
|
529
787
|
|
|
530
|
-
.
|
|
788
|
+
.audit-drawer summary {
|
|
789
|
+
cursor: pointer;
|
|
790
|
+
list-style: none;
|
|
791
|
+
padding: 20px;
|
|
531
792
|
display: flex;
|
|
532
|
-
|
|
533
|
-
|
|
793
|
+
align-items: center;
|
|
794
|
+
justify-content: space-between;
|
|
795
|
+
gap: 16px;
|
|
534
796
|
}
|
|
535
797
|
|
|
536
|
-
.
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
border: 1px solid var(--border);
|
|
540
|
-
font-size: 0.78rem;
|
|
541
|
-
color: var(--muted);
|
|
542
|
-
background: var(--muted-surface);
|
|
543
|
-
line-height: 1.1;
|
|
544
|
-
}
|
|
798
|
+
.audit-drawer summary::-webkit-details-marker { display: none; }
|
|
799
|
+
.audit-drawer summary span:first-child { display: grid; gap: 5px; }
|
|
800
|
+
.audit-drawer small { color: var(--muted); }
|
|
545
801
|
|
|
546
|
-
.
|
|
547
|
-
border
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
802
|
+
.disclosure-hint {
|
|
803
|
+
border: 1px solid var(--border);
|
|
804
|
+
border-radius: 999px;
|
|
805
|
+
padding: 7px 11px;
|
|
806
|
+
color: var(--primary);
|
|
807
|
+
background: var(--primary-soft);
|
|
808
|
+
font-weight: 800;
|
|
809
|
+
white-space: nowrap;
|
|
551
810
|
}
|
|
552
811
|
|
|
553
|
-
.
|
|
554
|
-
|
|
555
|
-
padding
|
|
812
|
+
.audit-content {
|
|
813
|
+
border-top: 1px solid var(--border);
|
|
814
|
+
padding: 20px;
|
|
556
815
|
display: grid;
|
|
557
|
-
gap:
|
|
558
|
-
line-height: 1.55;
|
|
559
|
-
color: var(--muted);
|
|
816
|
+
gap: 20px;
|
|
560
817
|
}
|
|
561
818
|
|
|
562
|
-
.
|
|
563
|
-
|
|
564
|
-
|
|
819
|
+
.outcome-strip {
|
|
820
|
+
display: grid;
|
|
821
|
+
grid-template-columns: repeat(auto-fill, minmax(34px, 1fr));
|
|
822
|
+
gap: 6px;
|
|
565
823
|
}
|
|
566
824
|
|
|
567
|
-
.
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
825
|
+
.outcome-cell {
|
|
826
|
+
min-height: 30px;
|
|
827
|
+
border: 1px solid var(--border);
|
|
828
|
+
border-radius: 10px;
|
|
829
|
+
display: inline-flex;
|
|
830
|
+
align-items: center;
|
|
831
|
+
justify-content: center;
|
|
832
|
+
font-size: 0.78rem;
|
|
833
|
+
font-weight: 900;
|
|
572
834
|
}
|
|
573
835
|
|
|
574
|
-
.
|
|
575
|
-
display:
|
|
836
|
+
.legend-row, .chip-row {
|
|
837
|
+
display: flex;
|
|
838
|
+
flex-wrap: wrap;
|
|
839
|
+
gap: 10px;
|
|
840
|
+
color: var(--muted);
|
|
576
841
|
}
|
|
577
842
|
|
|
578
|
-
.
|
|
579
|
-
margin-top: 10px;
|
|
580
|
-
}
|
|
843
|
+
.legend-row strong { color: var(--text); }
|
|
581
844
|
|
|
582
|
-
.
|
|
583
|
-
|
|
845
|
+
.audit-page-grid {
|
|
846
|
+
grid-template-columns: repeat(auto-fit, minmax(270px, 1fr));
|
|
584
847
|
}
|
|
585
848
|
|
|
586
|
-
.
|
|
587
|
-
cursor: pointer;
|
|
849
|
+
.audit-page-card {
|
|
588
850
|
display: grid;
|
|
589
|
-
gap:
|
|
590
|
-
list-style: none;
|
|
851
|
+
gap: 12px;
|
|
591
852
|
}
|
|
592
853
|
|
|
593
|
-
.
|
|
594
|
-
display:
|
|
854
|
+
.audit-page-card > div:first-child {
|
|
855
|
+
display: grid;
|
|
856
|
+
gap: 4px;
|
|
595
857
|
}
|
|
596
858
|
|
|
597
|
-
.
|
|
598
|
-
width: fit-content;
|
|
599
|
-
padding: 6px 10px;
|
|
600
|
-
border-radius: var(--radius-sm);
|
|
601
|
-
border: 1px solid var(--border);
|
|
602
|
-
background: var(--muted-surface);
|
|
859
|
+
.audit-page-card > div:first-child span, .audit-page-card li {
|
|
603
860
|
color: var(--muted);
|
|
604
|
-
|
|
605
|
-
font-weight: 650;
|
|
861
|
+
line-height: 1.5;
|
|
606
862
|
}
|
|
607
863
|
|
|
608
|
-
.
|
|
609
|
-
color: var(--
|
|
610
|
-
|
|
611
|
-
|
|
864
|
+
.stage-chip {
|
|
865
|
+
color: var(--primary);
|
|
866
|
+
background: var(--primary-soft);
|
|
867
|
+
border-color: #b9d4ec;
|
|
612
868
|
}
|
|
613
869
|
|
|
614
870
|
.footer-grid {
|
|
@@ -616,171 +872,134 @@ export function renderIngestDossierAsHtml(dossier) {
|
|
|
616
872
|
}
|
|
617
873
|
|
|
618
874
|
.footer-card {
|
|
619
|
-
padding: 18px;
|
|
620
|
-
border-radius: var(--radius-sm);
|
|
621
|
-
border: 1px solid var(--border);
|
|
622
|
-
background: var(--panel);
|
|
623
875
|
display: grid;
|
|
624
|
-
gap:
|
|
625
|
-
}
|
|
626
|
-
|
|
627
|
-
.footer-card h3 {
|
|
628
|
-
margin: 0;
|
|
629
|
-
font-size: 0.98rem;
|
|
876
|
+
gap: 9px;
|
|
630
877
|
}
|
|
631
878
|
|
|
632
879
|
.footer-card p, .footer-card li {
|
|
633
|
-
margin: 0;
|
|
634
880
|
color: var(--muted);
|
|
635
|
-
line-height: 1.
|
|
881
|
+
line-height: 1.58;
|
|
636
882
|
}
|
|
637
883
|
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
884
|
+
@media (max-width: 980px) {
|
|
885
|
+
.layout { grid-template-columns: minmax(0, 1fr); padding: 18px; max-width: 100%; }
|
|
886
|
+
.sidebar { position: static; min-height: auto; }
|
|
887
|
+
.nav-list { grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); }
|
|
888
|
+
.hero-main { grid-template-columns: 1fr; }
|
|
643
889
|
}
|
|
644
890
|
|
|
645
|
-
@media (max-width:
|
|
646
|
-
.
|
|
891
|
+
@media (max-width: 620px) {
|
|
892
|
+
.layout { display: block; width: 100%; max-width: 390px; margin: 0; padding: 12px; }
|
|
893
|
+
.sidebar, .content, .hero, .data-section, .audit-drawer, .footer-grid {
|
|
894
|
+
width: 100%;
|
|
895
|
+
max-width: 100%;
|
|
896
|
+
}
|
|
897
|
+
.content { margin-top: 18px; }
|
|
647
898
|
.hero { padding: 22px; }
|
|
648
|
-
.
|
|
899
|
+
.executive-grid, .metric-grid, .card-grid, .evidence-grid, .footer-grid { grid-template-columns: 1fr; }
|
|
900
|
+
.nav-list { grid-template-columns: 1fr; }
|
|
901
|
+
h1 { font-size: 1.72rem; max-width: 100%; }
|
|
649
902
|
}
|
|
650
903
|
</style>
|
|
651
904
|
</head>
|
|
652
905
|
<body>
|
|
653
|
-
<
|
|
654
|
-
<
|
|
655
|
-
<div class="
|
|
656
|
-
<
|
|
657
|
-
|
|
658
|
-
<h1>${escapeHtml(dossier.title)}</h1>
|
|
659
|
-
<p>${escapeHtml(dossier.summary)}</p>
|
|
660
|
-
</div>
|
|
661
|
-
<div class="hero-meta">
|
|
662
|
-
<strong>${escapeHtml(dossier.subtitle)}</strong>
|
|
663
|
-
<span>${escapeHtml(dossier.sourceLabel)}</span>
|
|
664
|
-
<span>Generated ${escapeHtml(generatedDate)}</span>
|
|
665
|
-
<span>geotechCLI v${escapeHtml(GEOTECHCLI_VERSION)}</span>
|
|
666
|
-
</div>
|
|
906
|
+
<div class="layout">
|
|
907
|
+
<aside class="sidebar" aria-label="Dossier navigation">
|
|
908
|
+
<div class="brand">
|
|
909
|
+
<strong>Geotechnical Intelligence Dossier</strong>
|
|
910
|
+
<span>AI-assisted extraction, verification, and engineering interpretation from geotechnical reports.</span>
|
|
667
911
|
</div>
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
</
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
<
|
|
689
|
-
|
|
912
|
+
<nav class="nav-list">
|
|
913
|
+
<a href="#overview">Overview</a>
|
|
914
|
+
<a href="#ground-model">Ground Model</a>
|
|
915
|
+
<a href="#boreholes">Boreholes</a>
|
|
916
|
+
<a href="#parameters">Engineering Parameters</a>
|
|
917
|
+
<a href="#risks">Risks and Limitations</a>
|
|
918
|
+
<a href="#source-evidence">Source Evidence</a>
|
|
919
|
+
<a href="#processing-audit">Audit Trail</a>
|
|
920
|
+
</nav>
|
|
921
|
+
</aside>
|
|
922
|
+
|
|
923
|
+
<main class="content">
|
|
924
|
+
<header class="hero" id="overview">
|
|
925
|
+
<div class="hero-main">
|
|
926
|
+
<div>
|
|
927
|
+
<span class="eyebrow">Geotechnical Intelligence Dossier</span>
|
|
928
|
+
<h1>${escapeHtml(dossier.title)}</h1>
|
|
929
|
+
<p class="hero-subtitle">${escapeHtml(subtitle)}</p>
|
|
930
|
+
<p class="hero-summary">${escapeHtml(dossier.summary)}</p>
|
|
931
|
+
</div>
|
|
932
|
+
<div class="hero-meta">
|
|
933
|
+
<strong>${escapeHtml(dossier.sourceLabel)}</strong>
|
|
934
|
+
<span>Generated ${escapeHtml(generatedDate)}</span>
|
|
935
|
+
<span>geotechCLI v${escapeHtml(GEOTECHCLI_VERSION)}</span>
|
|
936
|
+
<span>${escapeHtml(dossier.documentType)}</span>
|
|
937
|
+
</div>
|
|
690
938
|
</div>
|
|
691
|
-
<div class="
|
|
692
|
-
${dossier.
|
|
693
|
-
<article class="
|
|
694
|
-
<
|
|
695
|
-
<
|
|
939
|
+
<div class="executive-grid">
|
|
940
|
+
${dossier.executiveItems.map((item) => `
|
|
941
|
+
<article class="fact-card ${toneClass(item.tone)}">
|
|
942
|
+
<span>${escapeHtml(item.label)}</span>
|
|
943
|
+
<strong>${escapeHtml(item.value)}</strong>
|
|
944
|
+
${item.detail ? `<small>${escapeHtml(item.detail)}</small>` : ''}
|
|
696
945
|
</article>
|
|
697
946
|
`).join('')}
|
|
698
947
|
</div>
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
${dossier.sections.length > 0 ? `
|
|
703
|
-
<section class="panel section-card" id="narrative">
|
|
704
|
-
<div class="section-head">
|
|
705
|
-
<h2>Engineering brief</h2>
|
|
706
|
-
<p>A concise narrative view designed for fast review before deeper page-by-page inspection.</p>
|
|
707
|
-
</div>
|
|
708
|
-
<div class="section-grid">
|
|
709
|
-
${dossier.sections.map((section) => `
|
|
710
|
-
<article class="narrative">
|
|
711
|
-
<h3>${escapeHtml(section.title)}</h3>
|
|
712
|
-
${section.paragraphs.map((paragraph) => `<p>${escapeHtml(paragraph)}</p>`).join('')}
|
|
713
|
-
</article>
|
|
714
|
-
`).join('')}
|
|
948
|
+
<div class="metric-grid">
|
|
949
|
+
${dossier.metrics.map(renderMetric).join('')}
|
|
715
950
|
</div>
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
${dossier.tables.map(renderTable).join('')}
|
|
951
|
+
${renderActionBar()}
|
|
952
|
+
</header>
|
|
720
953
|
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
<
|
|
725
|
-
<p>Page-level outcome, extraction posture, and the strongest retained signals for each page.</p>
|
|
954
|
+
<section class="data-section" id="ground-model">
|
|
955
|
+
<div class="section-heading">
|
|
956
|
+
<h2>Ground Model</h2>
|
|
957
|
+
<p>Decision-focused interpretation cards. The main view prioritizes engineering meaning, missing data, and verification needs.</p>
|
|
726
958
|
</div>
|
|
727
|
-
<div class="
|
|
728
|
-
${dossier.
|
|
729
|
-
<article class="
|
|
730
|
-
<div class="page-kicker">
|
|
731
|
-
<div>
|
|
732
|
-
<strong>${escapeHtml(card.pageLabel)}</strong>
|
|
733
|
-
<div><span>${escapeHtml(card.classification)}</span></div>
|
|
734
|
-
</div>
|
|
735
|
-
<span>${escapeHtml(card.parseStatus)} | ${escapeHtml(`${card.confidence}%`)}</span>
|
|
736
|
-
</div>
|
|
959
|
+
<div class="card-grid">
|
|
960
|
+
${dossier.insightCards.map((card) => `
|
|
961
|
+
<article class="insight-card ${toneClass(card.tone)}">
|
|
737
962
|
<h3>${escapeHtml(card.title)}</h3>
|
|
738
|
-
<
|
|
739
|
-
|
|
740
|
-
${card.sectionType ? `<span class="chip">${escapeHtml(card.sectionType)}</span>` : ''}
|
|
741
|
-
${card.scope ? `<span class="chip">${escapeHtml(card.scope)}</span>` : ''}
|
|
742
|
-
${(card.stageBadges ?? []).map((stage) => `<span class="chip stage-chip">${escapeHtml(stage)}</span>`).join('')}
|
|
743
|
-
</div>
|
|
744
|
-
${card.highlights.length > 0 ? `<ul>${card.highlights.map((item) => `<li>${escapeHtml(item)}</li>`).join('')}</ul>` : '<div class="empty-state">No strong structured highlights were retained for this page.</div>'}
|
|
745
|
-
${card.warnings.length > 0 ? `
|
|
746
|
-
<details>
|
|
747
|
-
<summary>Warnings (${card.warnings.length})</summary>
|
|
748
|
-
<ul>${card.warnings.map((warning) => `<li>${escapeHtml(warning)}</li>`).join('')}</ul>
|
|
749
|
-
</details>
|
|
750
|
-
` : ''}
|
|
963
|
+
<p>${escapeHtml(card.body)}</p>
|
|
964
|
+
${card.detail ? `<p>${escapeHtml(card.detail)}</p>` : ''}
|
|
751
965
|
</article>
|
|
752
966
|
`).join('')}
|
|
753
967
|
</div>
|
|
754
968
|
</section>
|
|
755
|
-
` : ''}
|
|
756
|
-
|
|
757
|
-
<section class="footer-grid">
|
|
758
|
-
${dossier.storedReview ? `
|
|
759
|
-
<article class="footer-card">
|
|
760
|
-
<h3>Stored review</h3>
|
|
761
|
-
<p>Project: ${escapeHtml(dossier.storedReview.projectId)}</p>
|
|
762
|
-
<p>Dataset: ${escapeHtml(dossier.storedReview.datasetName)}</p>
|
|
763
|
-
<p>Review ID: ${escapeHtml(dossier.storedReview.reviewId)}</p>
|
|
764
|
-
${dossier.storedReview.createdAt ? `<p>Created: ${escapeHtml(dossier.storedReview.createdAt)}</p>` : ''}
|
|
765
|
-
</article>
|
|
766
|
-
` : ''}
|
|
767
969
|
|
|
768
|
-
${dossier.
|
|
970
|
+
${renderBoreholeProfile(dossier.boreholeProfile)}
|
|
971
|
+
${renderTrustTable(dossier)}
|
|
972
|
+
${mainTables.map((table) => renderTable(table)).join('')}
|
|
973
|
+
${renderFindingGroups(dossier)}
|
|
974
|
+
${renderSourceEvidence(dossier)}
|
|
975
|
+
${renderProcessingAudit(dossier, auditTables)}
|
|
976
|
+
|
|
977
|
+
<section class="footer-grid">
|
|
978
|
+
${dossier.storedReview ? `
|
|
979
|
+
<article class="footer-card">
|
|
980
|
+
<h3>Stored review</h3>
|
|
981
|
+
<p>Project: ${escapeHtml(dossier.storedReview.projectId)}</p>
|
|
982
|
+
<p>Dataset: ${escapeHtml(dossier.storedReview.datasetName)}</p>
|
|
983
|
+
<p>Review ID: ${escapeHtml(dossier.storedReview.reviewId)}</p>
|
|
984
|
+
${dossier.storedReview.createdAt ? `<p>Created: ${escapeHtml(dossier.storedReview.createdAt)}</p>` : ''}
|
|
985
|
+
</article>
|
|
986
|
+
` : ''}
|
|
987
|
+
${dossier.approval ? `
|
|
988
|
+
<article class="footer-card">
|
|
989
|
+
<h3>Approval</h3>
|
|
990
|
+
<p>Dataset: ${escapeHtml(dossier.approval.datasetName)}</p>
|
|
991
|
+
<p>Approved: ${escapeHtml(dossier.approval.approvedAt)}</p>
|
|
992
|
+
<p>Approved by: ${escapeHtml(dossier.approval.approvedBy ?? 'Unspecified')}</p>
|
|
993
|
+
${dossier.approval.rationale ? `<p>${escapeHtml(dossier.approval.rationale)}</p>` : ''}
|
|
994
|
+
</article>
|
|
995
|
+
` : ''}
|
|
769
996
|
<article class="footer-card">
|
|
770
|
-
<h3>
|
|
771
|
-
<
|
|
772
|
-
<p>Approved: ${escapeHtml(dossier.approval.approvedAt)}</p>
|
|
773
|
-
<p>Approved by: ${escapeHtml(dossier.approval.approvedBy ?? 'Unspecified')}</p>
|
|
774
|
-
${dossier.approval.rationale ? `<p>${escapeHtml(dossier.approval.rationale)}</p>` : ''}
|
|
997
|
+
<h3>Source notes</h3>
|
|
998
|
+
<ul>${dossier.footerNotes.map((note) => `<li>${escapeHtml(note)}</li>`).join('')}</ul>
|
|
775
999
|
</article>
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
<h3>Source notes</h3>
|
|
780
|
-
<ul>${dossier.footerNotes.map((note) => `<li>${escapeHtml(note)}</li>`).join('')}</ul>
|
|
781
|
-
</article>
|
|
782
|
-
</section>
|
|
783
|
-
</main>
|
|
1000
|
+
</section>
|
|
1001
|
+
</main>
|
|
1002
|
+
</div>
|
|
784
1003
|
</body>
|
|
785
1004
|
</html>`;
|
|
786
1005
|
}
|