@geotechcli/core 0.4.53 → 0.4.54
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/evidence/evidence-ref.d.ts +8 -0
- package/dist/evidence/evidence-ref.d.ts.map +1 -1
- package/dist/evidence/evidence-ref.js.map +1 -1
- package/dist/evidence/index.d.ts +1 -1
- package/dist/evidence/index.d.ts.map +1 -1
- package/dist/evidence/index.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/ingest/geotech-document.d.ts +2 -0
- package/dist/ingest/geotech-document.d.ts.map +1 -1
- package/dist/ingest/geotech-document.js +17 -2
- package/dist/ingest/geotech-document.js.map +1 -1
- package/dist/ingest/geotech-extract.d.ts +2 -0
- package/dist/ingest/geotech-extract.d.ts.map +1 -1
- package/dist/ingest/geotech-extract.js +12 -0
- package/dist/ingest/geotech-extract.js.map +1 -1
- package/dist/ingest/page-evidence-cache.d.ts +4 -1
- package/dist/ingest/page-evidence-cache.d.ts.map +1 -1
- package/dist/ingest/page-evidence-cache.js +86 -1
- package/dist/ingest/page-evidence-cache.js.map +1 -1
- package/dist/llm/index.d.ts +1 -0
- package/dist/llm/index.d.ts.map +1 -1
- package/dist/llm/index.js +1 -0
- package/dist/llm/index.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 +596 -1309
- package/dist/report/html.js.map +1 -1
- package/dist/report/index.d.ts +1 -0
- package/dist/report/index.d.ts.map +1 -1
- package/dist/report/index.js +1 -0
- package/dist/report/index.js.map +1 -1
- package/dist/report/ingest-dossier.d.ts +5 -0
- package/dist/report/ingest-dossier.d.ts.map +1 -1
- package/dist/report/ingest-dossier.js +376 -2
- package/dist/report/ingest-dossier.js.map +1 -1
- package/dist/report/integrated-review-model.d.ts +147 -0
- package/dist/report/integrated-review-model.d.ts.map +1 -0
- package/dist/report/integrated-review-model.js +649 -0
- package/dist/report/integrated-review-model.js.map +1 -0
- package/package.json +1 -1
package/dist/report/html.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { GEOTECHCLI_VERSION } from '../meta/index.js';
|
|
2
|
+
import { buildIntegratedReviewModel, integratedBoreholeMaxDepth, integratedPercent, sourcePagesLabel, } from './integrated-review-model.js';
|
|
2
3
|
function escapeHtml(value) {
|
|
3
4
|
return String(value ?? '')
|
|
4
5
|
.replace(/&/g, '&')
|
|
@@ -84,6 +85,10 @@ function compactSvgText(value, maxLength = 28) {
|
|
|
84
85
|
}
|
|
85
86
|
return `${normalized.slice(0, Math.max(0, maxLength - 3)).trimEnd()}...`;
|
|
86
87
|
}
|
|
88
|
+
function uniqueStrings(values) {
|
|
89
|
+
return [...new Set(values.map((value) => value?.trim()).filter((value) => Boolean(value)))]
|
|
90
|
+
.filter(Boolean);
|
|
91
|
+
}
|
|
87
92
|
function layerMaterialKey(layer) {
|
|
88
93
|
const explicit = layer.materialKey?.trim();
|
|
89
94
|
if (explicit) {
|
|
@@ -210,7 +215,7 @@ function renderConfidenceBreakdown(dossier) {
|
|
|
210
215
|
<section class="data-section compact-section" id="trust-breakdown">
|
|
211
216
|
<div class="section-heading">
|
|
212
217
|
<h2>Trust breakdown</h2>
|
|
213
|
-
<p>Provider-neutral confidence split for review triage. Raw page/model details remain in
|
|
218
|
+
<p>Provider-neutral confidence split for review triage. Raw page/model details remain in Validation + JSON.</p>
|
|
214
219
|
</div>
|
|
215
220
|
<div class="metric-grid trust-breakdown-grid">
|
|
216
221
|
${dossier.confidenceBreakdown.map((item) => `
|
|
@@ -785,1362 +790,641 @@ function renderGroundModelVisualReview(model) {
|
|
|
785
790
|
</section>
|
|
786
791
|
`;
|
|
787
792
|
}
|
|
788
|
-
function
|
|
789
|
-
if (
|
|
790
|
-
return
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
793
|
+
function lightMaterialClass(className) {
|
|
794
|
+
if (className === 'fill')
|
|
795
|
+
return 'made';
|
|
796
|
+
if (className === 'clay')
|
|
797
|
+
return 'clay';
|
|
798
|
+
if (className === 'sand' || className === 'silt' || className === 'mixed')
|
|
799
|
+
return 'sand';
|
|
800
|
+
return 'gravel';
|
|
801
|
+
}
|
|
802
|
+
function lightMaterialColor(className) {
|
|
803
|
+
switch (lightMaterialClass(className)) {
|
|
804
|
+
case 'made': return '#8b5a2b';
|
|
805
|
+
case 'clay': return '#c98b67';
|
|
806
|
+
case 'sand': return '#f5d77b';
|
|
807
|
+
default: return '#a3a3a3';
|
|
798
808
|
}
|
|
809
|
+
}
|
|
810
|
+
function renderLightMetric(label, value, note, status = 'blue') {
|
|
799
811
|
return `
|
|
800
|
-
<
|
|
801
|
-
<div class="
|
|
802
|
-
|
|
803
|
-
|
|
812
|
+
<article class="metric">
|
|
813
|
+
<div class="label">${escapeHtml(label)}</div>
|
|
814
|
+
<div class="value status-${status}">${escapeHtml(value)}</div>
|
|
815
|
+
<div class="note">${escapeHtml(note)}</div>
|
|
816
|
+
</article>
|
|
817
|
+
`;
|
|
818
|
+
}
|
|
819
|
+
function renderLightEvidenceBox(input) {
|
|
820
|
+
const classes = [
|
|
821
|
+
'evidence-box',
|
|
822
|
+
input.status === 'review_recommended' ? 'warn' : '',
|
|
823
|
+
].filter(Boolean).join(' ');
|
|
824
|
+
const regionAttr = input.regionId ? ` data-region-id="${escapeHtml(input.regionId)}"` : '';
|
|
825
|
+
const text = input.text ? `<span class="layout-region-text">${escapeHtml(compactSvgText(input.text, 120))}</span>` : '';
|
|
826
|
+
return `<button class="${classes}" type="button" data-type="${escapeHtml(input.type)}" data-id="${escapeHtml(input.id)}" data-region-mode="${escapeHtml(input.mode)}"${regionAttr} aria-label="${escapeHtml(input.label)}" title="${escapeHtml(input.label)}" style="${escapeHtml(input.style)}">${text}</button>`;
|
|
827
|
+
}
|
|
828
|
+
function collectBoreholeEvidenceIds(borehole) {
|
|
829
|
+
return new Set([
|
|
830
|
+
...borehole.evidenceIds,
|
|
831
|
+
borehole.coordinateEvidenceId,
|
|
832
|
+
...borehole.strata.map((stratum) => stratum.evidenceId),
|
|
833
|
+
...borehole.spt.map((point) => point.evidenceId),
|
|
834
|
+
...borehole.groundwater.map((point) => point.evidenceId),
|
|
835
|
+
...borehole.parameters.map((parameter) => parameter.evidenceId),
|
|
836
|
+
].filter((id) => typeof id === 'string' && id.trim().length > 0));
|
|
837
|
+
}
|
|
838
|
+
function sourcePageScoreForBorehole(page, borehole) {
|
|
839
|
+
const evidenceIds = collectBoreholeEvidenceIds(borehole);
|
|
840
|
+
const pageMatch = borehole.sourcePages.includes(page.pageNumber) ? 3 : 0;
|
|
841
|
+
const regionMatches = page.regions.filter((region) => evidenceIds.has(region.evidenceId)).length * 5;
|
|
842
|
+
const textMatches = page.regions.filter((region) => region.text.toUpperCase().includes(borehole.id.toUpperCase())).length;
|
|
843
|
+
return pageMatch + regionMatches + textMatches;
|
|
844
|
+
}
|
|
845
|
+
function selectSourcePageForBorehole(borehole, model) {
|
|
846
|
+
const ranked = model.sourcePages
|
|
847
|
+
.filter((page) => page.regions.length > 0)
|
|
848
|
+
.map((page) => ({ page, score: sourcePageScoreForBorehole(page, borehole) }))
|
|
849
|
+
.filter((entry) => entry.score > 0)
|
|
850
|
+
.sort((left, right) => right.score - left.score || left.page.pageNumber - right.page.pageNumber);
|
|
851
|
+
return ranked[0]?.page;
|
|
852
|
+
}
|
|
853
|
+
function layoutRegionStyle(region, page) {
|
|
854
|
+
const [x1, y1, x2, y2] = region.bbox;
|
|
855
|
+
const left = (x1 / page.width) * 100;
|
|
856
|
+
const top = (y1 / page.height) * 100;
|
|
857
|
+
const width = ((x2 - x1) / page.width) * 100;
|
|
858
|
+
const height = ((y2 - y1) / page.height) * 100;
|
|
859
|
+
return `left:${left.toFixed(3)}%;top:${top.toFixed(3)}%;width:${Math.max(0.3, width).toFixed(3)}%;height:${Math.max(0.3, height).toFixed(3)}%`;
|
|
860
|
+
}
|
|
861
|
+
function renderLightLayoutSourcePage(page, borehole, model) {
|
|
862
|
+
const evidenceIds = collectBoreholeEvidenceIds(borehole);
|
|
863
|
+
const sortedRegions = [...page.regions].sort((left, right) => left.bbox[1] - right.bbox[1] || left.bbox[0] - right.bbox[0]);
|
|
864
|
+
return `
|
|
865
|
+
<div class="layout-source" aria-label="GLM-OCR source layout for ${escapeHtml(borehole.id)}">
|
|
866
|
+
<div class="layout-meta">
|
|
867
|
+
<span>Page ${escapeHtml(page.pageNumber)}</span>
|
|
868
|
+
<span>${escapeHtml(page.method)}</span>
|
|
869
|
+
<span>${escapeHtml(`${Math.round(page.width)} x ${Math.round(page.height)}`)}</span>
|
|
870
|
+
<span>${escapeHtml(page.sourcePath)}</span>
|
|
804
871
|
</div>
|
|
805
|
-
<div class="
|
|
806
|
-
${
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
872
|
+
<div class="layout-page" style="aspect-ratio:${escapeHtml(page.width.toFixed(0))}/${escapeHtml(page.height.toFixed(0))}">
|
|
873
|
+
<div class="layout-grid-label">GLM-OCR layout regions linked to ${escapeHtml(model.run.models.ocr)}</div>
|
|
874
|
+
${sortedRegions.map((region) => {
|
|
875
|
+
const linked = evidenceIds.has(region.evidenceId);
|
|
876
|
+
const label = `${region.type} evidence ${region.evidenceId}: ${region.text || region.label}`;
|
|
877
|
+
return renderLightEvidenceBox({
|
|
878
|
+
type: region.type,
|
|
879
|
+
id: region.evidenceId,
|
|
880
|
+
regionId: region.id,
|
|
881
|
+
label,
|
|
882
|
+
mode: 'layout',
|
|
883
|
+
status: linked ? region.status : 'review_recommended',
|
|
884
|
+
style: layoutRegionStyle(region, page),
|
|
885
|
+
text: region.text || region.label,
|
|
886
|
+
});
|
|
887
|
+
}).join('')}
|
|
812
888
|
</div>
|
|
813
|
-
</
|
|
889
|
+
</div>
|
|
814
890
|
`;
|
|
815
891
|
}
|
|
816
|
-
function
|
|
892
|
+
function renderLightSourcePage(borehole, model) {
|
|
893
|
+
if (!borehole) {
|
|
894
|
+
return '<div class="empty-light">No borehole object was available for source-page review.</div>';
|
|
895
|
+
}
|
|
896
|
+
const layoutPage = selectSourcePageForBorehole(borehole, model);
|
|
897
|
+
if (layoutPage) {
|
|
898
|
+
return renderLightLayoutSourcePage(layoutPage, borehole, model);
|
|
899
|
+
}
|
|
900
|
+
const maxDepth = integratedBoreholeMaxDepth(borehole);
|
|
901
|
+
const yPct = (depth) => 7 + (Math.max(0, Math.min(maxDepth, depth)) / maxDepth) * 91;
|
|
902
|
+
const ticks = Array.from({ length: 6 }, (_value, index) => Number((maxDepth * index / 5).toFixed(2)));
|
|
903
|
+
const coordinateValue = borehole.latitude != null && borehole.longitude != null
|
|
904
|
+
? `${borehole.latitude.toFixed(6)}, ${borehole.longitude.toFixed(6)}`
|
|
905
|
+
: borehole.easting != null && borehole.northing != null
|
|
906
|
+
? `E ${borehole.easting.toFixed(2)} / N ${borehole.northing.toFixed(2)}`
|
|
907
|
+
: 'not resolved';
|
|
817
908
|
return `
|
|
818
|
-
<
|
|
819
|
-
<div class="
|
|
820
|
-
|
|
821
|
-
<
|
|
909
|
+
<div class="mock-page" aria-label="Extracted source borehole log page">
|
|
910
|
+
<div class="page-title"><span>GROUND INVESTIGATION LOG</span><span>${escapeHtml(borehole.id)}</span></div>
|
|
911
|
+
<div class="page-meta">
|
|
912
|
+
<div>Project: ${escapeHtml(model.project.name)}</div><div>Exploratory Hole: ${escapeHtml(borehole.id)}</div>
|
|
913
|
+
<div>Ground Level: ${escapeHtml(borehole.groundLevel.toFixed(2))} ${escapeHtml(model.project.verticalDatum)}</div><div>Final Depth: ${escapeHtml(borehole.totalDepth.toFixed(2))} m bgl</div>
|
|
914
|
+
<div>${escapeHtml(coordinateValue)}</div><div>${escapeHtml(sourcePagesLabel(borehole.sourcePages))}</div>
|
|
822
915
|
</div>
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
<div class="
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
916
|
+
${renderLightEvidenceBox({ type: 'header', id: borehole.evidenceIds[0] ?? `${borehole.id}-id`, label: 'Borehole ID evidence', mode: 'reconstructed', style: 'left:63%;top:10.2%;width:22%;height:3.8%' })}
|
|
917
|
+
${renderLightEvidenceBox({ type: 'coordinates', id: borehole.coordinateEvidenceId ?? `${borehole.id}-coords`, label: 'Coordinate evidence', mode: 'reconstructed', style: 'left:5.5%;top:16.7%;width:83%;height:2.0%' })}
|
|
918
|
+
${renderLightEvidenceBox({ type: 'totalDepth', id: `${borehole.id}-td`, label: 'Total depth evidence', mode: 'reconstructed', style: 'left:50.5%;top:14.1%;width:38%;height:3.2%' })}
|
|
919
|
+
<div class="log-frame">
|
|
920
|
+
<div class="col c-depth"><div class="col-label">Depth<br>m</div></div>
|
|
921
|
+
<div class="col c-lith"><div class="col-label">Legend</div></div>
|
|
922
|
+
<div class="col c-desc"><div class="col-label">Strata description</div></div>
|
|
923
|
+
<div class="col c-sample"><div class="col-label">Sample</div></div>
|
|
924
|
+
<div class="col c-spt"><div class="col-label">SPT</div></div>
|
|
925
|
+
<div class="col c-water"><div class="col-label">Water</div></div>
|
|
926
|
+
<div class="col c-remarks"><div class="col-label">Remarks</div></div>
|
|
927
|
+
${ticks.map((tick) => `<div class="depth-tick" style="top:${yPct(tick).toFixed(3)}%"><span>${escapeHtml(tick.toFixed(tick % 1 === 0 ? 0 : 1))}</span></div>`).join('')}
|
|
928
|
+
${borehole.strata.map((stratum) => {
|
|
929
|
+
const top = yPct(stratum.top);
|
|
930
|
+
const base = yPct(stratum.base);
|
|
931
|
+
const height = Math.max(3.8, base - top);
|
|
932
|
+
const material = lightMaterialClass(stratum.className);
|
|
933
|
+
return `
|
|
934
|
+
<div class="hatch ${material}" style="top:${top.toFixed(3)}%;height:${height.toFixed(3)}%"></div>
|
|
935
|
+
<div class="layer-line ${stratum.status === 'accepted' ? '' : 'review'}" style="top:${base.toFixed(3)}%"></div>
|
|
936
|
+
<div class="desc-text" style="top:${(top + 1).toFixed(3)}%;height:${Math.max(4, height - 1).toFixed(3)}%">${escapeHtml(stratum.description)}</div>
|
|
937
|
+
${renderLightEvidenceBox({ type: 'strata', id: stratum.evidenceId, label: `${stratum.name} evidence`, mode: 'reconstructed', status: stratum.status, style: `left:23%;top:${(top + 0.7).toFixed(3)}%;width:34%;height:${Math.max(4.2, Math.min(8, height - 1)).toFixed(3)}%` })}
|
|
938
|
+
`;
|
|
939
|
+
}).join('')}
|
|
940
|
+
${borehole.spt.map((spt) => {
|
|
941
|
+
const y = yPct(spt.depth);
|
|
942
|
+
return `
|
|
943
|
+
<div class="spt-text" style="top:${(y - 0.7).toFixed(3)}%">${escapeHtml(spt.label.replace(/^N/i, 'N='))}</div>
|
|
944
|
+
${renderLightEvidenceBox({ type: 'spt', id: spt.evidenceId, label: `${spt.label} evidence`, mode: 'reconstructed', style: `left:72.5%;top:${(y - 1.2).toFixed(3)}%;width:10.5%;height:2.6%` })}
|
|
945
|
+
`;
|
|
946
|
+
}).join('')}
|
|
947
|
+
${borehole.groundwater.map((water) => {
|
|
948
|
+
const y = yPct(water.depth);
|
|
949
|
+
return `
|
|
950
|
+
<div class="water-text" style="top:${(y - 0.9).toFixed(3)}%">GW</div>
|
|
951
|
+
${renderLightEvidenceBox({ type: 'water', id: water.evidenceId, label: 'Groundwater symbol evidence', mode: 'reconstructed', status: 'review_recommended', style: `left:84.7%;top:${(y - 1.3).toFixed(3)}%;width:6.8%;height:3.0%` })}
|
|
952
|
+
`;
|
|
953
|
+
}).join('')}
|
|
848
954
|
</div>
|
|
849
|
-
</
|
|
955
|
+
</div>
|
|
850
956
|
`;
|
|
851
957
|
}
|
|
852
|
-
function
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
}, { parsed: 0, partial: 0, failed: 0 });
|
|
958
|
+
function renderLightStripLog(borehole, model) {
|
|
959
|
+
if (!borehole) {
|
|
960
|
+
return '<div class="empty-light">No borehole object was available for strip-log rendering.</div>';
|
|
961
|
+
}
|
|
962
|
+
const maxDepth = integratedBoreholeMaxDepth(borehole);
|
|
963
|
+
const top = 82;
|
|
964
|
+
const height = 560;
|
|
965
|
+
const yForDepth = (depth) => top + (Math.max(0, Math.min(maxDepth, depth)) / maxDepth) * height;
|
|
966
|
+
const ticks = Array.from({ length: 6 }, (_value, index) => Number((maxDepth * index / 5).toFixed(2)));
|
|
862
967
|
return `
|
|
863
|
-
<
|
|
864
|
-
<
|
|
865
|
-
<
|
|
866
|
-
<
|
|
867
|
-
<
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
<
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
<
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
</
|
|
882
|
-
${
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
968
|
+
<div class="svg-wrap">
|
|
969
|
+
<svg width="430" height="720" viewBox="0 0 430 720" role="img" aria-label="Rendered borehole log">
|
|
970
|
+
<defs>
|
|
971
|
+
<pattern id="strip-made" width="12" height="12" patternUnits="userSpaceOnUse"><rect width="12" height="12" fill="#8b5a2b" opacity=".75"/><path d="M0 10 L12 0" stroke="#6b3f1d" stroke-width="2" opacity=".5"/></pattern>
|
|
972
|
+
<pattern id="strip-clay" width="8" height="8" patternUnits="userSpaceOnUse" patternTransform="rotate(45)"><rect width="8" height="8" fill="#f3d3c1"/><line x1="0" y1="0" x2="0" y2="8" stroke="#8a4d33" stroke-width="2" opacity=".55"/></pattern>
|
|
973
|
+
<pattern id="strip-sand" width="10" height="10" patternUnits="userSpaceOnUse"><rect width="10" height="10" fill="#f5d77b"/><circle cx="2" cy="2" r="1.2" fill="#8a6a10"/><circle cx="7" cy="6" r="1.2" fill="#8a6a10"/></pattern>
|
|
974
|
+
<pattern id="strip-gravel" width="14" height="14" patternUnits="userSpaceOnUse"><rect width="14" height="14" fill="#d4d4d4"/><circle cx="4" cy="5" r="2" fill="#64748b"/><circle cx="10" cy="10" r="2.4" fill="#64748b"/></pattern>
|
|
975
|
+
</defs>
|
|
976
|
+
<rect x="0" y="0" width="430" height="720" rx="14" fill="#ffffff"/>
|
|
977
|
+
<text x="20" y="32" font-size="18" font-weight="850" fill="#111827">${escapeHtml(borehole.id)}</text>
|
|
978
|
+
<text x="20" y="52" font-size="12" fill="#64748b">GL ${escapeHtml(borehole.groundLevel.toFixed(2))} ${escapeHtml(model.project.verticalDatum)} | TD ${escapeHtml(borehole.totalDepth.toFixed(2))} m bgl | ${escapeHtml(sourcePagesLabel(borehole.sourcePages))}</text>
|
|
979
|
+
<line x1="72" y1="${top}" x2="72" y2="${top + height}" stroke="#334155" stroke-width="2"/>
|
|
980
|
+
<line x1="260" y1="${top}" x2="260" y2="${top + height}" stroke="#334155" stroke-width="2"/>
|
|
981
|
+
<line x1="320" y1="${top}" x2="320" y2="${top + height}" stroke="#334155" stroke-width="2"/>
|
|
982
|
+
<line x1="388" y1="${top}" x2="388" y2="${top + height}" stroke="#334155" stroke-width="2"/>
|
|
983
|
+
<text x="24" y="78" font-size="11" font-weight="850" fill="#334155">Depth</text>
|
|
984
|
+
<text x="118" y="78" font-size="11" font-weight="850" fill="#334155">Strata</text>
|
|
985
|
+
<text x="275" y="78" font-size="11" font-weight="850" fill="#334155">SPT N</text>
|
|
986
|
+
<text x="335" y="78" font-size="11" font-weight="850" fill="#334155">Water</text>
|
|
987
|
+
${ticks.map((tick) => {
|
|
988
|
+
const y = yForDepth(tick);
|
|
989
|
+
return `<text x="30" y="${(y + 4).toFixed(2)}" font-size="10" fill="#475569">${escapeHtml(tick.toFixed(tick % 1 === 0 ? 0 : 1))}</text><line x1="52" y1="${y.toFixed(2)}" x2="388" y2="${y.toFixed(2)}" stroke="#e2e8f0"/>`;
|
|
990
|
+
}).join('')}
|
|
991
|
+
${borehole.strata.map((stratum) => {
|
|
992
|
+
const y = yForDepth(stratum.top);
|
|
993
|
+
const h = Math.max(2, yForDepth(stratum.base) - y);
|
|
994
|
+
const material = lightMaterialClass(stratum.className);
|
|
995
|
+
return `
|
|
996
|
+
<g class="log-highlight" data-id="${escapeHtml(stratum.evidenceId)}">
|
|
997
|
+
<rect x="72" y="${y.toFixed(2)}" width="188" height="${h.toFixed(2)}" fill="url(#strip-${material})" stroke="${stratum.status === 'accepted' ? '#111827' : '#b7791f'}" stroke-width="1.5" ${stratum.status === 'accepted' ? '' : 'stroke-dasharray="7 5"'}/>
|
|
998
|
+
<text x="84" y="${(y + Math.min(30, h / 2)).toFixed(2)}" font-size="11" fill="#111827">${escapeHtml(compactSvgText(stratum.name, 20))}</text>
|
|
999
|
+
<text x="84" y="${(y + Math.min(46, h / 2 + 16)).toFixed(2)}" font-size="10" fill="#475569">${escapeHtml(stratum.top.toFixed(2))} - ${escapeHtml(stratum.base.toFixed(2))} m</text>
|
|
1000
|
+
</g>
|
|
1001
|
+
`;
|
|
1002
|
+
}).join('')}
|
|
1003
|
+
${borehole.spt.map((spt) => {
|
|
1004
|
+
const y = yForDepth(spt.depth);
|
|
1005
|
+
return `<g class="log-highlight" data-id="${escapeHtml(spt.evidenceId)}"><circle cx="290" cy="${y.toFixed(2)}" r="9" fill="#dbeafe" stroke="#1d4ed8"/><text x="306" y="${(y + 4).toFixed(2)}" font-size="12" fill="#111827">${escapeHtml(spt.label.replace(/^N/i, ''))}</text></g>`;
|
|
1006
|
+
}).join('')}
|
|
1007
|
+
${borehole.groundwater.map((water) => {
|
|
1008
|
+
const y = yForDepth(water.depth);
|
|
1009
|
+
return `<g class="log-highlight" data-id="${escapeHtml(water.evidenceId)}"><path d="M346 ${y - 6} l18 0 l-9 15 z" fill="#bae6fd" stroke="#0369a1" stroke-width="2"/><text x="334" y="${y + 30}" font-size="10" fill="#0369a1">${escapeHtml(water.depth.toFixed(2))} m</text></g>`;
|
|
1010
|
+
}).join('')}
|
|
1011
|
+
<rect x="20" y="668" width="390" height="34" rx="10" fill="#f8fafc" stroke="#e2e8f0"/>
|
|
1012
|
+
<text x="34" y="689" font-size="11" fill="#475569">Rendered from validated JSON; dashed boundaries require review.</text>
|
|
1013
|
+
</svg>
|
|
1014
|
+
</div>
|
|
902
1015
|
`;
|
|
903
1016
|
}
|
|
904
|
-
function
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
const
|
|
1017
|
+
function renderLightFields(borehole, model) {
|
|
1018
|
+
if (!borehole) {
|
|
1019
|
+
return '<div class="empty-light">No extracted fields were available.</div>';
|
|
1020
|
+
}
|
|
1021
|
+
const fields = [
|
|
1022
|
+
{ id: borehole.evidenceIds[0] ?? `${borehole.id}-id`, type: 'header', title: 'Borehole ID', value: borehole.id, conf: borehole.confidence, status: 'accepted', engine: 'GLM-OCR + GLM-5.1' },
|
|
1023
|
+
{ id: borehole.coordinateEvidenceId ?? `${borehole.id}-coords`, type: 'header', title: 'Coordinates', value: borehole.easting != null && borehole.northing != null ? `E ${borehole.easting.toFixed(2)} / N ${borehole.northing.toFixed(2)} (${model.project.inputCrs})` : 'not resolved', conf: borehole.confidence, status: 'accepted', engine: 'OCR + CRS validator' },
|
|
1024
|
+
{ id: `${borehole.id}-td`, type: 'header', title: 'Final depth', value: `${borehole.totalDepth.toFixed(2)} m bgl`, conf: borehole.confidence, status: 'accepted', engine: 'GLM-OCR' },
|
|
1025
|
+
...borehole.strata.map((stratum, index) => ({ id: stratum.evidenceId, type: 'strata', title: `Stratum ${index + 1}`, value: `${stratum.top.toFixed(2)}-${stratum.base.toFixed(2)} m | ${stratum.description}`, conf: stratum.confidence, status: stratum.status === 'accepted' ? 'accepted' : 'review', engine: 'OCR + depth map' })),
|
|
1026
|
+
...borehole.spt.map((spt) => ({ id: spt.evidenceId, type: 'spt', title: 'SPT', value: `${spt.label} at ${spt.depth.toFixed(2)} m`, conf: spt.confidence, status: 'accepted', engine: 'OCR + geometry' })),
|
|
1027
|
+
...borehole.groundwater.map((water) => ({ id: water.evidenceId, type: 'water', title: 'Groundwater', value: `${water.label} at ${water.depth.toFixed(2)} m`, conf: water.confidence, status: 'review', engine: 'vision symbol check' })),
|
|
1028
|
+
...borehole.parameters.map((parameter) => ({
|
|
1029
|
+
id: parameter.evidenceId,
|
|
1030
|
+
type: 'parameter',
|
|
1031
|
+
title: parameter.name,
|
|
1032
|
+
value: `${parameter.value}${parameter.depth != null ? ` at ${parameter.depth.toFixed(2)} m` : ''}`,
|
|
1033
|
+
conf: parameter.confidence,
|
|
1034
|
+
status: 'accepted',
|
|
1035
|
+
engine: 'OCR + lab/index parser',
|
|
1036
|
+
})),
|
|
1037
|
+
];
|
|
909
1038
|
return `
|
|
910
|
-
<div class="
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
1039
|
+
<div class="fields">
|
|
1040
|
+
${fields.map((field) => {
|
|
1041
|
+
const barClass = field.conf >= 0.85 ? '' : field.conf >= 0.7 ? 'warn' : 'bad';
|
|
1042
|
+
return `
|
|
1043
|
+
<button type="button" class="field-card" data-id="${escapeHtml(field.id)}" data-type="${escapeHtml(field.type)}">
|
|
1044
|
+
<div class="field-title"><span>${escapeHtml(field.title)}</span><span class="pill ${field.status === 'accepted' ? 'good' : 'warn'}">${escapeHtml(field.status)}</span></div>
|
|
1045
|
+
<div class="field-value">${escapeHtml(field.value)}</div>
|
|
1046
|
+
<div class="field-meta"><span>${escapeHtml(integratedPercent(field.conf))} confidence</span><span>${escapeHtml(field.id)}</span><span>${escapeHtml(field.engine)}</span></div>
|
|
1047
|
+
<div class="confidence"><span class="${barClass}" style="width:${escapeHtml(Math.round(field.conf * 100))}%"></span></div>
|
|
1048
|
+
</button>
|
|
1049
|
+
`;
|
|
1050
|
+
}).join('')}
|
|
1051
|
+
</div>
|
|
1052
|
+
`;
|
|
1053
|
+
}
|
|
1054
|
+
function renderLightMap(model) {
|
|
1055
|
+
const points = model.boreholes.filter((borehole) => borehole.easting != null && borehole.northing != null);
|
|
1056
|
+
if (points.length === 0) {
|
|
1057
|
+
return '<div class="empty-light">No validated borehole coordinates were available for map rendering.</div>';
|
|
1058
|
+
}
|
|
1059
|
+
const width = 860;
|
|
1060
|
+
const height = 420;
|
|
1061
|
+
const pad = 52;
|
|
1062
|
+
const xs = points.map((point) => point.easting ?? 0);
|
|
1063
|
+
const ys = points.map((point) => point.northing ?? 0);
|
|
1064
|
+
const minX = Math.min(...xs);
|
|
1065
|
+
const maxX = Math.max(...xs);
|
|
1066
|
+
const minY = Math.min(...ys);
|
|
1067
|
+
const maxY = Math.max(...ys);
|
|
1068
|
+
const xFor = (x) => pad + ((x - minX) / Math.max(1, maxX - minX)) * (width - pad * 2);
|
|
1069
|
+
const yFor = (y) => height - pad - ((y - minY) / Math.max(1, maxY - minY)) * (height - pad * 2);
|
|
1070
|
+
return `
|
|
1071
|
+
<div class="map-canvas">
|
|
1072
|
+
<svg viewBox="0 0 ${width} ${height}" style="width:100%;height:auto;display:block" role="img" aria-label="Borehole coordinate map">
|
|
1073
|
+
<rect x="0" y="0" width="${width}" height="${height}" rx="14" fill="#f8fafc"/>
|
|
1074
|
+
${Array.from({ length: 9 }, (_value, index) => `<line x1="${pad + index * ((width - pad * 2) / 8)}" y1="${pad}" x2="${pad + index * ((width - pad * 2) / 8)}" y2="${height - pad}" stroke="#dbe4f0"/><line x1="${pad}" y1="${pad + index * ((height - pad * 2) / 8)}" x2="${width - pad}" y2="${pad + index * ((height - pad * 2) / 8)}" stroke="#dbe4f0"/>`).join('')}
|
|
1075
|
+
<polyline points="${points.map((point) => `${xFor(point.easting ?? 0).toFixed(2)},${yFor(point.northing ?? 0).toFixed(2)}`).join(' ')}" fill="none" stroke="#2563eb" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
|
|
1076
|
+
${points.map((point, index) => {
|
|
1077
|
+
const x = xFor(point.easting ?? 0);
|
|
1078
|
+
const y = yFor(point.northing ?? 0);
|
|
1079
|
+
return `<g><circle cx="${x.toFixed(2)}" cy="${y.toFixed(2)}" r="${index === 0 ? 11 : 9}" fill="#2563eb" stroke="#ffffff" stroke-width="4"/><text x="${(x + 14).toFixed(2)}" y="${(y - 12).toFixed(2)}" font-size="12" font-weight="850" fill="#172033">${escapeHtml(point.id)}</text></g>`;
|
|
1080
|
+
}).join('')}
|
|
1081
|
+
<text x="${pad}" y="${height - 18}" font-size="12" fill="#64748b">Source coordinates retained in ${escapeHtml(model.project.inputCrs)}. Map-ready coordinates render only after CRS validation.</text>
|
|
1082
|
+
</svg>
|
|
1083
|
+
</div>
|
|
1084
|
+
`;
|
|
1085
|
+
}
|
|
1086
|
+
function renderLightSelectedBorehole(borehole, model) {
|
|
1087
|
+
if (!borehole) {
|
|
1088
|
+
return '<div class="empty-light">No selected borehole was available.</div>';
|
|
1089
|
+
}
|
|
1090
|
+
return `
|
|
1091
|
+
<div class="selected-panel">
|
|
1092
|
+
<strong>${escapeHtml(borehole.id)}</strong>
|
|
1093
|
+
<div class="selected-grid">
|
|
1094
|
+
<div><b>Source</b><br>${escapeHtml(sourcePagesLabel(borehole.sourcePages))}</div><div><b>Confidence</b><br>${escapeHtml(integratedPercent(borehole.confidence))}</div>
|
|
1095
|
+
<div><b>Easting</b><br>${escapeHtml(borehole.easting != null ? borehole.easting.toFixed(2) : '-')}</div><div><b>Northing</b><br>${escapeHtml(borehole.northing != null ? borehole.northing.toFixed(2) : '-')}</div>
|
|
1096
|
+
<div><b>Latitude</b><br>${escapeHtml(borehole.latitude != null ? borehole.latitude.toFixed(6) : '-')}</div><div><b>Longitude</b><br>${escapeHtml(borehole.longitude != null ? borehole.longitude.toFixed(6) : '-')}</div>
|
|
1097
|
+
<div><b>GL</b><br>${escapeHtml(borehole.groundLevel.toFixed(2))} ${escapeHtml(model.project.verticalDatum)}</div><div><b>Total depth</b><br>${escapeHtml(borehole.totalDepth.toFixed(2))} m</div>
|
|
922
1098
|
</div>
|
|
923
1099
|
</div>
|
|
1100
|
+
<div class="crs-card">
|
|
1101
|
+
<div class="crs-box"><div class="k">Project</div><div class="v">${escapeHtml(model.project.name)}</div><div class="s">Document: ${escapeHtml(model.run.document)}</div></div>
|
|
1102
|
+
<div class="crs-box"><div class="k">Input coordinates</div><div class="v">${escapeHtml(model.project.inputCrs)}</div><div class="s">Source coordinate system retained for checking.</div></div>
|
|
1103
|
+
<div class="crs-box"><div class="k">Display coordinates</div><div class="v">${escapeHtml(model.project.displayCrs)}</div><div class="s">Map rendering is gated by CRS validation.</div></div>
|
|
1104
|
+
<div class="crs-box"><div class="k">Transform engine</div><div class="v">${escapeHtml(model.project.crsTransformEngine)}</div><div class="s">Production transform must run locally or in trusted infrastructure.</div></div>
|
|
1105
|
+
</div>
|
|
924
1106
|
`;
|
|
925
1107
|
}
|
|
926
|
-
function
|
|
1108
|
+
function renderLightCrossSection(model) {
|
|
1109
|
+
const boreholes = model.boreholes.filter((borehole) => borehole.strata.length > 0);
|
|
1110
|
+
if (boreholes.length < 2) {
|
|
1111
|
+
return '<div class="empty-light">A-A section needs at least two boreholes with retained stratum boundaries.</div>';
|
|
1112
|
+
}
|
|
1113
|
+
const maxDepth = Math.max(...boreholes.map(integratedBoreholeMaxDepth));
|
|
1114
|
+
const width = Math.max(980, 220 + boreholes.length * 210);
|
|
1115
|
+
const height = 590;
|
|
1116
|
+
const chart = { x0: 86, y0: 86, w: width - 170, h: 360 };
|
|
1117
|
+
const xFor = (index) => chart.x0 + (index / Math.max(1, boreholes.length - 1)) * chart.w;
|
|
1118
|
+
const yForDepth = (depth) => chart.y0 + (Math.max(0, Math.min(maxDepth, depth)) / maxDepth) * chart.h;
|
|
1119
|
+
const maxLayerCount = Math.max(...boreholes.map((borehole) => borehole.strata.length));
|
|
927
1120
|
return `
|
|
928
|
-
<div class="
|
|
929
|
-
<
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
1121
|
+
<div class="cross-section-wrap">
|
|
1122
|
+
<svg width="${width}" height="${height}" viewBox="0 0 ${width} ${height}" role="img" aria-label="Professional stratigraphic cross-section">
|
|
1123
|
+
<defs>
|
|
1124
|
+
<pattern id="aa-made" width="14" height="14" patternUnits="userSpaceOnUse"><rect width="14" height="14" fill="#8b5a2b" opacity=".78"/><path d="M0 12 L14 0" stroke="#6b3f1d" stroke-width="2" opacity=".45"/></pattern>
|
|
1125
|
+
<pattern id="aa-clay" width="10" height="10" patternUnits="userSpaceOnUse" patternTransform="rotate(45)"><rect width="10" height="10" fill="#c98b67" opacity=".72"/><line x1="0" y1="0" x2="0" y2="10" stroke="#8a4d33" stroke-width="2" opacity=".45"/></pattern>
|
|
1126
|
+
<pattern id="aa-sand" width="12" height="12" patternUnits="userSpaceOnUse"><rect width="12" height="12" fill="#f5d77b" opacity=".80"/><circle cx="3" cy="3" r="1.2" fill="#9a7216" opacity=".55"/><circle cx="9" cy="8" r="1.2" fill="#9a7216" opacity=".55"/></pattern>
|
|
1127
|
+
<pattern id="aa-gravel" width="16" height="16" patternUnits="userSpaceOnUse"><rect width="16" height="16" fill="#a3a3a3" opacity=".72"/><circle cx="5" cy="6" r="2.1" fill="#525252" opacity=".55"/><circle cx="11" cy="11" r="2.5" fill="#525252" opacity=".55"/></pattern>
|
|
1128
|
+
</defs>
|
|
1129
|
+
<rect x="0" y="0" width="${width}" height="${height}" rx="16" fill="#ffffff"/>
|
|
1130
|
+
<text x="28" y="34" font-size="19" font-weight="850" fill="#111827">A-A Stratigraphic Section Along Borehole Alignment</text>
|
|
1131
|
+
<text x="28" y="56" font-size="12" fill="#64748b">Direct log columns are drawn at borehole positions. Dashed layer contacts are interpolated between extracted borehole boundaries.</text>
|
|
1132
|
+
<rect x="${chart.x0}" y="${chart.y0}" width="${chart.w}" height="${chart.h}" fill="#f8fafc" stroke="#cbd5e1"/>
|
|
1133
|
+
${Array.from({ length: 6 }, (_value, index) => {
|
|
1134
|
+
const depth = Number((maxDepth * index / 5).toFixed(2));
|
|
1135
|
+
const y = yForDepth(depth);
|
|
1136
|
+
return `<line x1="${chart.x0}" y1="${y.toFixed(2)}" x2="${chart.x0 + chart.w}" y2="${y.toFixed(2)}" stroke="#e2e8f0"/><text x="${chart.x0 - 58}" y="${(y + 4).toFixed(2)}" font-size="10" fill="#475569">${escapeHtml(depth.toFixed(depth % 1 === 0 ? 0 : 1))} m bgl</text>`;
|
|
1137
|
+
}).join('')}
|
|
1138
|
+
${Array.from({ length: Math.min(maxLayerCount, 8) }, (_value, layerIndex) => {
|
|
1139
|
+
const representative = boreholes.map((borehole) => borehole.strata[layerIndex]).find(Boolean);
|
|
1140
|
+
if (!representative)
|
|
1141
|
+
return '';
|
|
1142
|
+
const topPoints = [];
|
|
1143
|
+
const bottomPoints = [];
|
|
1144
|
+
boreholes.forEach((borehole, index) => {
|
|
1145
|
+
const layer = borehole.strata[layerIndex] ?? representative;
|
|
1146
|
+
const x = xFor(index);
|
|
1147
|
+
topPoints.push(`${x.toFixed(2)},${yForDepth(layer.top).toFixed(2)}`);
|
|
1148
|
+
bottomPoints.unshift(`${x.toFixed(2)},${yForDepth(layer.base).toFixed(2)}`);
|
|
1149
|
+
});
|
|
1150
|
+
const material = lightMaterialClass(representative.className);
|
|
1151
|
+
return `<polygon points="${[...topPoints, ...bottomPoints].join(' ')}" fill="url(#aa-${material})" stroke="${lightMaterialColor(representative.className)}" stroke-width="1.4" stroke-dasharray="${layerIndex === 0 ? '' : '6 5'}" opacity=".88"/>`;
|
|
1152
|
+
}).join('')}
|
|
1153
|
+
${boreholes.map((borehole, index) => {
|
|
1154
|
+
const x = xFor(index);
|
|
1155
|
+
return `
|
|
1156
|
+
<g class="bh-log">
|
|
1157
|
+
<line x1="${x.toFixed(2)}" y1="${chart.y0}" x2="${x.toFixed(2)}" y2="${yForDepth(integratedBoreholeMaxDepth(borehole)).toFixed(2)}" stroke="#111827" stroke-width="1" opacity=".35"/>
|
|
1158
|
+
${borehole.strata.map((stratum) => `<rect x="${(x - 15).toFixed(2)}" y="${yForDepth(stratum.top).toFixed(2)}" width="30" height="${Math.max(2, yForDepth(stratum.base) - yForDepth(stratum.top)).toFixed(2)}" fill="url(#aa-${lightMaterialClass(stratum.className)})" stroke="${stratum.status === 'accepted' ? '#172033' : '#b7791f'}" stroke-width="1" ${stratum.status === 'accepted' ? '' : 'stroke-dasharray="5 4"'}/>`).join('')}
|
|
1159
|
+
${borehole.spt.map((spt) => `<circle cx="${(x + 24).toFixed(2)}" cy="${yForDepth(spt.depth).toFixed(2)}" r="4.2" fill="#dbeafe" stroke="#1d4ed8"/>`).join('')}
|
|
1160
|
+
${borehole.groundwater.map((water) => `<path d="M${(x - 40).toFixed(2)} ${yForDepth(water.depth) - 5} l15 0 l-7.5 12 z" fill="#bae6fd" stroke="#0369a1" stroke-width="1.5"/>`).join('')}
|
|
1161
|
+
<circle cx="${x.toFixed(2)}" cy="${chart.y0}" r="7.5" fill="#2563eb" stroke="#fff" stroke-width="3"/>
|
|
1162
|
+
<text x="${(x - 22).toFixed(2)}" y="${chart.y0 - 14}" font-size="12" font-weight="850" fill="#111827">${escapeHtml(borehole.id)}</text>
|
|
1163
|
+
<text x="${(x - 24).toFixed(2)}" y="${(yForDepth(integratedBoreholeMaxDepth(borehole)) + 18).toFixed(2)}" font-size="10" fill="#64748b">TD ${escapeHtml(borehole.totalDepth.toFixed(1))} m</text>
|
|
1164
|
+
</g>
|
|
1165
|
+
`;
|
|
1166
|
+
}).join('')}
|
|
1167
|
+
<text x="${chart.x0}" y="${height - 28}" font-size="11" fill="#64748b">Vertical exaggeration used for review. Use engineering judgment before adopting interpolated strata surfaces.</text>
|
|
1168
|
+
</svg>
|
|
936
1169
|
</div>
|
|
937
1170
|
`;
|
|
938
1171
|
}
|
|
939
|
-
|
|
1172
|
+
function renderLightTables(dossier) {
|
|
1173
|
+
if (dossier.tables.length === 0) {
|
|
1174
|
+
return '<div class="empty-light">No audit tables were retained.</div>';
|
|
1175
|
+
}
|
|
1176
|
+
return `
|
|
1177
|
+
<div class="table-panel-grid">
|
|
1178
|
+
${dossier.tables.map((table) => `
|
|
1179
|
+
<article class="panel embedded-panel">
|
|
1180
|
+
<h2>${escapeHtml(table.title)}${table.description ? `<small>${escapeHtml(table.description)}</small>` : ''}</h2>
|
|
1181
|
+
<div class="panel-body table-scroll">
|
|
1182
|
+
<table>
|
|
1183
|
+
<thead><tr>${table.columns.map((column) => `<th>${escapeHtml(column)}</th>`).join('')}</tr></thead>
|
|
1184
|
+
<tbody>${table.rows.length > 0 ? table.rows.map((row) => `<tr>${row.map((cell) => `<td>${escapeHtml(cell)}</td>`).join('')}</tr>`).join('') : `<tr><td colspan="${escapeHtml(table.columns.length)}">${escapeHtml(table.emptyState ?? 'No rows retained.')}</td></tr>`}</tbody>
|
|
1185
|
+
</table>
|
|
1186
|
+
</div>
|
|
1187
|
+
</article>
|
|
1188
|
+
`).join('')}
|
|
1189
|
+
</div>
|
|
1190
|
+
`;
|
|
1191
|
+
}
|
|
1192
|
+
function renderLightAgentReviews(model) {
|
|
1193
|
+
if (model.agentReviews.length === 0) {
|
|
1194
|
+
return '<div class="empty-light">No single-agent or swarm review session was attached to this ingest output.</div>';
|
|
1195
|
+
}
|
|
1196
|
+
return `
|
|
1197
|
+
<div class="pipeline">
|
|
1198
|
+
${model.agentReviews.map((review, index) => `
|
|
1199
|
+
<div class="step">
|
|
1200
|
+
<div class="num">${index + 1}</div>
|
|
1201
|
+
<div>
|
|
1202
|
+
<strong>${escapeHtml(review.title)}</strong>
|
|
1203
|
+
<small>${escapeHtml(review.summary)}</small>
|
|
1204
|
+
${review.warnings.length > 0 ? `<small>${escapeHtml(review.warnings.join(' | '))}</small>` : ''}
|
|
1205
|
+
</div>
|
|
1206
|
+
<span class="pill ${review.warnings.length > 0 ? 'warn' : 'good'}">${escapeHtml([
|
|
1207
|
+
review.mode,
|
|
1208
|
+
review.stepCount != null ? `${review.stepCount} steps` : null,
|
|
1209
|
+
review.tokens != null ? `${review.tokens} tokens` : null,
|
|
1210
|
+
].filter(Boolean).join(' | '))}</span>
|
|
1211
|
+
</div>
|
|
1212
|
+
`).join('')}
|
|
1213
|
+
</div>
|
|
1214
|
+
`;
|
|
1215
|
+
}
|
|
1216
|
+
function renderLightValidation(dossier, model) {
|
|
1217
|
+
const warnings = model.quality.warnings.length > 0
|
|
1218
|
+
? model.quality.warnings
|
|
1219
|
+
: ['No integrated review warnings were retained.'];
|
|
1220
|
+
const json = escapeHtml(JSON.stringify(model, null, 2));
|
|
1221
|
+
return `
|
|
1222
|
+
<div class="data-grid">
|
|
1223
|
+
<div class="panel">
|
|
1224
|
+
<h2>Validation workflow <small>extraction, geospatial, and rendering checks</small></h2>
|
|
1225
|
+
<div class="tabs">
|
|
1226
|
+
<button class="tab active" type="button" data-tab-target="warningsTab">Warnings</button>
|
|
1227
|
+
<button class="tab" type="button" data-tab-target="pipelineTab">Pipeline</button>
|
|
1228
|
+
<button class="tab" type="button" data-tab-target="agentsTab">Agents</button>
|
|
1229
|
+
<button class="tab" type="button" data-tab-target="tablesTab">Tables</button>
|
|
1230
|
+
</div>
|
|
1231
|
+
<div class="panel-body">
|
|
1232
|
+
<div id="warningsTab" class="tab-content active">
|
|
1233
|
+
<div class="warning-list">
|
|
1234
|
+
${warnings.map((warning) => `<div class="warning"><strong>Review gate</strong><br>${escapeHtml(warning)}</div>`).join('')}
|
|
1235
|
+
</div>
|
|
1236
|
+
</div>
|
|
1237
|
+
<div id="pipelineTab" class="tab-content">
|
|
1238
|
+
<div class="pipeline">
|
|
1239
|
+
<div class="step"><div class="num">1</div><div><strong>GLM-OCR layout evidence</strong><small>Text, tables, layout blocks, bboxes, and page dimensions seed the extraction.</small></div><span class="pill good">ready</span></div>
|
|
1240
|
+
<div class="step"><div class="num">2</div><div><strong>GLM-5.1 document routing</strong><small>Pages are classified into borehole logs, report narrative, lab data, and ground model evidence.</small></div><span class="pill good">ready</span></div>
|
|
1241
|
+
<div class="step"><div class="num">3</div><div><strong>Deterministic depth mapping</strong><small>Depth intervals, SPT records, and groundwater are rendered from structured objects.</small></div><span class="pill good">ready</span></div>
|
|
1242
|
+
<div class="step"><div class="num">4</div><div><strong>Vision verification gate</strong><small>Uncertain symbols remain review-gated before engineering reuse.</small></div><span class="pill warn">review</span></div>
|
|
1243
|
+
<div class="step"><div class="num">5</div><div><strong>Provider-neutral schema</strong><small>The same JSON drives fields, strip logs, map, section, validation, and export.</small></div><span class="pill good">ready</span></div>
|
|
1244
|
+
</div>
|
|
1245
|
+
</div>
|
|
1246
|
+
<div id="agentsTab" class="tab-content">
|
|
1247
|
+
${renderLightAgentReviews(model)}
|
|
1248
|
+
</div>
|
|
1249
|
+
<div id="tablesTab" class="tab-content">
|
|
1250
|
+
${renderLightTables(dossier)}
|
|
1251
|
+
</div>
|
|
1252
|
+
</div>
|
|
1253
|
+
</div>
|
|
1254
|
+
<div class="panel">
|
|
1255
|
+
<h2>Integrated extraction JSON <small>single source of truth for this UI</small></h2>
|
|
1256
|
+
<div class="panel-body"><pre>${json}</pre></div>
|
|
1257
|
+
</div>
|
|
1258
|
+
</div>
|
|
1259
|
+
`;
|
|
1260
|
+
}
|
|
1261
|
+
function renderIntegratedReviewOnlyHtml(dossier) {
|
|
1262
|
+
const model = buildIntegratedReviewModel(dossier);
|
|
1263
|
+
const selected = model.boreholes[0];
|
|
940
1264
|
const generatedDate = new Date(dossier.generatedAt).toLocaleString('en-CA', {
|
|
941
1265
|
dateStyle: 'medium',
|
|
942
1266
|
timeStyle: 'short',
|
|
943
1267
|
});
|
|
944
|
-
const
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
? 'Borehole Log Interpretation and Review Summary'
|
|
948
|
-
: 'AI-Assisted Geotechnical Review Summary';
|
|
1268
|
+
const depthMapping = model.quality.depthMappingR2 == null
|
|
1269
|
+
? 'n/a'
|
|
1270
|
+
: model.quality.depthMappingR2.toFixed(3).replace(/0+$/, '').replace(/\.$/, '');
|
|
949
1271
|
return `<!doctype html>
|
|
950
1272
|
<html lang="en">
|
|
951
1273
|
<head>
|
|
952
1274
|
<meta charset="utf-8" />
|
|
953
1275
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
954
|
-
<title>
|
|
1276
|
+
<title>geotechCLI Integrated Vision + Geospatial Review - ${escapeHtml(dossier.title)}</title>
|
|
955
1277
|
<style>
|
|
956
|
-
:root
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
--border-strong: #334155;
|
|
975
|
-
--shadow: 0 20px 60px rgba(0, 0, 0, 0.28);
|
|
976
|
-
--radius: 14px;
|
|
977
|
-
--radius-sm: 10px;
|
|
978
|
-
}
|
|
979
|
-
|
|
980
|
-
* { box-sizing: border-box; }
|
|
981
|
-
html {
|
|
982
|
-
scroll-behavior: smooth;
|
|
983
|
-
overflow-x: hidden;
|
|
984
|
-
}
|
|
985
|
-
html, body { margin: 0; min-height: 100%; }
|
|
986
|
-
|
|
987
|
-
body {
|
|
988
|
-
font-family: Inter, "Segoe UI Variable", "Segoe UI", Arial, sans-serif;
|
|
989
|
-
color: var(--text);
|
|
990
|
-
background: var(--bg);
|
|
991
|
-
font-variant-numeric: tabular-nums;
|
|
992
|
-
overflow-x: hidden;
|
|
993
|
-
padding-bottom: 98px;
|
|
994
|
-
}
|
|
995
|
-
|
|
996
|
-
.layout {
|
|
997
|
-
display: grid;
|
|
998
|
-
grid-template-columns: minmax(0, 1fr);
|
|
999
|
-
gap: 24px;
|
|
1000
|
-
width: 100%;
|
|
1001
|
-
max-width: 1280px;
|
|
1002
|
-
margin: 0 auto;
|
|
1003
|
-
padding: 16px 24px 120px;
|
|
1004
|
-
min-width: 0;
|
|
1005
|
-
}
|
|
1006
|
-
|
|
1007
|
-
.sidebar {
|
|
1008
|
-
position: sticky;
|
|
1009
|
-
top: 0;
|
|
1010
|
-
z-index: 30;
|
|
1011
|
-
display: flex;
|
|
1012
|
-
align-items: center;
|
|
1013
|
-
justify-content: space-between;
|
|
1014
|
-
gap: 18px;
|
|
1015
|
-
align-self: stretch;
|
|
1016
|
-
min-height: 58px;
|
|
1017
|
-
min-width: 0;
|
|
1018
|
-
padding: 12px 16px;
|
|
1019
|
-
border: 1px solid var(--border);
|
|
1020
|
-
border-radius: 14px;
|
|
1021
|
-
background: rgba(11, 17, 32, 0.94);
|
|
1022
|
-
color: #f8fafc;
|
|
1023
|
-
box-shadow: 0 14px 44px rgba(0, 0, 0, 0.26);
|
|
1024
|
-
backdrop-filter: blur(14px);
|
|
1025
|
-
}
|
|
1026
|
-
|
|
1027
|
-
.brand {
|
|
1028
|
-
display: flex;
|
|
1029
|
-
align-items: center;
|
|
1030
|
-
gap: 10px;
|
|
1031
|
-
padding: 0;
|
|
1032
|
-
border-bottom: 0;
|
|
1033
|
-
margin: 0;
|
|
1034
|
-
min-width: 190px;
|
|
1035
|
-
}
|
|
1036
|
-
|
|
1037
|
-
.brand::before {
|
|
1038
|
-
content: "";
|
|
1039
|
-
width: 28px;
|
|
1040
|
-
height: 28px;
|
|
1041
|
-
border-radius: 8px;
|
|
1042
|
-
background: var(--primary);
|
|
1043
|
-
box-shadow: 0 0 24px rgba(6, 182, 212, 0.28);
|
|
1044
|
-
flex: 0 0 auto;
|
|
1045
|
-
}
|
|
1046
|
-
|
|
1047
|
-
.brand strong { font-size: 0.96rem; white-space: nowrap; }
|
|
1048
|
-
.brand span { display: none; }
|
|
1049
|
-
|
|
1050
|
-
.nav-list {
|
|
1051
|
-
display: flex;
|
|
1052
|
-
align-items: center;
|
|
1053
|
-
justify-content: flex-end;
|
|
1054
|
-
gap: 6px;
|
|
1055
|
-
flex-wrap: wrap;
|
|
1056
|
-
}
|
|
1057
|
-
|
|
1058
|
-
.nav-list a {
|
|
1059
|
-
color: #cbd5e1;
|
|
1060
|
-
text-decoration: none;
|
|
1061
|
-
padding: 8px 10px;
|
|
1062
|
-
border-radius: 8px;
|
|
1063
|
-
font-weight: 650;
|
|
1064
|
-
font-size: 0.8rem;
|
|
1065
|
-
overflow-wrap: anywhere;
|
|
1066
|
-
}
|
|
1067
|
-
|
|
1068
|
-
.nav-list a:hover, .nav-list a:focus {
|
|
1069
|
-
background: rgba(100, 116, 139, 0.16);
|
|
1070
|
-
outline: none;
|
|
1071
|
-
}
|
|
1072
|
-
|
|
1073
|
-
.content {
|
|
1074
|
-
display: grid;
|
|
1075
|
-
gap: 28px;
|
|
1076
|
-
min-width: 0;
|
|
1077
|
-
max-width: 100%;
|
|
1078
|
-
}
|
|
1079
|
-
|
|
1080
|
-
.hero {
|
|
1081
|
-
padding: 30px;
|
|
1082
|
-
border: 1px solid var(--border);
|
|
1083
|
-
border-radius: 18px;
|
|
1084
|
-
background:
|
|
1085
|
-
radial-gradient(circle at top right, rgba(6, 182, 212, 0.09), transparent 34%),
|
|
1086
|
-
linear-gradient(135deg, #0b1120 0%, #0f172a 100%);
|
|
1087
|
-
box-shadow: var(--shadow);
|
|
1088
|
-
display: grid;
|
|
1089
|
-
gap: 22px;
|
|
1090
|
-
min-width: 0;
|
|
1091
|
-
overflow: hidden;
|
|
1092
|
-
}
|
|
1093
|
-
|
|
1094
|
-
.eyebrow {
|
|
1095
|
-
display: inline-flex;
|
|
1096
|
-
width: fit-content;
|
|
1097
|
-
padding: 8px 12px;
|
|
1098
|
-
border-radius: 999px;
|
|
1099
|
-
background: var(--primary-soft);
|
|
1100
|
-
color: #67e8f9;
|
|
1101
|
-
font-weight: 800;
|
|
1102
|
-
font-size: 0.78rem;
|
|
1103
|
-
text-transform: uppercase;
|
|
1104
|
-
}
|
|
1105
|
-
|
|
1106
|
-
.hero-main {
|
|
1107
|
-
display: grid;
|
|
1108
|
-
grid-template-columns: minmax(0, 1fr) 300px;
|
|
1109
|
-
gap: 24px;
|
|
1110
|
-
align-items: start;
|
|
1111
|
-
min-width: 0;
|
|
1112
|
-
}
|
|
1113
|
-
|
|
1114
|
-
.hero-main > div {
|
|
1115
|
-
min-width: 0;
|
|
1116
|
-
}
|
|
1117
|
-
|
|
1118
|
-
h1, h2, h3, p { margin: 0; }
|
|
1119
|
-
|
|
1120
|
-
h1 {
|
|
1121
|
-
margin-top: 12px;
|
|
1122
|
-
font-size: clamp(2rem, 4vw, 3rem);
|
|
1123
|
-
line-height: 1.04;
|
|
1124
|
-
max-width: 22ch;
|
|
1125
|
-
overflow-wrap: anywhere;
|
|
1126
|
-
}
|
|
1127
|
-
|
|
1128
|
-
.hero-subtitle {
|
|
1129
|
-
margin-top: 12px;
|
|
1130
|
-
color: #67e8f9;
|
|
1131
|
-
font-weight: 800;
|
|
1132
|
-
font-size: 1.12rem;
|
|
1133
|
-
}
|
|
1134
|
-
|
|
1135
|
-
.hero-summary {
|
|
1136
|
-
margin-top: 14px;
|
|
1137
|
-
color: var(--muted);
|
|
1138
|
-
line-height: 1.7;
|
|
1139
|
-
max-width: 92ch;
|
|
1140
|
-
overflow-wrap: anywhere;
|
|
1141
|
-
}
|
|
1142
|
-
|
|
1143
|
-
.hero-meta {
|
|
1144
|
-
display: grid;
|
|
1145
|
-
gap: 10px;
|
|
1146
|
-
padding: 18px;
|
|
1147
|
-
border: 1px solid var(--border-strong);
|
|
1148
|
-
border-radius: var(--radius-sm);
|
|
1149
|
-
background: rgba(15, 23, 42, 0.74);
|
|
1150
|
-
color: var(--muted);
|
|
1151
|
-
font-size: 0.92rem;
|
|
1152
|
-
min-width: 0;
|
|
1153
|
-
overflow-wrap: anywhere;
|
|
1154
|
-
}
|
|
1155
|
-
|
|
1156
|
-
.hero-meta strong {
|
|
1157
|
-
color: var(--text);
|
|
1158
|
-
overflow-wrap: anywhere;
|
|
1159
|
-
}
|
|
1160
|
-
|
|
1161
|
-
.status-badge-row {
|
|
1162
|
-
display: flex;
|
|
1163
|
-
flex-wrap: wrap;
|
|
1164
|
-
gap: 9px;
|
|
1165
|
-
margin-top: 16px;
|
|
1166
|
-
}
|
|
1167
|
-
|
|
1168
|
-
.status-badge {
|
|
1169
|
-
display: inline-grid;
|
|
1170
|
-
gap: 4px;
|
|
1171
|
-
min-width: 126px;
|
|
1172
|
-
padding: 10px 12px;
|
|
1173
|
-
border: 1px solid currentColor;
|
|
1174
|
-
border-radius: 999px;
|
|
1175
|
-
line-height: 1.2;
|
|
1176
|
-
background: rgba(15, 23, 42, 0.74);
|
|
1177
|
-
}
|
|
1178
|
-
|
|
1179
|
-
.status-badge span {
|
|
1180
|
-
color: currentColor;
|
|
1181
|
-
opacity: 0.72;
|
|
1182
|
-
font-size: 0.68rem;
|
|
1183
|
-
text-transform: uppercase;
|
|
1184
|
-
font-weight: 900;
|
|
1185
|
-
}
|
|
1186
|
-
|
|
1187
|
-
.status-badge strong {
|
|
1188
|
-
color: currentColor;
|
|
1189
|
-
font-size: 0.86rem;
|
|
1190
|
-
overflow-wrap: anywhere;
|
|
1191
|
-
}
|
|
1192
|
-
|
|
1193
|
-
.executive-grid, .metric-grid, .card-grid, .evidence-grid, .audit-page-grid, .footer-grid {
|
|
1194
|
-
display: grid;
|
|
1195
|
-
gap: 16px;
|
|
1196
|
-
}
|
|
1197
|
-
|
|
1198
|
-
.executive-grid {
|
|
1199
|
-
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
|
|
1200
|
-
}
|
|
1201
|
-
|
|
1202
|
-
.fact-card, .metric-card, .insight-card, .evidence-card, .footer-card, .audit-page-card {
|
|
1203
|
-
border: 1px solid var(--border);
|
|
1204
|
-
border-radius: var(--radius);
|
|
1205
|
-
background: var(--surface);
|
|
1206
|
-
padding: 18px;
|
|
1207
|
-
min-width: 0;
|
|
1208
|
-
}
|
|
1209
|
-
|
|
1210
|
-
.fact-card {
|
|
1211
|
-
display: grid;
|
|
1212
|
-
gap: 8px;
|
|
1213
|
-
}
|
|
1214
|
-
|
|
1215
|
-
.fact-card span, .metric-card span {
|
|
1216
|
-
color: var(--muted);
|
|
1217
|
-
font-size: 0.78rem;
|
|
1218
|
-
text-transform: uppercase;
|
|
1219
|
-
font-weight: 800;
|
|
1220
|
-
}
|
|
1221
|
-
|
|
1222
|
-
.fact-card strong {
|
|
1223
|
-
color: var(--text);
|
|
1224
|
-
font-size: 1rem;
|
|
1225
|
-
line-height: 1.45;
|
|
1226
|
-
overflow-wrap: anywhere;
|
|
1227
|
-
}
|
|
1228
|
-
|
|
1229
|
-
.fact-card small, .metric-card small {
|
|
1230
|
-
color: var(--muted);
|
|
1231
|
-
line-height: 1.5;
|
|
1232
|
-
}
|
|
1233
|
-
|
|
1234
|
-
.metric-grid {
|
|
1235
|
-
grid-template-columns: repeat(auto-fit, minmax(165px, 1fr));
|
|
1236
|
-
}
|
|
1237
|
-
|
|
1238
|
-
.metric-card {
|
|
1239
|
-
display: grid;
|
|
1240
|
-
gap: 10px;
|
|
1241
|
-
}
|
|
1242
|
-
|
|
1243
|
-
.metric-card strong {
|
|
1244
|
-
font-size: 1.65rem;
|
|
1245
|
-
line-height: 1;
|
|
1246
|
-
}
|
|
1247
|
-
|
|
1248
|
-
.meter {
|
|
1249
|
-
display: block;
|
|
1250
|
-
height: 8px;
|
|
1251
|
-
overflow: hidden;
|
|
1252
|
-
border-radius: 999px;
|
|
1253
|
-
background: rgba(102, 112, 133, 0.18);
|
|
1254
|
-
}
|
|
1255
|
-
|
|
1256
|
-
.meter span {
|
|
1257
|
-
display: block;
|
|
1258
|
-
height: 100%;
|
|
1259
|
-
border-radius: inherit;
|
|
1260
|
-
background: currentColor;
|
|
1261
|
-
opacity: 0.75;
|
|
1262
|
-
}
|
|
1263
|
-
|
|
1264
|
-
.tone-accent { background: var(--primary-soft); color: #22d3ee; border-color: rgba(6, 182, 212, 0.28); }
|
|
1265
|
-
.tone-good { background: var(--success-soft); color: #34d399; border-color: rgba(16, 185, 129, 0.28); }
|
|
1266
|
-
.tone-warning { background: var(--warning-soft); color: #fcd34d; border-color: rgba(245, 158, 11, 0.3); }
|
|
1267
|
-
.tone-danger { background: var(--danger-soft); color: #f87171; border-color: rgba(239, 68, 68, 0.32); }
|
|
1268
|
-
.tone-neutral { background: var(--neutral-soft); color: #cbd5e1; border-color: rgba(100, 116, 139, 0.28); }
|
|
1269
|
-
|
|
1270
|
-
.action-bar {
|
|
1271
|
-
display: flex;
|
|
1272
|
-
flex-wrap: wrap;
|
|
1273
|
-
gap: 10px;
|
|
1274
|
-
}
|
|
1275
|
-
|
|
1276
|
-
.action-bar button, .action-bar a {
|
|
1277
|
-
border: 1px solid var(--border-strong);
|
|
1278
|
-
border-radius: 8px;
|
|
1279
|
-
background: var(--surface-soft);
|
|
1280
|
-
color: #cbd5e1;
|
|
1281
|
-
padding: 9px 12px;
|
|
1282
|
-
font: inherit;
|
|
1283
|
-
font-weight: 800;
|
|
1284
|
-
text-decoration: none;
|
|
1285
|
-
box-shadow: none;
|
|
1286
|
-
cursor: pointer;
|
|
1287
|
-
overflow-wrap: anywhere;
|
|
1288
|
-
}
|
|
1289
|
-
|
|
1290
|
-
.action-bar .primary-action {
|
|
1291
|
-
background: var(--primary);
|
|
1292
|
-
border-color: var(--primary);
|
|
1293
|
-
color: #06111f;
|
|
1294
|
-
}
|
|
1295
|
-
|
|
1296
|
-
.review-bar {
|
|
1297
|
-
position: fixed;
|
|
1298
|
-
left: 50%;
|
|
1299
|
-
bottom: 0;
|
|
1300
|
-
z-index: 45;
|
|
1301
|
-
display: flex;
|
|
1302
|
-
align-items: center;
|
|
1303
|
-
justify-content: space-between;
|
|
1304
|
-
gap: 18px;
|
|
1305
|
-
width: min(1180px, calc(100vw - 36px));
|
|
1306
|
-
margin: 10px auto 0;
|
|
1307
|
-
padding: 13px 14px;
|
|
1308
|
-
border: 1px solid #263244;
|
|
1309
|
-
border-radius: 18px 18px 0 0;
|
|
1310
|
-
background: rgba(11, 18, 32, 0.94);
|
|
1311
|
-
color: #f8fafc;
|
|
1312
|
-
box-shadow: 0 -18px 38px rgba(11, 18, 32, 0.2);
|
|
1313
|
-
backdrop-filter: blur(14px);
|
|
1314
|
-
transform: translate(-50%, 120%);
|
|
1315
|
-
transition: transform 180ms ease;
|
|
1316
|
-
}
|
|
1317
|
-
|
|
1318
|
-
.review-bar.visible {
|
|
1319
|
-
transform: translate(-50%, 0);
|
|
1320
|
-
}
|
|
1321
|
-
|
|
1322
|
-
.review-bar > div:first-child {
|
|
1323
|
-
display: grid;
|
|
1324
|
-
gap: 3px;
|
|
1325
|
-
min-width: 220px;
|
|
1326
|
-
}
|
|
1327
|
-
|
|
1328
|
-
.review-bar strong { font-size: 0.95rem; }
|
|
1329
|
-
.review-bar span { color: #94a3b8; font-size: 0.84rem; line-height: 1.4; }
|
|
1330
|
-
|
|
1331
|
-
.review-actions {
|
|
1332
|
-
display: flex;
|
|
1333
|
-
flex-wrap: wrap;
|
|
1334
|
-
justify-content: flex-end;
|
|
1335
|
-
gap: 8px;
|
|
1336
|
-
}
|
|
1337
|
-
|
|
1338
|
-
.review-actions a, .review-actions button {
|
|
1339
|
-
border: 1px solid #334155;
|
|
1340
|
-
border-radius: 10px;
|
|
1341
|
-
background: #111827;
|
|
1342
|
-
color: #dbeafe;
|
|
1343
|
-
padding: 8px 10px;
|
|
1344
|
-
font: inherit;
|
|
1345
|
-
font-size: 0.78rem;
|
|
1346
|
-
font-weight: 850;
|
|
1347
|
-
text-decoration: none;
|
|
1348
|
-
cursor: pointer;
|
|
1349
|
-
}
|
|
1350
|
-
|
|
1351
|
-
.review-actions .primary-action {
|
|
1352
|
-
background: #38bdf8;
|
|
1353
|
-
border-color: #38bdf8;
|
|
1354
|
-
color: #0b1220;
|
|
1355
|
-
}
|
|
1356
|
-
|
|
1357
|
-
.data-section {
|
|
1358
|
-
display: grid;
|
|
1359
|
-
gap: 16px;
|
|
1360
|
-
scroll-margin-top: 24px;
|
|
1361
|
-
min-width: 0;
|
|
1362
|
-
}
|
|
1363
|
-
|
|
1364
|
-
.section-heading {
|
|
1365
|
-
display: grid;
|
|
1366
|
-
gap: 8px;
|
|
1367
|
-
}
|
|
1368
|
-
|
|
1369
|
-
.section-heading h2 {
|
|
1370
|
-
font-size: 1.35rem;
|
|
1371
|
-
letter-spacing: 0;
|
|
1372
|
-
color: var(--text);
|
|
1373
|
-
}
|
|
1374
|
-
|
|
1375
|
-
.section-heading p {
|
|
1376
|
-
color: var(--muted);
|
|
1377
|
-
line-height: 1.65;
|
|
1378
|
-
max-width: 96ch;
|
|
1379
|
-
}
|
|
1380
|
-
|
|
1381
|
-
.card-grid {
|
|
1382
|
-
grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
|
|
1383
|
-
}
|
|
1384
|
-
|
|
1385
|
-
.insight-card {
|
|
1386
|
-
display: grid;
|
|
1387
|
-
gap: 12px;
|
|
1388
|
-
}
|
|
1389
|
-
|
|
1390
|
-
.insight-card h3 {
|
|
1391
|
-
color: var(--text);
|
|
1392
|
-
font-size: 1.06rem;
|
|
1393
|
-
}
|
|
1394
|
-
|
|
1395
|
-
.insight-card p, .insight-card li, .evidence-card p, .evidence-card li {
|
|
1396
|
-
color: var(--muted);
|
|
1397
|
-
line-height: 1.62;
|
|
1398
|
-
}
|
|
1399
|
-
|
|
1400
|
-
.insight-card ul, .evidence-card ul, .profile-notes, .footer-card ul, .audit-page-card ul {
|
|
1401
|
-
margin: 0;
|
|
1402
|
-
padding-left: 18px;
|
|
1403
|
-
display: grid;
|
|
1404
|
-
gap: 7px;
|
|
1405
|
-
}
|
|
1406
|
-
|
|
1407
|
-
.profile-shell, .table-shell {
|
|
1408
|
-
overflow: auto;
|
|
1409
|
-
border: 1px solid var(--border);
|
|
1410
|
-
border-radius: var(--radius);
|
|
1411
|
-
background: var(--surface);
|
|
1412
|
-
min-width: 0;
|
|
1413
|
-
max-width: 100%;
|
|
1414
|
-
}
|
|
1415
|
-
|
|
1416
|
-
.table-shell {
|
|
1417
|
-
max-height: 560px;
|
|
1418
|
-
}
|
|
1419
|
-
|
|
1420
|
-
.profile-shell {
|
|
1421
|
-
overflow-x: auto;
|
|
1422
|
-
overflow-y: visible;
|
|
1423
|
-
max-height: none;
|
|
1424
|
-
}
|
|
1425
|
-
|
|
1426
|
-
.borehole-profile {
|
|
1427
|
-
display: block;
|
|
1428
|
-
min-width: 520px;
|
|
1429
|
-
width: min(100%, 900px);
|
|
1430
|
-
height: auto;
|
|
1431
|
-
margin: 0 auto;
|
|
1432
|
-
max-height: 460px;
|
|
1433
|
-
}
|
|
1434
|
-
|
|
1435
|
-
.profile-legend {
|
|
1436
|
-
display: grid;
|
|
1437
|
-
gap: 8px;
|
|
1438
|
-
margin-top: 12px;
|
|
1439
|
-
}
|
|
1440
|
-
|
|
1441
|
-
.profile-legend-row {
|
|
1442
|
-
display: grid;
|
|
1443
|
-
grid-template-columns: 18px minmax(64px, 0.7fr) minmax(96px, 0.8fr) minmax(160px, 2fr) minmax(94px, 0.8fr);
|
|
1444
|
-
align-items: center;
|
|
1445
|
-
gap: 10px;
|
|
1446
|
-
padding: 10px 12px;
|
|
1447
|
-
border: 1px solid var(--border);
|
|
1448
|
-
border-radius: 12px;
|
|
1449
|
-
background: var(--surface-soft);
|
|
1450
|
-
color: var(--muted);
|
|
1451
|
-
font-size: 0.82rem;
|
|
1452
|
-
line-height: 1.35;
|
|
1453
|
-
}
|
|
1454
|
-
|
|
1455
|
-
.profile-legend-row strong {
|
|
1456
|
-
color: var(--text);
|
|
1457
|
-
font-size: 0.84rem;
|
|
1458
|
-
}
|
|
1459
|
-
|
|
1460
|
-
.profile-legend-row em {
|
|
1461
|
-
color: var(--warning);
|
|
1462
|
-
font-style: normal;
|
|
1463
|
-
font-weight: 800;
|
|
1464
|
-
font-size: 0.78rem;
|
|
1465
|
-
text-align: right;
|
|
1466
|
-
}
|
|
1467
|
-
|
|
1468
|
-
.legend-swatch {
|
|
1469
|
-
width: 18px;
|
|
1470
|
-
height: 18px;
|
|
1471
|
-
border-radius: 5px;
|
|
1472
|
-
border: 1px solid rgba(255, 255, 255, 0.24);
|
|
1473
|
-
box-shadow: inset 0 0 0 1px rgba(15, 23, 42, 0.18);
|
|
1474
|
-
}
|
|
1475
|
-
|
|
1476
|
-
.cross-section-shell {
|
|
1477
|
-
background:
|
|
1478
|
-
linear-gradient(180deg, rgba(6, 182, 212, 0.06), rgba(16, 185, 129, 0.04)),
|
|
1479
|
-
var(--surface);
|
|
1480
|
-
}
|
|
1481
|
-
|
|
1482
|
-
.ground-cross-section {
|
|
1483
|
-
display: block;
|
|
1484
|
-
min-width: 620px;
|
|
1485
|
-
width: min(100%, 920px);
|
|
1486
|
-
height: auto;
|
|
1487
|
-
margin: 0 auto;
|
|
1488
|
-
max-height: 430px;
|
|
1489
|
-
}
|
|
1490
|
-
|
|
1491
|
-
.verification-note {
|
|
1492
|
-
color: var(--muted);
|
|
1493
|
-
font-weight: 700;
|
|
1494
|
-
line-height: 1.55;
|
|
1495
|
-
padding-left: 14px;
|
|
1496
|
-
border-left: 3px solid var(--warning);
|
|
1497
|
-
}
|
|
1498
|
-
|
|
1499
|
-
.gm-visual-grid {
|
|
1500
|
-
display: grid;
|
|
1501
|
-
grid-template-columns: minmax(0, 1fr);
|
|
1502
|
-
gap: 16px;
|
|
1503
|
-
min-width: 0;
|
|
1504
|
-
}
|
|
1505
|
-
|
|
1506
|
-
.gm-panel {
|
|
1507
|
-
display: grid;
|
|
1508
|
-
gap: 12px;
|
|
1509
|
-
min-width: 0;
|
|
1510
|
-
padding: 16px;
|
|
1511
|
-
border: 1px solid var(--border);
|
|
1512
|
-
border-radius: var(--radius);
|
|
1513
|
-
background: var(--surface);
|
|
1514
|
-
}
|
|
1515
|
-
|
|
1516
|
-
.gm-panel h3, .gm-mini-card h4 {
|
|
1517
|
-
margin: 0;
|
|
1518
|
-
color: var(--text);
|
|
1519
|
-
letter-spacing: 0;
|
|
1520
|
-
}
|
|
1521
|
-
|
|
1522
|
-
.gm-panel h3 {
|
|
1523
|
-
font-size: 1rem;
|
|
1524
|
-
}
|
|
1525
|
-
|
|
1526
|
-
.gm-chart-shell {
|
|
1527
|
-
overflow-x: auto;
|
|
1528
|
-
overflow-y: visible;
|
|
1529
|
-
max-width: 100%;
|
|
1530
|
-
min-width: 0;
|
|
1531
|
-
border: 1px solid rgba(100, 116, 139, 0.28);
|
|
1532
|
-
border-radius: 16px;
|
|
1533
|
-
background: #0b1120;
|
|
1534
|
-
}
|
|
1535
|
-
|
|
1536
|
-
.gm-strip-log, .gm-depth-chart {
|
|
1537
|
-
display: block;
|
|
1538
|
-
width: min(100%, 900px);
|
|
1539
|
-
min-width: 520px;
|
|
1540
|
-
height: auto;
|
|
1541
|
-
margin: 0 auto;
|
|
1542
|
-
max-height: 410px;
|
|
1543
|
-
}
|
|
1544
|
-
|
|
1545
|
-
.gm-strip-log {
|
|
1546
|
-
margin: 0;
|
|
1547
|
-
}
|
|
1548
|
-
|
|
1549
|
-
.gm-mini-grid {
|
|
1550
|
-
display: grid;
|
|
1551
|
-
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
|
|
1552
|
-
gap: 12px;
|
|
1553
|
-
min-width: 0;
|
|
1554
|
-
}
|
|
1555
|
-
|
|
1556
|
-
.gm-mini-card {
|
|
1557
|
-
display: grid;
|
|
1558
|
-
gap: 8px;
|
|
1559
|
-
min-width: 0;
|
|
1560
|
-
padding: 12px;
|
|
1561
|
-
border: 1px solid rgba(100, 116, 139, 0.28);
|
|
1562
|
-
border-radius: 14px;
|
|
1563
|
-
background: #0b1120;
|
|
1564
|
-
}
|
|
1565
|
-
|
|
1566
|
-
.gm-mini-card h4 {
|
|
1567
|
-
font-size: 0.86rem;
|
|
1568
|
-
overflow-wrap: anywhere;
|
|
1569
|
-
}
|
|
1570
|
-
|
|
1571
|
-
.gm-mini-card svg {
|
|
1572
|
-
display: block;
|
|
1573
|
-
width: 100%;
|
|
1574
|
-
height: auto;
|
|
1575
|
-
max-height: 230px;
|
|
1576
|
-
}
|
|
1577
|
-
|
|
1578
|
-
.gm-monitoring-table table {
|
|
1579
|
-
min-width: 620px;
|
|
1580
|
-
}
|
|
1581
|
-
|
|
1582
|
-
.profile-title { font: 800 15px Inter, Segoe UI, sans-serif; fill: #f8fafc; }
|
|
1583
|
-
.profile-label { font: 800 13px Inter, Segoe UI, sans-serif; fill: #f8fafc; }
|
|
1584
|
-
.profile-small { font: 11px Inter, Segoe UI, sans-serif; fill: #cbd5e1; }
|
|
1585
|
-
.profile-axis { font: 11px Inter, Segoe UI, sans-serif; fill: #94a3b8; }
|
|
1586
|
-
.profile-water { font: 800 11px Inter, Segoe UI, sans-serif; fill: #67e8f9; }
|
|
1587
|
-
|
|
1588
|
-
.profile-notes {
|
|
1589
|
-
color: var(--muted);
|
|
1590
|
-
line-height: 1.55;
|
|
1591
|
-
}
|
|
1592
|
-
|
|
1593
|
-
table {
|
|
1594
|
-
width: 100%;
|
|
1595
|
-
border-collapse: collapse;
|
|
1596
|
-
min-width: 920px;
|
|
1597
|
-
}
|
|
1598
|
-
|
|
1599
|
-
th, td {
|
|
1600
|
-
padding: 13px 14px;
|
|
1601
|
-
border-bottom: 1px solid rgba(100, 116, 139, 0.22);
|
|
1602
|
-
text-align: left;
|
|
1603
|
-
vertical-align: top;
|
|
1604
|
-
font-size: 0.92rem;
|
|
1605
|
-
line-height: 1.48;
|
|
1606
|
-
overflow-wrap: anywhere;
|
|
1607
|
-
color: #cbd5e1;
|
|
1608
|
-
}
|
|
1609
|
-
|
|
1610
|
-
th {
|
|
1611
|
-
position: sticky;
|
|
1612
|
-
top: 0;
|
|
1613
|
-
z-index: 1;
|
|
1614
|
-
background: var(--surface-raised);
|
|
1615
|
-
color: var(--muted);
|
|
1616
|
-
font-size: 0.76rem;
|
|
1617
|
-
text-transform: uppercase;
|
|
1618
|
-
font-weight: 900;
|
|
1619
|
-
white-space: nowrap;
|
|
1620
|
-
overflow-wrap: normal;
|
|
1621
|
-
}
|
|
1622
|
-
|
|
1623
|
-
.status-pill, .chip {
|
|
1624
|
-
display: inline-flex;
|
|
1625
|
-
align-items: center;
|
|
1626
|
-
width: fit-content;
|
|
1627
|
-
padding: 6px 10px;
|
|
1628
|
-
border-radius: 999px;
|
|
1629
|
-
border: 1px solid rgba(102, 112, 133, 0.2);
|
|
1630
|
-
font-size: 0.78rem;
|
|
1631
|
-
font-weight: 800;
|
|
1632
|
-
line-height: 1;
|
|
1633
|
-
}
|
|
1634
|
-
|
|
1635
|
-
.trust-controls {
|
|
1636
|
-
display: flex;
|
|
1637
|
-
align-items: end;
|
|
1638
|
-
justify-content: space-between;
|
|
1639
|
-
gap: 12px;
|
|
1640
|
-
flex-wrap: wrap;
|
|
1641
|
-
padding: 14px;
|
|
1642
|
-
border: 1px solid var(--border);
|
|
1643
|
-
border-radius: var(--radius);
|
|
1644
|
-
background: var(--surface);
|
|
1645
|
-
}
|
|
1646
|
-
|
|
1647
|
-
.trust-controls label {
|
|
1648
|
-
display: grid;
|
|
1649
|
-
gap: 7px;
|
|
1650
|
-
flex: 1 1 320px;
|
|
1651
|
-
color: var(--muted);
|
|
1652
|
-
font-size: 0.75rem;
|
|
1653
|
-
text-transform: uppercase;
|
|
1654
|
-
font-weight: 900;
|
|
1655
|
-
}
|
|
1656
|
-
|
|
1657
|
-
.trust-controls input {
|
|
1658
|
-
width: 100%;
|
|
1659
|
-
border: 1px solid var(--border-strong);
|
|
1660
|
-
border-radius: 12px;
|
|
1661
|
-
background: #0b1120;
|
|
1662
|
-
color: var(--text);
|
|
1663
|
-
padding: 11px 12px;
|
|
1664
|
-
font: inherit;
|
|
1665
|
-
font-size: 0.92rem;
|
|
1666
|
-
outline: none;
|
|
1667
|
-
text-transform: none;
|
|
1668
|
-
font-weight: 600;
|
|
1669
|
-
}
|
|
1670
|
-
|
|
1671
|
-
.trust-controls input:focus {
|
|
1672
|
-
border-color: var(--primary);
|
|
1673
|
-
box-shadow: 0 0 0 4px rgba(6, 182, 212, 0.12);
|
|
1674
|
-
}
|
|
1675
|
-
|
|
1676
|
-
.filter-row {
|
|
1677
|
-
display: flex;
|
|
1678
|
-
gap: 8px;
|
|
1679
|
-
flex-wrap: wrap;
|
|
1680
|
-
}
|
|
1681
|
-
|
|
1682
|
-
.filter-button {
|
|
1683
|
-
border: 1px solid var(--border-strong);
|
|
1684
|
-
border-radius: 999px;
|
|
1685
|
-
background: var(--surface-soft);
|
|
1686
|
-
color: var(--muted);
|
|
1687
|
-
padding: 9px 12px;
|
|
1688
|
-
font: inherit;
|
|
1689
|
-
font-size: 0.8rem;
|
|
1690
|
-
font-weight: 850;
|
|
1691
|
-
cursor: pointer;
|
|
1692
|
-
}
|
|
1693
|
-
|
|
1694
|
-
.filter-button.active {
|
|
1695
|
-
background: var(--primary);
|
|
1696
|
-
color: #06111f;
|
|
1697
|
-
border-color: var(--primary);
|
|
1698
|
-
}
|
|
1699
|
-
|
|
1700
|
-
.evidence-grid {
|
|
1701
|
-
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
|
|
1702
|
-
}
|
|
1703
|
-
|
|
1704
|
-
.evidence-card {
|
|
1705
|
-
display: grid;
|
|
1706
|
-
gap: 11px;
|
|
1707
|
-
}
|
|
1708
|
-
|
|
1709
|
-
.evidence-card > div {
|
|
1710
|
-
display: flex;
|
|
1711
|
-
justify-content: space-between;
|
|
1712
|
-
gap: 12px;
|
|
1713
|
-
color: var(--muted);
|
|
1714
|
-
font-size: 0.86rem;
|
|
1715
|
-
}
|
|
1716
|
-
|
|
1717
|
-
.evidence-card h3 {
|
|
1718
|
-
color: var(--text);
|
|
1719
|
-
font-size: 1.02rem;
|
|
1720
|
-
}
|
|
1721
|
-
|
|
1722
|
-
.evidence-card small {
|
|
1723
|
-
color: var(--warning);
|
|
1724
|
-
font-weight: 800;
|
|
1725
|
-
line-height: 1.45;
|
|
1726
|
-
}
|
|
1727
|
-
|
|
1728
|
-
.evidence-actions {
|
|
1729
|
-
display: flex;
|
|
1730
|
-
flex-wrap: wrap;
|
|
1731
|
-
gap: 8px;
|
|
1732
|
-
padding-top: 4px;
|
|
1733
|
-
}
|
|
1734
|
-
|
|
1735
|
-
.evidence-actions a, .evidence-actions button {
|
|
1736
|
-
border: 1px solid var(--border-strong);
|
|
1737
|
-
border-radius: 10px;
|
|
1738
|
-
background: #0b1120;
|
|
1739
|
-
color: #67e8f9;
|
|
1740
|
-
padding: 8px 9px;
|
|
1741
|
-
font: inherit;
|
|
1742
|
-
font-size: 0.78rem;
|
|
1743
|
-
font-weight: 850;
|
|
1744
|
-
text-decoration: none;
|
|
1745
|
-
cursor: pointer;
|
|
1746
|
-
}
|
|
1747
|
-
|
|
1748
|
-
.evidence-actions button[data-state="verified"] {
|
|
1749
|
-
color: var(--success);
|
|
1750
|
-
border-color: rgba(22, 135, 93, 0.28);
|
|
1751
|
-
background: var(--success-soft);
|
|
1752
|
-
}
|
|
1753
|
-
|
|
1754
|
-
.evidence-actions button[data-state="flagged"] {
|
|
1755
|
-
color: var(--warning);
|
|
1756
|
-
border-color: rgba(183, 121, 31, 0.28);
|
|
1757
|
-
background: var(--warning-soft);
|
|
1758
|
-
}
|
|
1759
|
-
|
|
1760
|
-
.empty-state {
|
|
1761
|
-
padding: 18px;
|
|
1762
|
-
border: 1px dashed var(--border);
|
|
1763
|
-
border-radius: var(--radius);
|
|
1764
|
-
background: var(--surface);
|
|
1765
|
-
color: var(--muted);
|
|
1766
|
-
line-height: 1.6;
|
|
1767
|
-
}
|
|
1768
|
-
|
|
1769
|
-
.audit-drawer {
|
|
1770
|
-
scroll-margin-top: 24px;
|
|
1771
|
-
border: 1px solid var(--border);
|
|
1772
|
-
border-radius: var(--radius);
|
|
1773
|
-
background: var(--surface);
|
|
1774
|
-
overflow: hidden;
|
|
1775
|
-
}
|
|
1776
|
-
|
|
1777
|
-
.audit-drawer summary {
|
|
1778
|
-
cursor: pointer;
|
|
1779
|
-
list-style: none;
|
|
1780
|
-
padding: 20px;
|
|
1781
|
-
display: flex;
|
|
1782
|
-
align-items: center;
|
|
1783
|
-
justify-content: space-between;
|
|
1784
|
-
gap: 16px;
|
|
1785
|
-
}
|
|
1786
|
-
|
|
1787
|
-
.audit-drawer summary::-webkit-details-marker { display: none; }
|
|
1788
|
-
.audit-drawer summary span:first-child { display: grid; gap: 5px; }
|
|
1789
|
-
.audit-drawer small { color: var(--muted); }
|
|
1790
|
-
|
|
1791
|
-
.disclosure-hint {
|
|
1792
|
-
border: 1px solid var(--border-strong);
|
|
1793
|
-
border-radius: 999px;
|
|
1794
|
-
padding: 7px 11px;
|
|
1795
|
-
color: #67e8f9;
|
|
1796
|
-
background: var(--primary-soft);
|
|
1797
|
-
font-weight: 800;
|
|
1798
|
-
white-space: nowrap;
|
|
1799
|
-
}
|
|
1800
|
-
|
|
1801
|
-
.audit-content {
|
|
1802
|
-
border-top: 1px solid var(--border);
|
|
1803
|
-
padding: 20px;
|
|
1804
|
-
display: grid;
|
|
1805
|
-
gap: 20px;
|
|
1806
|
-
}
|
|
1807
|
-
|
|
1808
|
-
.outcome-strip {
|
|
1809
|
-
display: grid;
|
|
1810
|
-
grid-template-columns: repeat(auto-fill, minmax(34px, 1fr));
|
|
1811
|
-
gap: 6px;
|
|
1812
|
-
}
|
|
1813
|
-
|
|
1814
|
-
.outcome-cell {
|
|
1815
|
-
min-height: 30px;
|
|
1816
|
-
border: 1px solid var(--border);
|
|
1817
|
-
border-radius: 10px;
|
|
1818
|
-
display: inline-flex;
|
|
1819
|
-
align-items: center;
|
|
1820
|
-
justify-content: center;
|
|
1821
|
-
font-size: 0.78rem;
|
|
1822
|
-
font-weight: 900;
|
|
1823
|
-
}
|
|
1824
|
-
|
|
1825
|
-
.legend-row, .chip-row {
|
|
1826
|
-
display: flex;
|
|
1827
|
-
flex-wrap: wrap;
|
|
1828
|
-
gap: 10px;
|
|
1829
|
-
color: var(--muted);
|
|
1830
|
-
}
|
|
1831
|
-
|
|
1832
|
-
.legend-row strong { color: var(--text); }
|
|
1833
|
-
|
|
1834
|
-
.audit-page-grid {
|
|
1835
|
-
grid-template-columns: repeat(auto-fit, minmax(270px, 1fr));
|
|
1836
|
-
}
|
|
1837
|
-
|
|
1838
|
-
.audit-page-card {
|
|
1839
|
-
display: grid;
|
|
1840
|
-
gap: 12px;
|
|
1841
|
-
}
|
|
1842
|
-
|
|
1843
|
-
.audit-page-card > div:first-child {
|
|
1844
|
-
display: grid;
|
|
1845
|
-
gap: 4px;
|
|
1846
|
-
}
|
|
1847
|
-
|
|
1848
|
-
.audit-page-card > div:first-child span, .audit-page-card li {
|
|
1849
|
-
color: var(--muted);
|
|
1850
|
-
line-height: 1.5;
|
|
1851
|
-
}
|
|
1852
|
-
|
|
1853
|
-
.stage-chip {
|
|
1854
|
-
color: #67e8f9;
|
|
1855
|
-
background: var(--primary-soft);
|
|
1856
|
-
border-color: rgba(6, 182, 212, 0.32);
|
|
1857
|
-
}
|
|
1858
|
-
|
|
1859
|
-
.footer-grid {
|
|
1860
|
-
grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
|
|
1861
|
-
}
|
|
1862
|
-
|
|
1863
|
-
.footer-card {
|
|
1864
|
-
display: grid;
|
|
1865
|
-
gap: 9px;
|
|
1866
|
-
}
|
|
1867
|
-
|
|
1868
|
-
.footer-card p, .footer-card li {
|
|
1869
|
-
color: var(--muted);
|
|
1870
|
-
line-height: 1.58;
|
|
1871
|
-
}
|
|
1872
|
-
|
|
1873
|
-
tr[hidden] { display: none; }
|
|
1874
|
-
|
|
1875
|
-
.toast-region {
|
|
1876
|
-
position: fixed;
|
|
1877
|
-
top: 18px;
|
|
1878
|
-
right: 18px;
|
|
1879
|
-
z-index: 60;
|
|
1880
|
-
display: grid;
|
|
1881
|
-
gap: 8px;
|
|
1882
|
-
pointer-events: none;
|
|
1883
|
-
}
|
|
1884
|
-
|
|
1885
|
-
.toast {
|
|
1886
|
-
max-width: 320px;
|
|
1887
|
-
padding: 11px 14px;
|
|
1888
|
-
border: 1px solid #334155;
|
|
1889
|
-
border-radius: 12px;
|
|
1890
|
-
background: #0b1220;
|
|
1891
|
-
color: #f8fafc;
|
|
1892
|
-
box-shadow: 0 18px 38px rgba(11, 18, 32, 0.22);
|
|
1893
|
-
font-size: 0.86rem;
|
|
1894
|
-
font-weight: 700;
|
|
1895
|
-
}
|
|
1896
|
-
|
|
1897
|
-
@media (max-width: 980px) {
|
|
1898
|
-
.layout { padding: 14px 16px 96px; max-width: 100%; }
|
|
1899
|
-
.sidebar { position: static; min-height: auto; align-items: flex-start; flex-direction: column; }
|
|
1900
|
-
.nav-list { justify-content: flex-start; }
|
|
1901
|
-
.hero-main { grid-template-columns: 1fr; }
|
|
1902
|
-
}
|
|
1903
|
-
|
|
1904
|
-
@media (max-width: 620px) {
|
|
1905
|
-
body { padding-bottom: 0; }
|
|
1906
|
-
.layout {
|
|
1907
|
-
display: grid;
|
|
1908
|
-
width: min(100vw, 390px);
|
|
1909
|
-
max-width: 390px;
|
|
1910
|
-
margin: 0;
|
|
1911
|
-
padding: 10px;
|
|
1912
|
-
gap: 18px;
|
|
1913
|
-
overflow: hidden;
|
|
1914
|
-
}
|
|
1915
|
-
.sidebar, .content, .hero, .data-section, .audit-drawer, .footer-grid {
|
|
1916
|
-
width: 100%;
|
|
1917
|
-
max-width: 100%;
|
|
1918
|
-
}
|
|
1919
|
-
.sidebar { padding: 14px; min-height: auto; border-radius: 14px; }
|
|
1920
|
-
.brand { gap: 8px; padding-bottom: 0; margin-bottom: 0; }
|
|
1921
|
-
.brand span { display: none; }
|
|
1922
|
-
.nav-list {
|
|
1923
|
-
width: 100%;
|
|
1924
|
-
display: grid;
|
|
1925
|
-
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
1926
|
-
gap: 5px;
|
|
1927
|
-
}
|
|
1928
|
-
.nav-list a {
|
|
1929
|
-
min-width: 0;
|
|
1930
|
-
padding: 7px 8px;
|
|
1931
|
-
font-size: 0.74rem;
|
|
1932
|
-
line-height: 1.25;
|
|
1933
|
-
}
|
|
1934
|
-
.content { margin-top: 18px; }
|
|
1935
|
-
.hero { padding: 20px; width: 100%; max-width: 100%; }
|
|
1936
|
-
.hero-main { width: 100%; max-width: 100%; grid-template-columns: minmax(0, 1fr); }
|
|
1937
|
-
.hero-main > div, .hero-meta, .status-badge-row {
|
|
1938
|
-
width: 100%;
|
|
1939
|
-
max-width: min(320px, 100%);
|
|
1940
|
-
}
|
|
1941
|
-
.executive-grid, .metric-grid, .card-grid, .evidence-grid, .footer-grid { grid-template-columns: 1fr; }
|
|
1942
|
-
.review-bar { position: static; transform: none; width: 100%; margin-top: 18px; border-radius: 18px; align-items: stretch; }
|
|
1943
|
-
.review-bar.visible { transform: none; }
|
|
1944
|
-
.review-actions { justify-content: flex-start; }
|
|
1945
|
-
h1 { font-size: 1.72rem; max-width: min(320px, 100%); line-height: 1.08; }
|
|
1946
|
-
.hero-subtitle { max-width: min(320px, 100%); font-size: 0.96rem; line-height: 1.35; overflow-wrap: anywhere; }
|
|
1947
|
-
.hero-summary { max-width: min(320px, 100%); font-size: 0.95rem; line-height: 1.55; }
|
|
1948
|
-
.status-badge-row { display: grid; grid-template-columns: minmax(0, 1fr); }
|
|
1949
|
-
.status-badge {
|
|
1950
|
-
width: 100%;
|
|
1951
|
-
max-width: 100%;
|
|
1952
|
-
min-width: 0;
|
|
1953
|
-
padding: 9px 10px;
|
|
1954
|
-
border-radius: 18px;
|
|
1955
|
-
}
|
|
1956
|
-
.status-badge span, .status-badge strong {
|
|
1957
|
-
min-width: 0;
|
|
1958
|
-
overflow-wrap: anywhere;
|
|
1959
|
-
word-break: break-word;
|
|
1960
|
-
}
|
|
1961
|
-
.profile-shell {
|
|
1962
|
-
width: 100%;
|
|
1963
|
-
border-radius: 14px;
|
|
1964
|
-
}
|
|
1965
|
-
.borehole-profile, .ground-cross-section {
|
|
1966
|
-
min-width: 520px;
|
|
1967
|
-
max-height: none;
|
|
1968
|
-
}
|
|
1969
|
-
.profile-legend-row {
|
|
1970
|
-
grid-template-columns: 16px minmax(54px, 0.7fr) minmax(78px, 0.8fr);
|
|
1971
|
-
gap: 8px;
|
|
1972
|
-
font-size: 0.75rem;
|
|
1973
|
-
}
|
|
1974
|
-
.profile-legend-row span:nth-of-type(2),
|
|
1975
|
-
.profile-legend-row em {
|
|
1976
|
-
grid-column: 2 / -1;
|
|
1977
|
-
text-align: left;
|
|
1978
|
-
}
|
|
1979
|
-
table { min-width: 760px; }
|
|
1980
|
-
}
|
|
1278
|
+
:root{--bg:#f6f7f9;--panel:#fff;--ink:#172033;--muted:#697386;--line:#d8dee9;--soft:#eef2f7;--good:#1f8f5f;--warn:#b7791f;--bad:#c2410c;--blue:#2563eb;--deep:#0f172a;--made:#8b5a2b;--clay:#c98b67;--sand:#f5d77b;--gravel:#a3a3a3;--shadow:0 10px 25px rgba(23,32,51,.08);--radius:16px}
|
|
1279
|
+
*{box-sizing:border-box}html,body{margin:0;min-height:100%}body{background:var(--bg);color:var(--ink);font-family:Inter,ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;font-variant-numeric:tabular-nums}button,select{font:inherit}h1,h2,p{margin:0}
|
|
1280
|
+
header{background:linear-gradient(135deg,#0f172a,#1e293b 55%,#334155);color:#fff;padding:28px 36px}.header-top{display:flex;justify-content:space-between;align-items:flex-start;gap:22px}h1{font-size:28px;letter-spacing:0;line-height:1.08}.subtitle{margin-top:8px;color:#cbd5e1;line-height:1.5;max-width:1120px}.badge-row{display:flex;flex-wrap:wrap;gap:8px;margin-top:18px}.badge{display:inline-flex;border-radius:999px;padding:7px 10px;background:rgba(255,255,255,.11);border:1px solid rgba(255,255,255,.16);color:#e5e7eb;font-size:12px;font-weight:750}.run-card{min-width:315px;background:rgba(255,255,255,.09);border:1px solid rgba(255,255,255,.16);border-radius:16px;padding:16px;font-size:13px}.run-card div{display:flex;justify-content:space-between;gap:14px;padding:4px 0;color:#dbeafe}.run-card span:first-child{color:#a7b3c7}
|
|
1281
|
+
main{padding:24px 28px 42px;max-width:1760px;margin:0 auto}.summary-grid{display:grid;grid-template-columns:repeat(5,minmax(160px,1fr));gap:14px;margin-bottom:16px}.metric{background:#fff;border:1px solid var(--line);border-radius:var(--radius);box-shadow:var(--shadow);padding:16px}.metric .label{color:var(--muted);font-size:11px;text-transform:uppercase;letter-spacing:.06em;margin-bottom:8px}.metric .value{font-size:25px;font-weight:850;letter-spacing:0}.metric .note{color:var(--muted);font-size:12px;margin-top:6px;line-height:1.35}.status-good{color:var(--good)}.status-warn{color:var(--warn)}.status-blue{color:var(--blue)}.status-bad{color:var(--bad)}
|
|
1282
|
+
.view-nav{display:flex;gap:8px;flex-wrap:wrap;margin-bottom:16px}.nav-btn{border:1px solid #cbd5e1;background:#fff;color:#172033;border-radius:999px;padding:9px 12px;font-weight:800;font-size:13px;cursor:pointer}.nav-btn:hover{border-color:var(--blue);color:var(--blue)}.nav-btn.active{background:var(--blue);border-color:var(--blue);color:#fff}.view{display:none}.view.active{display:block}
|
|
1283
|
+
.panel{background:#fff;border:1px solid var(--line);border-radius:var(--radius);box-shadow:var(--shadow);overflow:hidden}.panel h2{font-size:16px;margin:0;padding:15px 17px;border-bottom:1px solid var(--line);display:flex;justify-content:space-between;gap:12px;align-items:center}.panel h2 small{color:var(--muted);font-weight:500}.panel-body{padding:15px}.review-grid{display:grid;grid-template-columns:minmax(340px,1.02fr) minmax(340px,.78fr) minmax(380px,1.08fr);gap:16px;align-items:start}
|
|
1284
|
+
.toolbar{display:flex;flex-wrap:wrap;gap:8px;margin-bottom:10px}.tool-btn{border:1px solid #cbd5e1;background:#fff;color:#172033;border-radius:999px;padding:7px 10px;font-weight:750;font-size:12px;cursor:pointer}.tool-btn.active{background:var(--blue);border-color:var(--blue);color:#fff}
|
|
1285
|
+
.mock-page{position:relative;width:100%;aspect-ratio:.707/1;background:#fff;border:1px solid #cbd5e1;border-radius:12px;overflow:hidden}.page-title{position:absolute;left:5%;top:3.2%;width:90%;height:5%;border-bottom:2px solid #111827;font-weight:850;font-size:clamp(12px,1vw,18px);display:flex;align-items:center;justify-content:space-between}.page-title span:last-child{color:#475569}.page-meta{position:absolute;left:5%;top:9.5%;width:90%;height:8.4%;border:1px solid #94a3b8;display:grid;grid-template-columns:1fr 1fr;font-size:clamp(8px,.62vw,11px)}.page-meta div{padding:4px 6px;border-bottom:1px solid #e2e8f0;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}
|
|
1286
|
+
.log-frame{position:absolute;left:5%;top:20%;width:90%;height:72%;border:2px solid #334155}.col{position:absolute;top:0;height:100%;border-right:1px solid #64748b}.c-depth{left:0;width:11%}.c-lith{left:11%;width:10%}.c-desc{left:21%;width:38%}.c-sample{left:59%;width:13%}.c-spt{left:72%;width:12%}.c-water{left:84%;width:8%}.c-remarks{left:92%;width:8%;border-right:none}.col-label{position:absolute;top:0;height:6.2%;width:100%;background:#e2e8f0;border-bottom:1px solid #64748b;font-size:clamp(6px,.55vw,9px);font-weight:800;display:flex;justify-content:center;align-items:center;text-align:center}
|
|
1287
|
+
.depth-tick{position:absolute;left:0;width:100%;border-top:1px solid #cbd5e1;font-size:clamp(6px,.55vw,9px);color:#334155}.depth-tick span{position:absolute;left:5%;top:-7px;background:#fff;padding-right:2px}.desc-text{position:absolute;left:23%;width:34%;font-size:clamp(6.5px,.58vw,10px);line-height:1.18;color:#111827;overflow:hidden}.hatch{position:absolute;left:12%;width:8%;border-left:1px solid #94a3b8;border-right:1px solid #94a3b8;background:repeating-linear-gradient(45deg,rgba(15,23,42,.24) 0 2px,transparent 2px 7px)}.hatch.made{background:repeating-linear-gradient(135deg,rgba(139,90,43,.55) 0 4px,rgba(139,90,43,.18) 4px 8px)}.hatch.clay{background:repeating-linear-gradient(45deg,rgba(111,78,55,.3) 0 2px,transparent 2px 7px),#f3d3c1}.hatch.sand{background:radial-gradient(circle,rgba(15,23,42,.35) 1px,transparent 1.5px) 0 0/8px 8px,#fde68a}.hatch.gravel{background:radial-gradient(circle,rgba(15,23,42,.35) 1.5px,transparent 2px) 0 0/10px 10px,repeating-linear-gradient(135deg,transparent 0 7px,rgba(15,23,42,.2) 7px 9px),#d4d4d4}.layer-line{position:absolute;left:11%;width:48%;border-top:2px solid #111827}.layer-line.review{border-top:2px dashed var(--warn)}
|
|
1288
|
+
.spt-text,.water-text{position:absolute;font-size:clamp(6.5px,.58vw,10px);color:#111827;font-weight:850}.spt-text{left:75%}.water-text{left:86%;color:#0369a1}.evidence-box{position:absolute;border:2px solid var(--blue);background:rgba(37,99,235,.10);border-radius:4px;opacity:.55;cursor:pointer;transition:.15s ease;padding:0;color:#172033;text-align:left;overflow:hidden}.evidence-box:hover,.evidence-box.active,.evidence-box:focus-visible{opacity:1;background:rgba(37,99,235,.18);box-shadow:0 0 0 3px rgba(37,99,235,.16);z-index:20;outline:none}.evidence-box.warn{border-color:var(--warn);background:rgba(183,121,31,.12)}.hidden-box{display:none!important}
|
|
1289
|
+
.layout-source{width:100%}.layout-meta{display:flex;flex-wrap:wrap;gap:8px;margin-bottom:10px;color:#475569;font-size:12px}.layout-meta span{display:inline-flex;align-items:center;border:1px solid #d8dee9;background:#f8fafc;border-radius:999px;padding:5px 8px;max-width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.layout-page{position:relative;width:100%;background:linear-gradient(#fff,#fbfdff);border:1px solid #cbd5e1;border-radius:12px;overflow:hidden;box-shadow:inset 0 0 0 1px #eef2f7}.layout-page:before{content:"";position:absolute;inset:0;background:linear-gradient(90deg,rgba(148,163,184,.16) 1px,transparent 1px),linear-gradient(rgba(148,163,184,.16) 1px,transparent 1px);background-size:8.33% 8.33%;pointer-events:none}.layout-grid-label{position:absolute;left:10px;top:8px;z-index:1;background:rgba(255,255,255,.88);border:1px solid #e2e8f0;border-radius:999px;padding:5px 8px;color:#475569;font-size:11px;font-weight:800}.layout-region-text{position:absolute;inset:3px;font-size:clamp(6.5px,.54vw,10px);line-height:1.16;color:#172033;overflow:hidden;pointer-events:none}
|
|
1290
|
+
.legend{display:flex;flex-wrap:wrap;gap:10px;margin-top:10px;font-size:12px;color:var(--muted)}.legend-item{display:flex;align-items:center;gap:7px}.swatch{width:20px;height:12px;border-radius:3px;border:2px solid var(--blue);background:rgba(37,99,235,.12)}.swatch.warn{border-color:var(--warn);background:rgba(183,121,31,.12)}.swatch.good{border-color:var(--good);background:rgba(31,143,95,.12)}.swatch.bad{border-color:var(--bad);background:rgba(194,65,12,.12)}.swatch.made{background:var(--made);border-color:rgba(15,23,42,.2)}.swatch.clay{background:var(--clay);border-color:rgba(15,23,42,.2)}.swatch.sand{background:var(--sand);border-color:rgba(15,23,42,.2)}.swatch.gravel{background:var(--gravel);border-color:rgba(15,23,42,.2)}
|
|
1291
|
+
.svg-wrap{display:flex;justify-content:center;background:linear-gradient(#fff,#f8fafc);border:1px solid #e2e8f0;border-radius:13px;padding:8px;overflow:auto}svg text{font-family:Inter,ui-sans-serif,system-ui,sans-serif}.log-highlight{cursor:pointer;transition:.15s ease}.log-highlight.active{filter:drop-shadow(0 0 6px rgba(37,99,235,.72))}
|
|
1292
|
+
.fields{display:grid;gap:9px;max-height:694px;overflow:auto;padding-right:4px}.field-card{border:1px solid #e2e8f0;border-radius:12px;padding:11px;cursor:pointer;transition:.15s ease;background:#fff;text-align:left}.field-card:hover,.field-card.active{border-color:var(--blue);box-shadow:0 0 0 3px rgba(37,99,235,.12)}.field-title{display:flex;justify-content:space-between;gap:10px;align-items:flex-start;font-weight:850}.field-value{margin-top:6px;color:#111827;font-size:13.5px;line-height:1.32}.field-meta{display:flex;flex-wrap:wrap;gap:8px;margin-top:9px;color:var(--muted);font-size:12px}.pill{display:inline-flex;align-items:center;border-radius:999px;padding:4px 8px;background:#eef2f7;color:#475569;font-size:12px;font-weight:800}.pill.good{background:#dcfce7;color:#166534}.pill.warn{background:#fef3c7;color:#92400e}.pill.bad{background:#ffedd5;color:#9a3412}.pill.blue{background:#dbeafe;color:#1d4ed8}.confidence{margin-top:9px;height:8px;background:#e2e8f0;border-radius:999px;overflow:hidden}.confidence span{display:block;height:100%;background:linear-gradient(90deg,var(--good),#65a30d)}.confidence span.warn{background:linear-gradient(90deg,var(--warn),#d97706)}.confidence span.bad{background:linear-gradient(90deg,var(--bad),#ef4444)}
|
|
1293
|
+
.geo-grid{display:grid;grid-template-columns:minmax(520px,1.18fr) minmax(360px,.82fr);gap:16px;align-items:start}.map-toolbar{display:flex;justify-content:space-between;gap:12px;align-items:center;margin-bottom:10px;flex-wrap:wrap}.map-canvas{border:1px solid #d8dee9;border-radius:14px;overflow:auto;background:#e2e8f0}.notice{border:1px solid #fde68a;background:#fffbeb;color:#78350f;padding:10px 12px;border-radius:12px;font-size:13px;line-height:1.45;margin-top:10px}.selected-panel{border:1px solid #dbeafe;border-radius:14px;background:#eff6ff;padding:13px;margin-bottom:11px}.selected-panel strong{font-size:18px}.selected-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:8px;margin-top:10px;font-size:13px;color:#334155}.crs-card{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:9px}.crs-box{border:1px solid #e2e8f0;border-radius:12px;padding:11px;background:#f8fafc}.crs-box .k{font-size:11px;color:var(--muted);text-transform:uppercase;letter-spacing:.05em;margin-bottom:5px}.crs-box .v{font-weight:850}.crs-box .s{font-size:12px;color:var(--muted);margin-top:5px;line-height:1.35}.section-panel{margin-top:16px}.cross-section-wrap{overflow:auto;background:linear-gradient(#fff,#f8fafc);border:1px solid #e2e8f0;border-radius:14px;padding:8px}
|
|
1294
|
+
.data-grid{display:grid;grid-template-columns:1fr 1fr;gap:16px;align-items:start}.table-panel-grid{display:grid;gap:12px}.embedded-panel{box-shadow:none}.table-scroll{overflow:auto;max-height:360px}table{width:100%;border-collapse:collapse;font-size:13px}th,td{text-align:left;padding:9px;border-bottom:1px solid #e2e8f0;vertical-align:top;color:#172033}th{color:#475569;background:#f8fafc;font-size:11px;text-transform:uppercase;letter-spacing:.04em}code{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;font-size:12px}.tabs{display:flex;gap:8px;padding:0 15px 13px;border-bottom:1px solid var(--line);flex-wrap:wrap}.tab{border:1px solid #cbd5e1;background:#fff;color:#172033;border-radius:999px;padding:7px 10px;font-weight:800;font-size:12px;cursor:pointer}.tab.active{background:var(--blue);border-color:var(--blue);color:#fff}.tab-content{display:none}.tab-content.active{display:block}.warning-list{display:grid;gap:10px}.warning{border-left:4px solid var(--warn);background:#fffbeb;border-radius:10px;padding:12px;color:#78350f;font-size:13px}.pipeline{display:grid;gap:10px}.step{display:grid;grid-template-columns:34px 1fr auto;gap:10px;align-items:center;border:1px solid #e2e8f0;border-radius:12px;padding:10px}.num{width:28px;height:28px;border-radius:50%;display:flex;align-items:center;justify-content:center;background:#dbeafe;color:#1d4ed8;font-weight:900}.step strong{display:block}.step small{color:var(--muted)}pre{background:#0f172a;color:#e2e8f0;border-radius:12px;padding:14px;overflow:auto;font-size:12px;line-height:1.45;max-height:620px}.empty-light{border:1px dashed #cbd5e1;background:#f8fafc;color:#475569;border-radius:12px;padding:16px;line-height:1.5}
|
|
1295
|
+
@media(max-width:1400px){.review-grid,.geo-grid,.data-grid{grid-template-columns:1fr}.fields{max-height:none}.summary-grid{grid-template-columns:repeat(2,minmax(0,1fr))}.header-top{flex-direction:column}.run-card{min-width:0;width:100%}}@media(max-width:720px){main{padding:16px}header{padding:22px 18px}.summary-grid,.crs-card{grid-template-columns:1fr}.metric .value{font-size:22px}.review-grid{gap:12px}.panel h2{align-items:flex-start;flex-direction:column}.legend{display:grid;grid-template-columns:1fr}.mock-page{min-height:560px}.page-meta{font-size:8px}.view-nav{display:grid}.nav-btn{width:100%}.selected-grid{grid-template-columns:1fr}}
|
|
1981
1296
|
</style>
|
|
1982
1297
|
</head>
|
|
1983
1298
|
<body>
|
|
1984
|
-
<
|
|
1985
|
-
<
|
|
1986
|
-
<div
|
|
1987
|
-
<
|
|
1988
|
-
<
|
|
1299
|
+
<header>
|
|
1300
|
+
<div class="header-top">
|
|
1301
|
+
<div>
|
|
1302
|
+
<h1>geotechCLI Integrated Vision + Geospatial Review</h1>
|
|
1303
|
+
<p class="subtitle">${escapeHtml(dossier.summary)}</p>
|
|
1304
|
+
<div class="badge-row">
|
|
1305
|
+
<span class="badge">OCR: ${escapeHtml(model.run.models.ocr)}</span>
|
|
1306
|
+
<span class="badge">Vision: ${escapeHtml(model.run.models.vision)}</span>
|
|
1307
|
+
<span class="badge">Text / Agent: ${escapeHtml(model.run.models.text)}</span>
|
|
1308
|
+
<span class="badge">BYOK profile: ${escapeHtml(model.run.providerProfile)}</span>
|
|
1309
|
+
<span class="badge">CRS checked before map render</span>
|
|
1310
|
+
</div>
|
|
1989
1311
|
</div>
|
|
1990
|
-
<
|
|
1991
|
-
<
|
|
1992
|
-
<
|
|
1993
|
-
<
|
|
1994
|
-
<
|
|
1995
|
-
<
|
|
1996
|
-
<
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
1312
|
+
<div class="run-card">
|
|
1313
|
+
<div><span>Run ID</span><strong>${escapeHtml(model.run.id)}</strong></div>
|
|
1314
|
+
<div><span>Document</span><strong>${escapeHtml(model.run.document)}</strong></div>
|
|
1315
|
+
<div><span>Generated</span><strong>${escapeHtml(generatedDate)}</strong></div>
|
|
1316
|
+
<div><span>Schema</span><strong>${escapeHtml(model.schemaVersion)}</strong></div>
|
|
1317
|
+
<div><span>geotechCLI</span><strong>v${escapeHtml(GEOTECHCLI_VERSION)}</strong></div>
|
|
1318
|
+
<div><span>Status</span><strong>${escapeHtml(model.run.status.replace(/_/g, ' '))}</strong></div>
|
|
1319
|
+
</div>
|
|
1320
|
+
</div>
|
|
1321
|
+
</header>
|
|
1322
|
+
<main>
|
|
1323
|
+
<section class="summary-grid" aria-label="Integrated review summary">
|
|
1324
|
+
${renderLightMetric('Overall confidence', integratedPercent(model.quality.overallConfidence), 'Borehole extraction confidence after validation.', model.quality.overallConfidence >= 0.85 ? 'good' : 'warn')}
|
|
1325
|
+
${renderLightMetric('Evidence coverage', integratedPercent(model.quality.evidenceCoverage), 'Rendered values retain source evidence where available.', model.quality.evidenceCoverage >= 0.85 ? 'good' : 'warn')}
|
|
1326
|
+
${renderLightMetric('Boreholes', String(model.boreholes.length), 'Mapped and included in A-A section.', 'blue')}
|
|
1327
|
+
${renderLightMetric('CRS', model.project.inputCrs, `Display coordinates: ${model.project.displayCrs}.`, model.project.inputCrs === 'unknown' ? 'warn' : 'good')}
|
|
1328
|
+
${renderLightMetric('Depth mapping', depthMapping === 'n/a' ? 'n/a' : `R2 ${depthMapping}`, 'Deterministic depth calibration from structured intervals.', depthMapping === 'n/a' ? 'warn' : 'good')}
|
|
1329
|
+
</section>
|
|
1330
|
+
<nav class="view-nav" aria-label="Review views">
|
|
1331
|
+
<button class="nav-btn active" type="button" data-view-target="reviewView">Extraction review</button>
|
|
1332
|
+
<button class="nav-btn" type="button" data-view-target="geoView">Map + A-A section</button>
|
|
1333
|
+
<button class="nav-btn" type="button" data-view-target="validationView">Validation + JSON</button>
|
|
1334
|
+
</nav>
|
|
1335
|
+
<section id="reviewView" class="view active">
|
|
1336
|
+
<div class="review-grid">
|
|
1337
|
+
<div class="panel">
|
|
1338
|
+
<h2>Source report evidence <small>GLM-OCR regions when available, reconstructed log otherwise</small></h2>
|
|
1339
|
+
<div class="panel-body">
|
|
1340
|
+
<div class="toolbar">
|
|
1341
|
+
<button class="tool-btn filter active" type="button" data-filter="all">All evidence</button>
|
|
1342
|
+
<button class="tool-btn filter" type="button" data-filter="header">Header</button>
|
|
1343
|
+
<button class="tool-btn filter" type="button" data-filter="coordinates">Coordinates</button>
|
|
1344
|
+
<button class="tool-btn filter" type="button" data-filter="totalDepth">Total depth</button>
|
|
1345
|
+
<button class="tool-btn filter" type="button" data-filter="strata">Strata</button>
|
|
1346
|
+
<button class="tool-btn filter" type="button" data-filter="spt">SPT</button>
|
|
1347
|
+
<button class="tool-btn filter" type="button" data-filter="water">Water</button>
|
|
1348
|
+
<button class="tool-btn filter" type="button" data-filter="parameter">Parameters</button>
|
|
1349
|
+
<button class="tool-btn filter" type="button" data-filter="table">Tables</button>
|
|
1350
|
+
</div>
|
|
1351
|
+
${renderLightSourcePage(selected, model)}
|
|
1352
|
+
<div class="legend">
|
|
1353
|
+
<span class="legend-item"><span class="swatch"></span>Accepted evidence</span>
|
|
1354
|
+
<span class="legend-item"><span class="swatch warn"></span>Review recommended</span>
|
|
1355
|
+
<span class="legend-item"><span class="swatch good"></span>Validated geometry</span>
|
|
1356
|
+
<span class="legend-item"><span class="swatch bad"></span>Blocking error, if present</span>
|
|
1357
|
+
</div>
|
|
2018
1358
|
</div>
|
|
2019
1359
|
</div>
|
|
2020
|
-
<div class="
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
</header>
|
|
2034
|
-
|
|
2035
|
-
${renderConfidenceBreakdown(dossier)}
|
|
2036
|
-
<section class="data-section" id="ground-model">
|
|
2037
|
-
<div class="section-heading">
|
|
2038
|
-
<h2>Ground Model</h2>
|
|
2039
|
-
<p>Decision-focused interpretation cards. The main view prioritizes engineering meaning, missing data, and verification needs.</p>
|
|
1360
|
+
<div class="panel"><h2>Validated strip log <small>deterministic renderer</small></h2><div class="panel-body">${renderLightStripLog(selected, model)}</div></div>
|
|
1361
|
+
<div class="panel"><h2>Extracted fields <small>schema + confidence + evidence</small></h2><div class="panel-body">${renderLightFields(selected, model)}</div></div>
|
|
1362
|
+
</div>
|
|
1363
|
+
</section>
|
|
1364
|
+
<section id="geoView" class="view">
|
|
1365
|
+
<div class="geo-grid">
|
|
1366
|
+
<div class="panel">
|
|
1367
|
+
<h2>Map-ready borehole view <small>source CRS guarded before visualization</small></h2>
|
|
1368
|
+
<div class="panel-body">
|
|
1369
|
+
<div class="map-toolbar"><span class="pill ${model.project.inputCrs === 'unknown' ? 'warn' : 'good'}">Input CRS: ${escapeHtml(model.project.inputCrs)}</span><span class="pill blue">Display: ${escapeHtml(model.project.displayCrs)}</span></div>
|
|
1370
|
+
${renderLightMap(model)}
|
|
1371
|
+
<div class="notice"><strong>CRS rule:</strong> no map or section should be treated as design-grade until source CRS, units, project location, and vertical datum have passed validation.</div>
|
|
1372
|
+
</div>
|
|
2040
1373
|
</div>
|
|
2041
|
-
<div class="
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
1374
|
+
<div class="panel"><h2>Selected borehole + CRS guardrail <small>same object drives log, map, and section</small></h2><div class="panel-body">${renderLightSelectedBorehole(selected, model)}</div></div>
|
|
1375
|
+
</div>
|
|
1376
|
+
<div class="panel section-panel">
|
|
1377
|
+
<h2>Professional A-A stratigraphic section <small>interpolated contacts + actual vertical borehole log columns</small></h2>
|
|
1378
|
+
<div class="panel-body">
|
|
1379
|
+
${renderLightCrossSection(model)}
|
|
1380
|
+
<div class="legend">
|
|
1381
|
+
<span class="legend-item"><span class="swatch made"></span>Made Ground / Fill</span>
|
|
1382
|
+
<span class="legend-item"><span class="swatch clay"></span>Clay</span>
|
|
1383
|
+
<span class="legend-item"><span class="swatch sand"></span>Sand / Silt</span>
|
|
1384
|
+
<span class="legend-item"><span class="swatch gravel"></span>Gravel / Rock</span>
|
|
1385
|
+
<span class="legend-item"><span style="width:30px;border-top:2px dashed #64748b;display:inline-block"></span>Interpolated contacts</span>
|
|
1386
|
+
</div>
|
|
1387
|
+
<div class="notice"><strong>Engineering note:</strong> strata between boreholes are conceptual interpolations for review. Directly extracted log columns are shown at borehole locations.</div>
|
|
2049
1388
|
</div>
|
|
2050
|
-
</
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
${renderBoreholeProfile(dossier.boreholeProfile)}
|
|
2055
|
-
${renderTrustTable(dossier)}
|
|
2056
|
-
${mainTables.map((table) => renderTable(table)).join('')}
|
|
2057
|
-
${renderFindingGroups(dossier)}
|
|
2058
|
-
${renderSourceEvidence(dossier)}
|
|
2059
|
-
${renderProcessingAudit(dossier, auditTables)}
|
|
2060
|
-
|
|
2061
|
-
<section class="footer-grid">
|
|
2062
|
-
${dossier.storedReview ? `
|
|
2063
|
-
<article class="footer-card">
|
|
2064
|
-
<h3>Stored review</h3>
|
|
2065
|
-
<p>Project: ${escapeHtml(dossier.storedReview.projectId)}</p>
|
|
2066
|
-
<p>Dataset: ${escapeHtml(dossier.storedReview.datasetName)}</p>
|
|
2067
|
-
<p>Review ID: ${escapeHtml(dossier.storedReview.reviewId)}</p>
|
|
2068
|
-
${dossier.storedReview.createdAt ? `<p>Created: ${escapeHtml(dossier.storedReview.createdAt)}</p>` : ''}
|
|
2069
|
-
</article>
|
|
2070
|
-
` : ''}
|
|
2071
|
-
${dossier.approval ? `
|
|
2072
|
-
<article class="footer-card">
|
|
2073
|
-
<h3>Approval</h3>
|
|
2074
|
-
<p>Dataset: ${escapeHtml(dossier.approval.datasetName)}</p>
|
|
2075
|
-
<p>Approved: ${escapeHtml(dossier.approval.approvedAt)}</p>
|
|
2076
|
-
<p>Approved by: ${escapeHtml(dossier.approval.approvedBy ?? 'Unspecified')}</p>
|
|
2077
|
-
${dossier.approval.rationale ? `<p>${escapeHtml(dossier.approval.rationale)}</p>` : ''}
|
|
2078
|
-
</article>
|
|
2079
|
-
` : ''}
|
|
2080
|
-
<article class="footer-card">
|
|
2081
|
-
<h3>Source notes</h3>
|
|
2082
|
-
<ul>${dossier.footerNotes.map((note) => `<li>${escapeHtml(note)}</li>`).join('')}</ul>
|
|
2083
|
-
</article>
|
|
2084
|
-
</section>
|
|
2085
|
-
</main>
|
|
2086
|
-
</div>
|
|
2087
|
-
${renderReviewBar(dossier)}
|
|
2088
|
-
<div class="toast-region" aria-live="polite" aria-atomic="true"></div>
|
|
1389
|
+
</div>
|
|
1390
|
+
</section>
|
|
1391
|
+
<section id="validationView" class="view">${renderLightValidation(dossier, model)}</section>
|
|
1392
|
+
</main>
|
|
2089
1393
|
<script>
|
|
2090
1394
|
(() => {
|
|
2091
|
-
const
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
const toastRegion = document.querySelector('.toast-region');
|
|
2095
|
-
const reviewBar = document.querySelector('.review-bar');
|
|
2096
|
-
let activeFilter = 'all';
|
|
2097
|
-
|
|
2098
|
-
const showToast = (message) => {
|
|
2099
|
-
if (!toastRegion) return;
|
|
2100
|
-
const toast = document.createElement('div');
|
|
2101
|
-
toast.className = 'toast';
|
|
2102
|
-
toast.textContent = message;
|
|
2103
|
-
toastRegion.appendChild(toast);
|
|
2104
|
-
window.setTimeout(() => toast.remove(), 2600);
|
|
2105
|
-
};
|
|
2106
|
-
|
|
2107
|
-
const applyTrustFilter = () => {
|
|
2108
|
-
const query = String(search?.value ?? '').trim().toLowerCase();
|
|
2109
|
-
rows.forEach((row) => {
|
|
2110
|
-
const text = row.getAttribute('data-search') ?? '';
|
|
2111
|
-
const review = row.getAttribute('data-review') ?? '';
|
|
2112
|
-
const queryMatch = !query || text.includes(query);
|
|
2113
|
-
const filterMatch = activeFilter === 'all' || review === activeFilter;
|
|
2114
|
-
row.hidden = !(queryMatch && filterMatch);
|
|
1395
|
+
const setActiveEvidence = (id) => {
|
|
1396
|
+
document.querySelectorAll('[data-id]').forEach((element) => {
|
|
1397
|
+
element.classList.toggle('active', element.getAttribute('data-id') === id);
|
|
2115
1398
|
});
|
|
2116
1399
|
};
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
1400
|
+
const firstEvidence = document.querySelector('[data-id]');
|
|
1401
|
+
if (firstEvidence) setActiveEvidence(firstEvidence.getAttribute('data-id'));
|
|
1402
|
+
document.addEventListener('click', (event) => {
|
|
1403
|
+
const evidence = event.target.closest('[data-id]');
|
|
1404
|
+
if (evidence) setActiveEvidence(evidence.getAttribute('data-id'));
|
|
1405
|
+
});
|
|
1406
|
+
document.querySelectorAll('[data-view-target]').forEach((button) => {
|
|
1407
|
+
button.addEventListener('click', () => {
|
|
1408
|
+
const target = button.getAttribute('data-view-target');
|
|
1409
|
+
document.querySelectorAll('[data-view-target]').forEach((candidate) => candidate.classList.toggle('active', candidate === button));
|
|
1410
|
+
document.querySelectorAll('.view').forEach((view) => view.classList.toggle('active', view.id === target));
|
|
1411
|
+
});
|
|
1412
|
+
});
|
|
1413
|
+
document.querySelectorAll('[data-tab-target]').forEach((button) => {
|
|
2129
1414
|
button.addEventListener('click', () => {
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
1415
|
+
const target = button.getAttribute('data-tab-target');
|
|
1416
|
+
document.querySelectorAll('[data-tab-target]').forEach((candidate) => candidate.classList.toggle('active', candidate === button));
|
|
1417
|
+
document.querySelectorAll('.tab-content').forEach((content) => content.classList.toggle('active', content.id === target));
|
|
2133
1418
|
});
|
|
2134
1419
|
});
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
: 'Evidence item flagged for engineering review in this local report view.');
|
|
1420
|
+
document.querySelectorAll('.filter').forEach((button) => {
|
|
1421
|
+
button.addEventListener('click', () => {
|
|
1422
|
+
const filter = button.getAttribute('data-filter') || 'all';
|
|
1423
|
+
document.querySelectorAll('.filter').forEach((candidate) => candidate.classList.toggle('active', candidate === button));
|
|
1424
|
+
document.querySelectorAll('.evidence-box').forEach((box) => {
|
|
1425
|
+
const visible = filter === 'all' || box.getAttribute('data-type') === filter;
|
|
1426
|
+
box.classList.toggle('hidden-box', !visible);
|
|
1427
|
+
});
|
|
2144
1428
|
});
|
|
2145
1429
|
});
|
|
2146
1430
|
})();
|
|
@@ -2148,4 +1432,7 @@ export function renderIngestDossierAsHtml(dossier) {
|
|
|
2148
1432
|
</body>
|
|
2149
1433
|
</html>`;
|
|
2150
1434
|
}
|
|
1435
|
+
export function renderIngestDossierAsHtml(dossier) {
|
|
1436
|
+
return renderIntegratedReviewOnlyHtml(dossier);
|
|
1437
|
+
}
|
|
2151
1438
|
//# sourceMappingURL=html.js.map
|