@geotechcli/core 0.4.124 → 0.4.125

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.
@@ -0,0 +1,59 @@
1
+ import type { BoreholeInterpretation, BoreholeLayer } from '../vision/index.js';
2
+ /**
3
+ * Deterministic borehole depth-continuity reconstruction.
4
+ *
5
+ * After multi-page borehole logs are merged ({@link mergeBoreholeLogPages}), the stitched layer
6
+ * sequence can still carry page-break artifacts: a boundary interval duplicated across two pages, a
7
+ * sub-millimetre rounding gap, or a small overlap where the same depth row was read twice. This module
8
+ * repairs those *page-break artifacts only* and records an explicit audit trail.
9
+ *
10
+ * Design rules (kept intentionally conservative so deterministic code never invents engineering data):
11
+ * - Never fabricate intervals and never silently hide a genuine anomaly.
12
+ * - Only repair small, page-break-shaped discrepancies (duplicates, micro-gaps, small overlaps).
13
+ * - Leave large/ambiguous overlaps and gaps untouched and flag them, so the authoritative
14
+ * `validateMergedBorehole` check still surfaces them as blocking/review findings downstream.
15
+ * - Be idempotent: a second pass over repaired layers produces zero further repairs.
16
+ */
17
+ export type BoreholeContinuityAction = 'trim-overlap' | 'drop-duplicate' | 'snap-gap' | 'flag-unrepairable';
18
+ export interface BoreholeContinuityRepairDepths {
19
+ from: number | null;
20
+ to: number | null;
21
+ }
22
+ export interface BoreholeContinuityRepair {
23
+ action: BoreholeContinuityAction;
24
+ boreholeId: string;
25
+ /** Index of the affected layer in the borehole's original (pre-repair) `layers` array. */
26
+ layerIndex: number;
27
+ before: BoreholeContinuityRepairDepths;
28
+ /** Repaired depths, or `null` when the layer was dropped or only flagged. */
29
+ after: BoreholeContinuityRepairDepths | null;
30
+ note: string;
31
+ }
32
+ export interface BoreholeContinuityMetrics {
33
+ overlapCount: number;
34
+ gapCount: number;
35
+ nonMonotonicCount: number;
36
+ duplicateCount: number;
37
+ }
38
+ export interface ReconstructBoreholeContinuityOptions {
39
+ /** Gaps at or below this size (m) are snapped closed as rounding artifacts. Default 0.05. */
40
+ gapSnapToleranceMeters?: number;
41
+ /** Overlaps at or below this size (m) are trimmed as page-break artifacts; larger overlaps are left intact and flagged. Default 0.5. */
42
+ maxRepairableOverlapMeters?: number;
43
+ }
44
+ /**
45
+ * Compute deterministic continuity metrics for an ordered (or unordered) layer list. Used for
46
+ * warnings and benchmark/regression assertions. Layers missing a numeric `depthFrom`/`depthTo` are
47
+ * ignored for the pairwise checks.
48
+ */
49
+ export declare function assessBoreholeContinuity(layers: BoreholeLayer[]): BoreholeContinuityMetrics;
50
+ /**
51
+ * Repair page-break continuity artifacts in a single merged borehole. Returns the (possibly new)
52
+ * borehole with repaired layers plus continuity warnings appended, and a typed list of the repairs
53
+ * applied. The input borehole is never mutated.
54
+ */
55
+ export declare function reconstructBoreholeContinuity(borehole: BoreholeInterpretation, options?: ReconstructBoreholeContinuityOptions): {
56
+ borehole: BoreholeInterpretation;
57
+ repairs: BoreholeContinuityRepair[];
58
+ };
59
+ //# sourceMappingURL=borehole-continuity.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"borehole-continuity.d.ts","sourceRoot":"","sources":["../../src/ingest/borehole-continuity.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,sBAAsB,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAEhF;;;;;;;;;;;;;;GAcG;AAEH,MAAM,MAAM,wBAAwB,GAChC,cAAc,GACd,gBAAgB,GAChB,UAAU,GACV,mBAAmB,CAAC;AAExB,MAAM,WAAW,8BAA8B;IAC7C,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,EAAE,EAAE,MAAM,GAAG,IAAI,CAAC;CACnB;AAED,MAAM,WAAW,wBAAwB;IACvC,MAAM,EAAE,wBAAwB,CAAC;IACjC,UAAU,EAAE,MAAM,CAAC;IACnB,0FAA0F;IAC1F,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,8BAA8B,CAAC;IACvC,6EAA6E;IAC7E,KAAK,EAAE,8BAA8B,GAAG,IAAI,CAAC;IAC7C,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,yBAAyB;IACxC,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,MAAM,CAAC;IACjB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,cAAc,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,oCAAoC;IACnD,6FAA6F;IAC7F,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC,wIAAwI;IACxI,0BAA0B,CAAC,EAAE,MAAM,CAAC;CACrC;AA+CD;;;;GAIG;AACH,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,aAAa,EAAE,GAAG,yBAAyB,CA8B3F;AAaD;;;;GAIG;AACH,wBAAgB,6BAA6B,CAC3C,QAAQ,EAAE,sBAAsB,EAChC,OAAO,GAAE,oCAAyC,GACjD;IAAE,QAAQ,EAAE,sBAAsB,CAAC;IAAC,OAAO,EAAE,wBAAwB,EAAE,CAAA;CAAE,CAsH3E"}
@@ -0,0 +1,163 @@
1
+ const DEFAULT_GAP_SNAP_TOLERANCE_M = 0.05;
2
+ const DEFAULT_MAX_REPAIRABLE_OVERLAP_M = 0.5;
3
+ const EPSILON_M = 1e-6;
4
+ function approxEqual(a, b, eps = EPSILON_M) {
5
+ return Math.abs(a - b) <= eps;
6
+ }
7
+ function round3(value) {
8
+ return Math.round(value * 1000) / 1000;
9
+ }
10
+ function fmt(value) {
11
+ return value == null ? 'n/a' : value.toFixed(2);
12
+ }
13
+ function normDescription(layer) {
14
+ return (layer.description ?? '').trim().toLowerCase();
15
+ }
16
+ function normUscs(layer) {
17
+ return (layer.uscsSymbol ?? '').trim().toUpperCase();
18
+ }
19
+ /** Two layers describe the same material when both their description and USCS symbol match. */
20
+ function sameMaterial(a, b) {
21
+ return normDescription(a) === normDescription(b) && normUscs(a) === normUscs(b);
22
+ }
23
+ function isNumericLayer(layer) {
24
+ return layer.depthFrom != null && layer.depthTo != null;
25
+ }
26
+ /** Keep the previous layer with the greatest bottom depth so the running boundary is monotonic. */
27
+ function pickDeeperPrev(current, candidate) {
28
+ if (!isNumericLayer(candidate))
29
+ return current;
30
+ if (current == null || candidate.depthTo >= current.depthTo)
31
+ return candidate;
32
+ return current;
33
+ }
34
+ /**
35
+ * Compute deterministic continuity metrics for an ordered (or unordered) layer list. Used for
36
+ * warnings and benchmark/regression assertions. Layers missing a numeric `depthFrom`/`depthTo` are
37
+ * ignored for the pairwise checks.
38
+ */
39
+ export function assessBoreholeContinuity(layers) {
40
+ let overlapCount = 0;
41
+ let gapCount = 0;
42
+ let nonMonotonicCount = 0;
43
+ let duplicateCount = 0;
44
+ for (const layer of layers) {
45
+ if (isNumericLayer(layer) && layer.depthTo < layer.depthFrom - EPSILON_M) {
46
+ nonMonotonicCount += 1;
47
+ }
48
+ }
49
+ const numeric = layers
50
+ .filter(isNumericLayer)
51
+ .slice()
52
+ .sort((a, b) => a.depthFrom - b.depthFrom || a.depthTo - b.depthTo);
53
+ for (let i = 1; i < numeric.length; i += 1) {
54
+ const prev = numeric[i - 1];
55
+ const cur = numeric[i];
56
+ if (approxEqual(cur.depthFrom, prev.depthFrom) && approxEqual(cur.depthTo, prev.depthTo)) {
57
+ duplicateCount += 1;
58
+ }
59
+ else if (cur.depthFrom < prev.depthTo - EPSILON_M) {
60
+ overlapCount += 1;
61
+ }
62
+ else if (cur.depthFrom > prev.depthTo + EPSILON_M) {
63
+ gapCount += 1;
64
+ }
65
+ }
66
+ return { overlapCount, gapCount, nonMonotonicCount, duplicateCount };
67
+ }
68
+ function makeRepair(action, boreholeId, layerIndex, before, after, note) {
69
+ return { action, boreholeId, layerIndex, before, after, note };
70
+ }
71
+ /**
72
+ * Repair page-break continuity artifacts in a single merged borehole. Returns the (possibly new)
73
+ * borehole with repaired layers plus continuity warnings appended, and a typed list of the repairs
74
+ * applied. The input borehole is never mutated.
75
+ */
76
+ export function reconstructBoreholeContinuity(borehole, options = {}) {
77
+ const gapTolerance = options.gapSnapToleranceMeters ?? DEFAULT_GAP_SNAP_TOLERANCE_M;
78
+ const maxOverlap = options.maxRepairableOverlapMeters ?? DEFAULT_MAX_REPAIRABLE_OVERLAP_M;
79
+ const repairs = [];
80
+ if (borehole.layers.length === 0) {
81
+ return { borehole, repairs };
82
+ }
83
+ const indexed = borehole.layers.map((layer, index) => ({ layer, index }));
84
+ const numeric = indexed.filter((entry) => isNumericLayer(entry.layer));
85
+ const nonNumeric = indexed.filter((entry) => !isNumericLayer(entry.layer));
86
+ // Stable sort numeric layers by (from, to), keeping original order as the tiebreaker.
87
+ numeric.sort((a, b) => {
88
+ const fromDelta = a.layer.depthFrom - b.layer.depthFrom;
89
+ if (fromDelta !== 0)
90
+ return fromDelta;
91
+ const toDelta = a.layer.depthTo - b.layer.depthTo;
92
+ if (toDelta !== 0)
93
+ return toDelta;
94
+ return a.index - b.index;
95
+ });
96
+ const kept = [];
97
+ let prev = null;
98
+ for (const { layer, index } of numeric) {
99
+ const from = layer.depthFrom;
100
+ const to = layer.depthTo;
101
+ if (prev == null) {
102
+ kept.push(layer);
103
+ prev = pickDeeperPrev(prev, layer);
104
+ continue;
105
+ }
106
+ const prevFrom = prev.depthFrom;
107
+ const prevTo = prev.depthTo;
108
+ // Exact duplicate boundary row repeated at a page break (same span + same material) -> drop.
109
+ if (approxEqual(from, prevFrom) && approxEqual(to, prevTo) && sameMaterial(layer, prev)) {
110
+ repairs.push(makeRepair('drop-duplicate', borehole.boreholeId, index, { from, to }, null, `Dropped duplicate interval ${fmt(from)}–${fmt(to)} m repeated at a page break.`));
111
+ continue;
112
+ }
113
+ const overlap = prevTo - from;
114
+ if (overlap > EPSILON_M) {
115
+ // Interval fully nested inside the previous one.
116
+ if (to <= prevTo + EPSILON_M) {
117
+ if (sameMaterial(layer, prev)) {
118
+ repairs.push(makeRepair('drop-duplicate', borehole.boreholeId, index, { from, to }, null, `Dropped interval ${fmt(from)}–${fmt(to)} m nested inside ${fmt(prevFrom)}–${fmt(prevTo)} m at a page break.`));
119
+ continue;
120
+ }
121
+ repairs.push(makeRepair('flag-unrepairable', borehole.boreholeId, index, { from, to }, { from, to }, `Interval ${fmt(from)}–${fmt(to)} m is nested inside ${fmt(prevFrom)}–${fmt(prevTo)} m; left unchanged for review.`));
122
+ kept.push(layer);
123
+ // Do not advance prev: the nested layer is shallower than the current bottom.
124
+ continue;
125
+ }
126
+ if (overlap <= maxOverlap) {
127
+ const newFrom = round3(prevTo);
128
+ const trimmed = { ...layer, depthFrom: newFrom };
129
+ repairs.push(makeRepair('trim-overlap', borehole.boreholeId, index, { from, to }, { from: newFrom, to }, `Trimmed ${fmt(overlap)} m page-break overlap at ${fmt(prevTo)} m so ${fmt(newFrom)}–${fmt(to)} m follows ${fmt(prevFrom)}–${fmt(prevTo)} m.`));
130
+ kept.push(trimmed);
131
+ prev = pickDeeperPrev(prev, trimmed);
132
+ continue;
133
+ }
134
+ repairs.push(makeRepair('flag-unrepairable', borehole.boreholeId, index, { from, to }, { from, to }, `Overlap of ${fmt(overlap)} m at ${fmt(prevTo)} m exceeds the repairable threshold (${fmt(maxOverlap)} m) and was left for review.`));
135
+ kept.push(layer);
136
+ prev = pickDeeperPrev(prev, layer);
137
+ continue;
138
+ }
139
+ const gap = from - prevTo;
140
+ if (gap > EPSILON_M && gap <= gapTolerance) {
141
+ const newFrom = round3(prevTo);
142
+ const snapped = { ...layer, depthFrom: newFrom };
143
+ repairs.push(makeRepair('snap-gap', borehole.boreholeId, index, { from, to }, { from: newFrom, to }, `Snapped ${fmt(gap)} m micro-gap closed at ${fmt(prevTo)} m.`));
144
+ kept.push(snapped);
145
+ prev = pickDeeperPrev(prev, snapped);
146
+ continue;
147
+ }
148
+ // Contiguous, or a larger gap left for validateMergedBorehole to surface as a review finding.
149
+ kept.push(layer);
150
+ prev = pickDeeperPrev(prev, layer);
151
+ }
152
+ if (repairs.length === 0) {
153
+ return { borehole, repairs };
154
+ }
155
+ const repairedLayers = [...kept, ...nonNumeric.map((entry) => entry.layer)];
156
+ const newWarnings = repairs.map((repair) => repair.note);
157
+ const warnings = [...new Set([...borehole.warnings, ...newWarnings])];
158
+ return {
159
+ borehole: { ...borehole, layers: repairedLayers, warnings },
160
+ repairs,
161
+ };
162
+ }
163
+ //# sourceMappingURL=borehole-continuity.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"borehole-continuity.js","sourceRoot":"","sources":["../../src/ingest/borehole-continuity.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,54 @@
1
+ import type { BoreholeLocation } from './geotech-schemas.js';
2
+ /**
3
+ * Deterministic coordinate plausibility validation for ingested borehole locations.
4
+ *
5
+ * OCR and vision extraction routinely confuse digits, decimal separators, and the label/value
6
+ * pairing of easting vs northing. The parsing and CRS detection in `geo/coordinates.ts` recover a
7
+ * location, but nothing yet checks that the recovered numbers are *plausible* for the detected CRS
8
+ * or consistent across the boreholes of one document. This module adds those checks without
9
+ * touching the parsing path:
10
+ *
11
+ * - per-borehole: CRS range envelopes, suspected easting/northing axis swaps, WGS84 results outside
12
+ * the CRS's geographic region, and UTM zone vs longitude consistency;
13
+ * - cross-borehole: mixed coordinate systems in one document and spatial outliers (the classic
14
+ * OCR digit-error signature: one borehole plotting tens of kilometres from its siblings).
15
+ *
16
+ * Issues are flags for human review only — coordinates are never dropped or rewritten here.
17
+ */
18
+ export type CoordinateValidationSeverity = 'advisory' | 'review';
19
+ export type CoordinateValidationCode = 'projected_out_of_crs_range' | 'suspected_axis_swap' | 'wgs84_out_of_crs_region' | 'utm_zone_longitude_mismatch' | 'coordinate_outlier' | 'mixed_coordinate_systems';
20
+ export interface CoordinateValidationIssue {
21
+ code: CoordinateValidationCode;
22
+ severity: CoordinateValidationSeverity;
23
+ message: string;
24
+ /** Present for borehole-scoped issues; absent for document-scoped issues. */
25
+ boreholeId?: string;
26
+ }
27
+ /**
28
+ * Validate a single borehole location against its detected CRS. Returns review-severity issues for
29
+ * out-of-range coordinates, suspected axis swaps, out-of-region WGS84 results, and UTM zone vs
30
+ * longitude mismatches. Locations without a recognized CRS envelope produce no issues here; the
31
+ * existing `validateMergedBorehole` checks (missing CRS, low CRS confidence, near-origin WGS84)
32
+ * remain authoritative for those cases.
33
+ */
34
+ export declare function validateBoreholeLocationPlausibility(boreholeId: string, location: BoreholeLocation | null | undefined): CoordinateValidationIssue[];
35
+ export interface BoreholeCoordinateEntry {
36
+ boreholeId: string;
37
+ location: BoreholeLocation | null | undefined;
38
+ }
39
+ export interface AssessBoreholeCoordinateConsistencyOptions {
40
+ /**
41
+ * A borehole whose nearest sibling is farther than this (km) while at least two other boreholes
42
+ * cluster within it is flagged as a spatial outlier. Default 10 km — generous enough for long
43
+ * linear (tunnel) alignments while still catching OCR digit errors, which typically displace a
44
+ * point by tens of kilometres.
45
+ */
46
+ maxNearestNeighborKm?: number;
47
+ }
48
+ /**
49
+ * Cross-borehole consistency checks for one document: mixed coordinate systems and spatial
50
+ * outliers. Outlier detection needs at least three located boreholes to attribute blame; with
51
+ * exactly two, an advisory document-scoped issue reports the separation without picking a culprit.
52
+ */
53
+ export declare function assessBoreholeCoordinateConsistency(entries: BoreholeCoordinateEntry[], options?: AssessBoreholeCoordinateConsistencyOptions): CoordinateValidationIssue[];
54
+ //# sourceMappingURL=coordinate-validation.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"coordinate-validation.d.ts","sourceRoot":"","sources":["../../src/ingest/coordinate-validation.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAA6B,MAAM,sBAAsB,CAAC;AAExF;;;;;;;;;;;;;;;GAeG;AAEH,MAAM,MAAM,4BAA4B,GAAG,UAAU,GAAG,QAAQ,CAAC;AAEjE,MAAM,MAAM,wBAAwB,GAChC,4BAA4B,GAC5B,qBAAqB,GACrB,yBAAyB,GACzB,6BAA6B,GAC7B,oBAAoB,GACpB,0BAA0B,CAAC;AAE/B,MAAM,WAAW,yBAAyB;IACxC,IAAI,EAAE,wBAAwB,CAAC;IAC/B,QAAQ,EAAE,4BAA4B,CAAC;IACvC,OAAO,EAAE,MAAM,CAAC;IAChB,6EAA6E;IAC7E,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAsED;;;;;;GAMG;AACH,wBAAgB,oCAAoC,CAClD,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,gBAAgB,GAAG,IAAI,GAAG,SAAS,GAC5C,yBAAyB,EAAE,CAyD7B;AAED,MAAM,WAAW,uBAAuB;IACtC,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,gBAAgB,GAAG,IAAI,GAAG,SAAS,CAAC;CAC/C;AAED,MAAM,WAAW,0CAA0C;IACzD;;;;;OAKG;IACH,oBAAoB,CAAC,EAAE,MAAM,CAAC;CAC/B;AAWD;;;;GAIG;AACH,wBAAgB,mCAAmC,CACjD,OAAO,EAAE,uBAAuB,EAAE,EAClC,OAAO,GAAE,0CAA+C,GACvD,yBAAyB,EAAE,CAsE7B"}
@@ -0,0 +1,194 @@
1
+ const BNG_ENVELOPE = {
2
+ label: 'British National Grid (EPSG:27700)',
3
+ easting: { min: 0, max: 700000 },
4
+ northing: { min: 0, max: 1300000 },
5
+ wgs84: {
6
+ latitude: { min: 49, max: 61.5 },
7
+ longitude: { min: -9, max: 2.5 },
8
+ },
9
+ };
10
+ const UTM_ENVELOPE = {
11
+ label: 'UTM',
12
+ easting: { min: 100000, max: 900000 },
13
+ northing: { min: 0, max: 10000000 },
14
+ };
15
+ /** Half-width of a UTM zone is 3 degrees; allow modest slack for boundary-zone surveys. */
16
+ const UTM_ZONE_LONGITUDE_SLACK_DEG = 4.5;
17
+ const DEG_TO_RAD = Math.PI / 180;
18
+ const EARTH_RADIUS_KM = 6371;
19
+ function within(value, range) {
20
+ return Number.isFinite(value) && value >= range.min && value <= range.max;
21
+ }
22
+ function fmt(value) {
23
+ return Number.isInteger(value) ? String(value) : value.toFixed(2);
24
+ }
25
+ function isUtmCrs(crs) {
26
+ if (crs.zone != null)
27
+ return true;
28
+ if (crs.epsg != null && ((crs.epsg >= 32601 && crs.epsg <= 32660) || (crs.epsg >= 32701 && crs.epsg <= 32760))) {
29
+ return true;
30
+ }
31
+ return crs.code === 'UTM';
32
+ }
33
+ function resolveCrsEnvelope(crs) {
34
+ if (!crs)
35
+ return undefined;
36
+ if (crs.epsg === 27700)
37
+ return BNG_ENVELOPE;
38
+ if (isUtmCrs(crs))
39
+ return UTM_ENVELOPE;
40
+ return undefined;
41
+ }
42
+ function haversineKm(aLat, aLon, bLat, bLon) {
43
+ const dLat = (bLat - aLat) * DEG_TO_RAD;
44
+ const dLon = (bLon - aLon) * DEG_TO_RAD;
45
+ const sinLat = Math.sin(dLat / 2);
46
+ const sinLon = Math.sin(dLon / 2);
47
+ const h = sinLat * sinLat
48
+ + Math.cos(aLat * DEG_TO_RAD) * Math.cos(bLat * DEG_TO_RAD) * sinLon * sinLon;
49
+ return 2 * EARTH_RADIUS_KM * Math.asin(Math.min(1, Math.sqrt(h)));
50
+ }
51
+ /**
52
+ * Validate a single borehole location against its detected CRS. Returns review-severity issues for
53
+ * out-of-range coordinates, suspected axis swaps, out-of-region WGS84 results, and UTM zone vs
54
+ * longitude mismatches. Locations without a recognized CRS envelope produce no issues here; the
55
+ * existing `validateMergedBorehole` checks (missing CRS, low CRS confidence, near-origin WGS84)
56
+ * remain authoritative for those cases.
57
+ */
58
+ export function validateBoreholeLocationPlausibility(boreholeId, location) {
59
+ if (!location)
60
+ return [];
61
+ const issues = [];
62
+ const crs = location.crs;
63
+ const envelope = resolveCrsEnvelope(crs);
64
+ const projected = location.projected;
65
+ if (envelope && projected) {
66
+ const { easting, northing } = projected;
67
+ const inRange = within(easting, envelope.easting) && within(northing, envelope.northing);
68
+ const swappedInRange = within(northing, envelope.easting) && within(easting, envelope.northing);
69
+ if (!inRange && swappedInRange) {
70
+ issues.push({
71
+ code: 'suspected_axis_swap',
72
+ severity: 'review',
73
+ boreholeId,
74
+ message: `Borehole ${boreholeId} projected coordinates (E ${fmt(easting)}, N ${fmt(northing)}) fall outside the expected ${envelope.label} ranges but fit when swapped; easting and northing may be transposed.`,
75
+ });
76
+ }
77
+ else if (!inRange) {
78
+ issues.push({
79
+ code: 'projected_out_of_crs_range',
80
+ severity: 'review',
81
+ boreholeId,
82
+ message: `Borehole ${boreholeId} projected coordinates (E ${fmt(easting)}, N ${fmt(northing)}) fall outside the expected ${envelope.label} ranges and should be reviewed.`,
83
+ });
84
+ }
85
+ }
86
+ const wgs84 = location.wgs84;
87
+ if (envelope?.wgs84 && wgs84) {
88
+ if (!within(wgs84.latitude, envelope.wgs84.latitude) || !within(wgs84.longitude, envelope.wgs84.longitude)) {
89
+ issues.push({
90
+ code: 'wgs84_out_of_crs_region',
91
+ severity: 'review',
92
+ boreholeId,
93
+ message: `Borehole ${boreholeId} resolved to WGS84 (${fmt(wgs84.latitude)}, ${fmt(wgs84.longitude)}), outside the geographic region covered by ${envelope.label}; the CRS or coordinates should be reviewed.`,
94
+ });
95
+ }
96
+ }
97
+ if (wgs84 && crs?.zone != null && crs.zone >= 1 && crs.zone <= 60) {
98
+ const centralMeridian = crs.zone * 6 - 183;
99
+ let delta = Math.abs(wgs84.longitude - centralMeridian);
100
+ if (delta > 180)
101
+ delta = 360 - delta;
102
+ if (delta > UTM_ZONE_LONGITUDE_SLACK_DEG) {
103
+ issues.push({
104
+ code: 'utm_zone_longitude_mismatch',
105
+ severity: 'review',
106
+ boreholeId,
107
+ message: `Borehole ${boreholeId} resolved longitude ${fmt(wgs84.longitude)} is ${fmt(delta)} degrees from the central meridian of UTM zone ${crs.zone} (${fmt(centralMeridian)}); the zone or coordinates may be wrong.`,
108
+ });
109
+ }
110
+ }
111
+ return issues;
112
+ }
113
+ const DEFAULT_MAX_NEAREST_NEIGHBOR_KM = 10;
114
+ function crsIdentifier(crs) {
115
+ if (!crs)
116
+ return null;
117
+ if (crs.epsg != null)
118
+ return `EPSG:${crs.epsg}`;
119
+ if (crs.code)
120
+ return crs.code;
121
+ return null;
122
+ }
123
+ /**
124
+ * Cross-borehole consistency checks for one document: mixed coordinate systems and spatial
125
+ * outliers. Outlier detection needs at least three located boreholes to attribute blame; with
126
+ * exactly two, an advisory document-scoped issue reports the separation without picking a culprit.
127
+ */
128
+ export function assessBoreholeCoordinateConsistency(entries, options = {}) {
129
+ const maxNearestNeighborKm = options.maxNearestNeighborKm ?? DEFAULT_MAX_NEAREST_NEIGHBOR_KM;
130
+ const issues = [];
131
+ const crsIds = new Set();
132
+ for (const entry of entries) {
133
+ const id = crsIdentifier(entry.location?.crs);
134
+ if (id)
135
+ crsIds.add(id);
136
+ }
137
+ if (crsIds.size > 1) {
138
+ issues.push({
139
+ code: 'mixed_coordinate_systems',
140
+ severity: 'review',
141
+ message: `Boreholes in this document use ${crsIds.size} different coordinate systems (${[...crsIds].sort().join(', ')}); confirm a single project CRS before mapping.`,
142
+ });
143
+ }
144
+ const points = entries.flatMap((entry) => {
145
+ const wgs84 = entry.location?.wgs84;
146
+ if (!wgs84 || !Number.isFinite(wgs84.latitude) || !Number.isFinite(wgs84.longitude)) {
147
+ return [];
148
+ }
149
+ return [{ boreholeId: entry.boreholeId, latitude: wgs84.latitude, longitude: wgs84.longitude }];
150
+ });
151
+ if (points.length === 2) {
152
+ const [a, b] = points;
153
+ const distance = haversineKm(a.latitude, a.longitude, b.latitude, b.longitude);
154
+ if (distance > maxNearestNeighborKm) {
155
+ issues.push({
156
+ code: 'coordinate_outlier',
157
+ severity: 'advisory',
158
+ message: `Boreholes ${a.boreholeId} and ${b.boreholeId} plot ${fmt(distance)} km apart, beyond the expected site extent (${fmt(maxNearestNeighborKm)} km); one location may carry an OCR digit error.`,
159
+ });
160
+ }
161
+ return issues;
162
+ }
163
+ if (points.length < 3) {
164
+ return issues;
165
+ }
166
+ const nearest = points.map((point, index) => {
167
+ let best = Number.POSITIVE_INFINITY;
168
+ for (let other = 0; other < points.length; other += 1) {
169
+ if (other === index)
170
+ continue;
171
+ const candidate = points[other];
172
+ const distance = haversineKm(point.latitude, point.longitude, candidate.latitude, candidate.longitude);
173
+ if (distance < best)
174
+ best = distance;
175
+ }
176
+ return { ...point, nearestKm: best };
177
+ });
178
+ const clusteredCount = nearest.filter((point) => point.nearestKm <= maxNearestNeighborKm).length;
179
+ if (clusteredCount < 2) {
180
+ return issues;
181
+ }
182
+ for (const point of nearest) {
183
+ if (point.nearestKm > maxNearestNeighborKm) {
184
+ issues.push({
185
+ code: 'coordinate_outlier',
186
+ severity: 'review',
187
+ boreholeId: point.boreholeId,
188
+ message: `Borehole ${point.boreholeId} plots ${fmt(point.nearestKm)} km from its nearest sibling while the remaining boreholes cluster together; the coordinates likely carry an OCR digit error and should be reviewed.`,
189
+ });
190
+ }
191
+ }
192
+ return issues;
193
+ }
194
+ //# sourceMappingURL=coordinate-validation.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"coordinate-validation.js","sourceRoot":"","sources":["../../src/ingest/coordinate-validation.ts"],"names":[],"mappings":"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"}
@@ -5,6 +5,7 @@ import type { GlmOcrLayoutPage } from '../vision/layout-ocr.js';
5
5
  import type { ParseStatus } from '../vision/parse.js';
6
6
  import type { PdfDocumentInspection, PdfPageClassification } from './pdf.js';
7
7
  import type { IngestSegmentationSummary } from './segmentation.js';
8
+ import { type ReconstructBoreholeContinuityOptions } from './borehole-continuity.js';
8
9
  export interface BoreholeVisionInput {
9
10
  base64: string;
10
11
  mimeType: string;
@@ -85,6 +86,8 @@ export interface IngestBoreholeLogDocumentOptions {
85
86
  interpretPageWithContext?: typeof interpretBoreholeLogWithContext;
86
87
  recoverTextHint?: typeof recoverDocumentTextHint;
87
88
  transcribePageImageText?: typeof transcribeDocumentImageText;
89
+ /** Optional thresholds for deterministic page-break continuity repair (defaults are conservative). */
90
+ continuityRepair?: ReconstructBoreholeContinuityOptions;
88
91
  now?: () => Date;
89
92
  }
90
93
  export declare function summarizeBoreholeIngestInspection(inspection: PdfDocumentInspection | null | undefined): BoreholeIngestInspectionSummary | null;
@@ -1 +1 @@
1
- {"version":3,"file":"geotech-extract.d.ts","sourceRoot":"","sources":["../../src/ingest/geotech-extract.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"geotech-extract.d.ts","sourceRoot":"","sources":["../../src/ingest/geotech-extract.ts"],"names":[],"mappings":"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"}
@@ -1,5 +1,7 @@
1
1
  import { interpretBoreholeLog, interpretBoreholeLogWithContext, mergeBoreholeLogPages, transcribeDocumentImageText, } from '../vision/index.js';
2
2
  import { recoverDocumentTextHint } from '../vision/ocr.js';
3
+ import { reconstructBoreholeContinuity, } from './borehole-continuity.js';
4
+ import { assessBoreholeCoordinateConsistency, validateBoreholeLocationPlausibility, } from './coordinate-validation.js';
3
5
  function uniqueStrings(values) {
4
6
  return [...new Set(values.filter((value) => typeof value === 'string' && value.trim().length > 0))];
5
7
  }
@@ -577,10 +579,19 @@ export async function ingestBoreholeLogDocument(options) {
577
579
  ? `No pages could be ingested successfully.\n${pageFailures.join('\n')}`
578
580
  : 'No pages could be ingested successfully.');
579
581
  }
580
- const mergedBoreholes = groups
582
+ const sanitizedBoreholes = groups
581
583
  .filter((group) => group.pages.length > 0)
582
584
  .map((group) => mergeBoreholeLogPages(group.pages, options.overrideBoreholeId ?? group.boreholeId ?? undefined))
583
585
  .map(sanitizeMergedBoreholeSptValues);
586
+ // Deterministic page-break continuity repair runs before validation so duplicated boundary rows,
587
+ // micro-gaps, and small page-break overlaps are healed, while genuine anomalies remain for
588
+ // validateMergedBorehole to surface authoritatively.
589
+ const continuityRepairs = [];
590
+ const mergedBoreholes = sanitizedBoreholes.map((borehole) => {
591
+ const reconstructed = reconstructBoreholeContinuity(borehole, options.continuityRepair ?? {});
592
+ continuityRepairs.push(...reconstructed.repairs);
593
+ return reconstructed.borehole;
594
+ });
584
595
  const boreholeValidationFeedback = mergedBoreholes.map((borehole) => validateMergedBorehole(borehole));
585
596
  const boreholes = mergedBoreholes.map((borehole, index) => {
586
597
  const validation = boreholeValidationFeedback[index];
@@ -650,6 +661,28 @@ export async function ingestBoreholeLogDocument(options) {
650
661
  });
651
662
  }
652
663
  reviewFindings.push(...boreholeValidationFeedback.flatMap((validation) => validation.findings));
664
+ reviewFindings.push(...continuityRepairs.map((repair) => ({
665
+ code: repair.action === 'flag-unrepairable'
666
+ ? 'continuity_unrepairable'
667
+ : 'continuity_repair_applied',
668
+ severity: 'advisory',
669
+ scope: 'borehole',
670
+ message: repair.note,
671
+ boreholeId: repair.boreholeId,
672
+ })));
673
+ // Deterministic coordinate plausibility and cross-borehole consistency checks. These only flag
674
+ // for review; locations are never dropped or rewritten here.
675
+ const coordinateIssues = [
676
+ ...boreholes.flatMap((borehole) => validateBoreholeLocationPlausibility(borehole.boreholeId, borehole.location)),
677
+ ...assessBoreholeCoordinateConsistency(boreholes.map((borehole) => ({ boreholeId: borehole.boreholeId, location: borehole.location }))),
678
+ ];
679
+ reviewFindings.push(...coordinateIssues.map((issue) => ({
680
+ code: issue.code,
681
+ severity: issue.severity,
682
+ scope: (issue.boreholeId != null ? 'borehole' : 'document'),
683
+ message: issue.message,
684
+ ...(issue.boreholeId != null ? { boreholeId: issue.boreholeId } : {}),
685
+ })));
653
686
  if (boreholes.some((borehole) => borehole.confidence < 70 || borehole.parseStatus !== 'parsed')) {
654
687
  reviewFindings.push({
655
688
  code: 'merged_borehole_incomplete',