@geotechcli/core 0.4.75 → 0.4.77
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/meta/metadata.json +1 -1
- package/dist/report/html.d.ts.map +1 -1
- package/dist/report/html.js +95 -18
- package/dist/report/html.js.map +1 -1
- package/dist/report/ingest-dossier.d.ts.map +1 -1
- package/dist/report/ingest-dossier.js +186 -4
- package/dist/report/ingest-dossier.js.map +1 -1
- package/dist/report/integrated-review-model.d.ts.map +1 -1
- package/dist/report/integrated-review-model.js +30 -2
- package/dist/report/integrated-review-model.js.map +1 -1
- package/dist/report/project-workflow.js +3 -3
- package/dist/report/project-workflow.js.map +1 -1
- package/dist/workspace/project-workflow-executor.d.ts +1 -1
- package/dist/workspace/project-workflow-executor.d.ts.map +1 -1
- package/dist/workspace/project-workflow-executor.js +59 -1
- package/dist/workspace/project-workflow-executor.js.map +1 -1
- package/dist/workspace/project-workflow-router.d.ts.map +1 -1
- package/dist/workspace/project-workflow-router.js +23 -1
- package/dist/workspace/project-workflow-router.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { buildGroundModelMap } from '../ground-model/index.js';
|
|
2
2
|
import { buildFemDraftCandidatesFromGroundModel, } from '../fem/index.js';
|
|
3
|
+
import { buildBoreholeLocation } from '../geo/coordinates.js';
|
|
3
4
|
import { buildIntegratedSourcePagesFromLayout } from './integrated-review-model.js';
|
|
4
5
|
function uniqueStrings(values) {
|
|
5
6
|
return [...new Set(values.filter((value) => typeof value === 'string' && value.trim().length > 0))];
|
|
@@ -1341,6 +1342,128 @@ function numericParameterValue(parameter) {
|
|
|
1341
1342
|
function parameterBoreholeId(parameter) {
|
|
1342
1343
|
return inferBoreholeIdsFromText(parameter.material, parameter.context, parameter.name)[0];
|
|
1343
1344
|
}
|
|
1345
|
+
function firstRegexGroup(text, pattern) {
|
|
1346
|
+
return text.match(pattern)?.[1]?.trim();
|
|
1347
|
+
}
|
|
1348
|
+
function labelledCoordinateValue(text, pattern, allowedHemisphere) {
|
|
1349
|
+
const match = text.match(pattern);
|
|
1350
|
+
if (!match?.[2]) {
|
|
1351
|
+
return undefined;
|
|
1352
|
+
}
|
|
1353
|
+
const hemisphere = match[1]?.trim().toUpperCase();
|
|
1354
|
+
const value = match[2].trim();
|
|
1355
|
+
if (!hemisphere || /[NSEW]$/i.test(value) || !allowedHemisphere.test(hemisphere)) {
|
|
1356
|
+
return value;
|
|
1357
|
+
}
|
|
1358
|
+
return `${value} ${hemisphere}`;
|
|
1359
|
+
}
|
|
1360
|
+
function reportBoreholeCoordinateSegments(text) {
|
|
1361
|
+
const normalized = text.replace(/\r\n/g, '\n');
|
|
1362
|
+
const boreholeMarker = /\b(?:B\.?\s*H\.?|BH|BORE\s*HOLE|BOREHOLE|BOREHOLENO)\s*(?:NO\.?)?\s*[:#-]?\s*0*\d{1,3}\b/gi;
|
|
1363
|
+
const matches = [...normalized.matchAll(boreholeMarker)]
|
|
1364
|
+
.map((match) => match.index)
|
|
1365
|
+
.filter((index) => index != null);
|
|
1366
|
+
if (matches.length === 0) {
|
|
1367
|
+
return normalized
|
|
1368
|
+
.split(/\r?\n/)
|
|
1369
|
+
.map((line) => line.replace(/\s+/g, ' ').trim())
|
|
1370
|
+
.filter(Boolean);
|
|
1371
|
+
}
|
|
1372
|
+
return matches
|
|
1373
|
+
.map((start, index) => normalized.slice(start, matches[index + 1] ?? normalized.length))
|
|
1374
|
+
.flatMap((segment) => segment.split(/(?<=\.)\s+(?=(?:BH|B\.?\s*H|Bore\s*Hole|Borehole)\b)/i))
|
|
1375
|
+
.map((segment) => segment.replace(/\s+/g, ' ').trim())
|
|
1376
|
+
.filter(Boolean);
|
|
1377
|
+
}
|
|
1378
|
+
function reportCoordinateTextEntries(result) {
|
|
1379
|
+
return [
|
|
1380
|
+
...(result.contentChunks ?? []).map((chunk) => ({
|
|
1381
|
+
text: [chunk.headingAncestry.join(' '), chunk.text].filter(Boolean).join('\n'),
|
|
1382
|
+
pageNumber: firstSourcePage([chunk.sourcePages]),
|
|
1383
|
+
})),
|
|
1384
|
+
...(result.inspection?.pages ?? []).map((page) => ({
|
|
1385
|
+
text: [
|
|
1386
|
+
page.normalizedText,
|
|
1387
|
+
page.extractedText,
|
|
1388
|
+
page.normalizedArtifact?.nativeText,
|
|
1389
|
+
].filter(Boolean).join('\n'),
|
|
1390
|
+
pageNumber: page.pageNumber,
|
|
1391
|
+
})),
|
|
1392
|
+
...result.parameters.map((parameter) => ({
|
|
1393
|
+
text: [parameter.name, parameter.valueText, parameter.unit, parameter.material, parameter.context].filter(Boolean).join(' '),
|
|
1394
|
+
pageNumber: firstSourcePage([parameter.sourcePages]),
|
|
1395
|
+
})),
|
|
1396
|
+
].filter((entry) => entry.text.trim().length > 0);
|
|
1397
|
+
}
|
|
1398
|
+
function extractReportCoordinateCandidateFromLine(rawLine, pageNumber) {
|
|
1399
|
+
const line = rawLine.replace(/\s+/g, ' ').trim();
|
|
1400
|
+
if (!line || !/\b(?:coordinate|location|latitude|longitude|easting|northing|bore\s*hole|borehole|bh)\b/i.test(line)) {
|
|
1401
|
+
return undefined;
|
|
1402
|
+
}
|
|
1403
|
+
const boreholeIds = inferBoreholeIdsFromText(line);
|
|
1404
|
+
if (boreholeIds.length !== 1) {
|
|
1405
|
+
return undefined;
|
|
1406
|
+
}
|
|
1407
|
+
const easting = firstRegexGroup(line, /\b(?:easting|east)\s*(?:\([ex]\))?\s*[:=\-]?\s*([+-]?\d[\d,]*(?:\.\d+)?)/i);
|
|
1408
|
+
const northing = firstRegexGroup(line, /\b(?:northing|north)\s*(?:\([ny]\))?\s*[:=\-]?\s*([+-]?\d[\d,]*(?:\.\d+)?)/i);
|
|
1409
|
+
const latitude = labelledCoordinateValue(line, /\b(?:latitude|lat)\s*(?:\(([ns])\))?\s*[:=\-]?\s*([+-]?\d{1,2}(?:[.,]\d+)?\s*[NS]?)/i, /^[NS]$/i);
|
|
1410
|
+
const longitude = labelledCoordinateValue(line, /\b(?:longitude|long|lon)\s*(?:\(([ew])\))?\s*[:=\-]?\s*([+-]?\d{1,3}(?:[.,]\d+)?\s*[EW]?)/i, /^[EW]$/i);
|
|
1411
|
+
if (!((easting && northing) || (latitude && longitude))) {
|
|
1412
|
+
return undefined;
|
|
1413
|
+
}
|
|
1414
|
+
const epsg = firstRegexGroup(line, /\bEPSG\s*[:#-]?\s*(\d{3,5})\b/i);
|
|
1415
|
+
return {
|
|
1416
|
+
boreholeId: boreholeIds[0],
|
|
1417
|
+
rawText: line,
|
|
1418
|
+
...(pageNumber != null ? { pageNumber } : {}),
|
|
1419
|
+
...(easting ? { easting } : {}),
|
|
1420
|
+
...(northing ? { northing } : {}),
|
|
1421
|
+
...(latitude ? { latitude } : {}),
|
|
1422
|
+
...(longitude ? { longitude } : {}),
|
|
1423
|
+
...(epsg ? { crs: `EPSG:${epsg}` } : {}),
|
|
1424
|
+
};
|
|
1425
|
+
}
|
|
1426
|
+
function extractReportBoreholeCoordinateCandidates(result) {
|
|
1427
|
+
const candidates = [];
|
|
1428
|
+
const seen = new Set();
|
|
1429
|
+
for (const entry of reportCoordinateTextEntries(result)) {
|
|
1430
|
+
for (const segment of reportBoreholeCoordinateSegments(entry.text)) {
|
|
1431
|
+
const candidate = extractReportCoordinateCandidateFromLine(segment, entry.pageNumber);
|
|
1432
|
+
if (!candidate) {
|
|
1433
|
+
continue;
|
|
1434
|
+
}
|
|
1435
|
+
const key = `${candidate.boreholeId}:${candidate.easting ?? ''}:${candidate.northing ?? ''}:${candidate.latitude ?? ''}:${candidate.longitude ?? ''}`;
|
|
1436
|
+
if (seen.has(key)) {
|
|
1437
|
+
continue;
|
|
1438
|
+
}
|
|
1439
|
+
seen.add(key);
|
|
1440
|
+
candidates.push(candidate);
|
|
1441
|
+
}
|
|
1442
|
+
}
|
|
1443
|
+
return candidates;
|
|
1444
|
+
}
|
|
1445
|
+
function coordinateSystemFromReportBoreholes(boreholes) {
|
|
1446
|
+
const coordinates = boreholes.map((borehole) => borehole.coordinates).filter(Boolean);
|
|
1447
|
+
const hasProjected = coordinates.some((coordinate) => coordinate?.easting != null && coordinate.northing != null);
|
|
1448
|
+
const hasGeographic = coordinates.some((coordinate) => coordinate?.latitude != null && coordinate.longitude != null);
|
|
1449
|
+
if (hasProjected) {
|
|
1450
|
+
return {
|
|
1451
|
+
kind: 'local-grid',
|
|
1452
|
+
warnings: ['PDF report coordinate evidence requires CRS/unit verification before design or GIS overlay use.'],
|
|
1453
|
+
};
|
|
1454
|
+
}
|
|
1455
|
+
if (hasGeographic) {
|
|
1456
|
+
return {
|
|
1457
|
+
kind: 'geographic',
|
|
1458
|
+
crs: 'EPSG:4326',
|
|
1459
|
+
warnings: ['Geographic borehole coordinates were extracted from report text; verify against the source PDF before design use.'],
|
|
1460
|
+
};
|
|
1461
|
+
}
|
|
1462
|
+
return {
|
|
1463
|
+
kind: 'unknown',
|
|
1464
|
+
warnings: ['PDF report evidence did not include plottable coordinate data.'],
|
|
1465
|
+
};
|
|
1466
|
+
}
|
|
1344
1467
|
function looksLikeBearingTableSptFalsePositive(parameter) {
|
|
1345
1468
|
const normalizedName = parameter.name.toLowerCase().replace(/\s+/g, '');
|
|
1346
1469
|
if (!/spt|nvalue|n-value|standardpenetration/.test(normalizedName)) {
|
|
@@ -1361,6 +1484,7 @@ function buildGroundModelFromGeotechReport(result, profile, sourceLabel) {
|
|
|
1361
1484
|
const strata = [];
|
|
1362
1485
|
const groundwater = [];
|
|
1363
1486
|
const parameters = [];
|
|
1487
|
+
let promotedCoordinateSystem;
|
|
1364
1488
|
const ensureBorehole = (id, evidenceIds = []) => {
|
|
1365
1489
|
const normalizedId = normalizeBoreholeId(id);
|
|
1366
1490
|
const existing = boreholes.get(normalizedId);
|
|
@@ -1485,6 +1609,67 @@ function buildGroundModelFromGeotechReport(result, profile, sourceLabel) {
|
|
|
1485
1609
|
};
|
|
1486
1610
|
parameters.push(visualParameter);
|
|
1487
1611
|
}
|
|
1612
|
+
for (const candidate of extractReportBoreholeCoordinateCandidates(result)) {
|
|
1613
|
+
const location = buildBoreholeLocation({
|
|
1614
|
+
boreholeId: candidate.boreholeId,
|
|
1615
|
+
source: 'pdf-report',
|
|
1616
|
+
description: candidate.rawText,
|
|
1617
|
+
crs: candidate.crs,
|
|
1618
|
+
easting: candidate.easting,
|
|
1619
|
+
northing: candidate.northing,
|
|
1620
|
+
latitude: candidate.latitude,
|
|
1621
|
+
longitude: candidate.longitude,
|
|
1622
|
+
raw: { rawCoordinateText: candidate.rawText },
|
|
1623
|
+
});
|
|
1624
|
+
const hasPlottableCoordinates = Boolean(location?.projected || location?.wgs84);
|
|
1625
|
+
if (!location || !hasPlottableCoordinates) {
|
|
1626
|
+
continue;
|
|
1627
|
+
}
|
|
1628
|
+
const locationCrs = location.crs?.code ?? (location.crs?.epsg != null ? `EPSG:${location.crs.epsg}` : location.crs?.name);
|
|
1629
|
+
if (!promotedCoordinateSystem) {
|
|
1630
|
+
promotedCoordinateSystem = location.projected
|
|
1631
|
+
? {
|
|
1632
|
+
kind: 'local-grid',
|
|
1633
|
+
...(locationCrs ? { crs: locationCrs } : {}),
|
|
1634
|
+
warnings: locationCrs
|
|
1635
|
+
? ['PDF report coordinate evidence requires source-page verification before design or GIS overlay use.']
|
|
1636
|
+
: ['Projected borehole coordinates were extracted from report text without an explicit CRS.'],
|
|
1637
|
+
}
|
|
1638
|
+
: {
|
|
1639
|
+
kind: 'geographic',
|
|
1640
|
+
crs: locationCrs ?? 'EPSG:4326',
|
|
1641
|
+
warnings: ['Geographic borehole coordinates were extracted from report text; verify against the source PDF before design use.'],
|
|
1642
|
+
};
|
|
1643
|
+
}
|
|
1644
|
+
const evidenceId = addReportEvidenceRef(evidenceState, {
|
|
1645
|
+
pageNumber: candidate.pageNumber,
|
|
1646
|
+
rawValue: candidate.rawText,
|
|
1647
|
+
normalizedValue: location.projected
|
|
1648
|
+
? `E ${location.projected.easting}, N ${location.projected.northing}`
|
|
1649
|
+
: location.wgs84
|
|
1650
|
+
? `${location.wgs84.latitude}, ${location.wgs84.longitude}`
|
|
1651
|
+
: null,
|
|
1652
|
+
warnings: location.crs?.kind === 'unknown' ? ['Coordinate CRS is unknown.'] : [],
|
|
1653
|
+
});
|
|
1654
|
+
const borehole = ensureBorehole(candidate.boreholeId, [evidenceId]);
|
|
1655
|
+
borehole.coordinates = {
|
|
1656
|
+
...(location.projected
|
|
1657
|
+
? {
|
|
1658
|
+
easting: location.projected.easting,
|
|
1659
|
+
northing: location.projected.northing,
|
|
1660
|
+
}
|
|
1661
|
+
: {}),
|
|
1662
|
+
...(location.wgs84
|
|
1663
|
+
? {
|
|
1664
|
+
latitude: location.wgs84.latitude,
|
|
1665
|
+
longitude: location.wgs84.longitude,
|
|
1666
|
+
}
|
|
1667
|
+
: {}),
|
|
1668
|
+
evidenceIds: [evidenceId],
|
|
1669
|
+
confidence: confidenceRatio(result.confidence),
|
|
1670
|
+
};
|
|
1671
|
+
borehole.evidenceIds = uniqueStrings([...borehole.evidenceIds, evidenceId]);
|
|
1672
|
+
}
|
|
1488
1673
|
const boreholeList = [...boreholes.values()].sort((left, right) => left.id.localeCompare(right.id, undefined, { numeric: true }));
|
|
1489
1674
|
const sptTestCount = boreholeList.reduce((count, borehole) => count + borehole.sptTests.length, 0);
|
|
1490
1675
|
if (strata.length === 0 && groundwater.length === 0 && parameters.length === 0 && sptTestCount === 0) {
|
|
@@ -1507,10 +1692,7 @@ function buildGroundModelFromGeotechReport(result, profile, sourceLabel) {
|
|
|
1507
1692
|
project: {
|
|
1508
1693
|
rootPath: result.source.filePath ?? result.source.fileName ?? sourceLabel,
|
|
1509
1694
|
},
|
|
1510
|
-
coordinateSystem:
|
|
1511
|
-
kind: 'unknown',
|
|
1512
|
-
warnings: ['PDF report evidence did not include plottable coordinate data.'],
|
|
1513
|
-
},
|
|
1695
|
+
coordinateSystem: promotedCoordinateSystem ?? coordinateSystemFromReportBoreholes(boreholeList),
|
|
1514
1696
|
boreholes: boreholeList,
|
|
1515
1697
|
strata,
|
|
1516
1698
|
groundwater,
|