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