@geotechcli/core 0.4.52 → 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.
Files changed (46) hide show
  1. package/dist/config/index.d.ts.map +1 -1
  2. package/dist/config/index.js +4 -2
  3. package/dist/config/index.js.map +1 -1
  4. package/dist/evidence/evidence-ref.d.ts +8 -0
  5. package/dist/evidence/evidence-ref.d.ts.map +1 -1
  6. package/dist/evidence/evidence-ref.js.map +1 -1
  7. package/dist/evidence/index.d.ts +1 -1
  8. package/dist/evidence/index.d.ts.map +1 -1
  9. package/dist/evidence/index.js.map +1 -1
  10. package/dist/index.d.ts +1 -1
  11. package/dist/index.d.ts.map +1 -1
  12. package/dist/index.js +1 -1
  13. package/dist/index.js.map +1 -1
  14. package/dist/ingest/geotech-document.d.ts +2 -0
  15. package/dist/ingest/geotech-document.d.ts.map +1 -1
  16. package/dist/ingest/geotech-document.js +17 -2
  17. package/dist/ingest/geotech-document.js.map +1 -1
  18. package/dist/ingest/geotech-extract.d.ts +2 -0
  19. package/dist/ingest/geotech-extract.d.ts.map +1 -1
  20. package/dist/ingest/geotech-extract.js +12 -0
  21. package/dist/ingest/geotech-extract.js.map +1 -1
  22. package/dist/ingest/page-evidence-cache.d.ts +4 -1
  23. package/dist/ingest/page-evidence-cache.d.ts.map +1 -1
  24. package/dist/ingest/page-evidence-cache.js +86 -1
  25. package/dist/ingest/page-evidence-cache.js.map +1 -1
  26. package/dist/llm/index.d.ts +1 -0
  27. package/dist/llm/index.d.ts.map +1 -1
  28. package/dist/llm/index.js +1 -0
  29. package/dist/llm/index.js.map +1 -1
  30. package/dist/meta/metadata.json +1 -1
  31. package/dist/report/html.d.ts.map +1 -1
  32. package/dist/report/html.js +596 -1309
  33. package/dist/report/html.js.map +1 -1
  34. package/dist/report/index.d.ts +1 -0
  35. package/dist/report/index.d.ts.map +1 -1
  36. package/dist/report/index.js +1 -0
  37. package/dist/report/index.js.map +1 -1
  38. package/dist/report/ingest-dossier.d.ts +5 -0
  39. package/dist/report/ingest-dossier.d.ts.map +1 -1
  40. package/dist/report/ingest-dossier.js +376 -2
  41. package/dist/report/ingest-dossier.js.map +1 -1
  42. package/dist/report/integrated-review-model.d.ts +147 -0
  43. package/dist/report/integrated-review-model.d.ts.map +1 -0
  44. package/dist/report/integrated-review-model.js +649 -0
  45. package/dist/report/integrated-review-model.js.map +1 -0
  46. package/package.json +1 -1
@@ -1,4 +1,5 @@
1
1
  import { buildGroundModelMap } from '../ground-model/index.js';
2
+ import { buildIntegratedSourcePagesFromLayout } from './integrated-review-model.js';
2
3
  function uniqueStrings(values) {
3
4
  return [...new Set(values.filter((value) => typeof value === 'string' && value.trim().length > 0))];
4
5
  }
@@ -1487,6 +1488,291 @@ function buildGroundModelFromGeotechReport(result, profile, sourceLabel) {
1487
1488
  map: buildGroundModelMap(model),
1488
1489
  };
1489
1490
  }
1491
+ function evidenceMethodForBoreholePage(result, pageNumber) {
1492
+ const audit = pageNumber != null
1493
+ ? result.pageAudits.find((candidate) => candidate.pageNumber === pageNumber)
1494
+ : result.pageAudits[0];
1495
+ if (!audit) {
1496
+ return result.source.inputKind === 'image' ? 'vision' : 'manual';
1497
+ }
1498
+ switch (audit.textHintSource) {
1499
+ case 'native-text':
1500
+ case 'pdfjs-text':
1501
+ return 'pdf-text';
1502
+ case 'none':
1503
+ return 'manual';
1504
+ default:
1505
+ return 'vision';
1506
+ }
1507
+ }
1508
+ function addBoreholeEvidenceRef(state, input) {
1509
+ state.counter += 1;
1510
+ const id = `bh-ev-${String(state.counter).padStart(5, '0')}`;
1511
+ const pageNumber = input.pageNumber ?? undefined;
1512
+ const hasAudit = pageNumber == null
1513
+ || state.result.pageAudits.some((candidate) => candidate.pageNumber === pageNumber);
1514
+ const warnings = [
1515
+ ...(input.warnings ?? []),
1516
+ ...(!hasAudit ? [`Source page ${pageNumber} was not present in retained borehole page audit; verify against the source log.`] : []),
1517
+ ];
1518
+ state.refs.push({
1519
+ id,
1520
+ sourceType: state.result.source.inputKind === 'image' ? 'image-region' : 'pdf-page',
1521
+ sourcePath: state.sourcePath,
1522
+ location: {
1523
+ filePath: state.sourcePath,
1524
+ ...(pageNumber != null ? { pageNumber } : {}),
1525
+ },
1526
+ method: evidenceMethodForBoreholePage(state.result, pageNumber),
1527
+ confidence: confidenceRatio(state.result.confidence),
1528
+ rawValue: input.rawValue,
1529
+ normalizedValue: input.normalizedValue,
1530
+ ...(input.unit ? { unit: input.unit } : {}),
1531
+ warnings,
1532
+ });
1533
+ return id;
1534
+ }
1535
+ function boreholeCoordinateSystem(result) {
1536
+ const locations = result.boreholes.map((borehole) => borehole.location).filter(Boolean);
1537
+ const projected = locations.find((location) => location?.projected);
1538
+ const geographic = locations.find((location) => location?.wgs84);
1539
+ const crs = locations.map((location) => location?.crs).find(Boolean);
1540
+ const code = crs?.code ?? (crs?.epsg != null ? `EPSG:${crs.epsg}` : crs?.name);
1541
+ if (projected) {
1542
+ return {
1543
+ kind: 'local-grid',
1544
+ ...(code ? { crs: code } : {}),
1545
+ warnings: code ? [] : ['Projected borehole coordinates were extracted without an explicit CRS.'],
1546
+ };
1547
+ }
1548
+ if (geographic) {
1549
+ return {
1550
+ kind: 'geographic',
1551
+ crs: code ?? 'EPSG:4326',
1552
+ warnings: code ? [] : ['Geographic borehole coordinates were extracted without an explicit CRS; EPSG:4326 display is assumed for review.'],
1553
+ };
1554
+ }
1555
+ return {
1556
+ kind: 'unknown',
1557
+ warnings: ['Borehole log evidence did not include plottable coordinate data.'],
1558
+ };
1559
+ }
1560
+ function midpointDepth(depthFrom, depthTo) {
1561
+ return Number(((depthFrom + depthTo) / 2).toFixed(2));
1562
+ }
1563
+ function buildGroundModelFromBoreholeIngest(result, sourceLabel) {
1564
+ if (result.boreholes.length === 0) {
1565
+ return undefined;
1566
+ }
1567
+ const sourcePath = result.source.fileName ?? result.source.filePath ?? sourceLabel;
1568
+ const evidenceState = {
1569
+ refs: [],
1570
+ counter: 0,
1571
+ sourcePath,
1572
+ result,
1573
+ };
1574
+ const strata = [];
1575
+ const groundwater = [];
1576
+ const parameters = [];
1577
+ const adapterWarnings = [];
1578
+ const boreholes = result.boreholes.map((sourceBorehole) => {
1579
+ const boreholeId = normalizeBoreholeId(sourceBorehole.boreholeId);
1580
+ const pageNumber = sourceBorehole.pageNumber ?? undefined;
1581
+ const headerEvidenceId = addBoreholeEvidenceRef(evidenceState, {
1582
+ pageNumber,
1583
+ rawValue: sourceBorehole.boreholeId,
1584
+ normalizedValue: boreholeId,
1585
+ warnings: sourceBorehole.warnings,
1586
+ });
1587
+ const coordinateEvidenceIds = [];
1588
+ const coordinate = sourceBorehole.location
1589
+ ? (() => {
1590
+ const rawCoordinateText = sourceBorehole.location?.raw?.rawCoordinateText
1591
+ ?? sourceBorehole.location?.raw?.coordinates
1592
+ ?? sourceBorehole.location?.description
1593
+ ?? sourceBorehole.location?.source
1594
+ ?? null;
1595
+ const evidenceId = addBoreholeEvidenceRef(evidenceState, {
1596
+ pageNumber,
1597
+ rawValue: typeof rawCoordinateText === 'string' || typeof rawCoordinateText === 'number' ? rawCoordinateText : null,
1598
+ normalizedValue: sourceBorehole.location?.projected
1599
+ ? `E ${sourceBorehole.location.projected.easting}, N ${sourceBorehole.location.projected.northing}`
1600
+ : sourceBorehole.location?.wgs84
1601
+ ? `${sourceBorehole.location.wgs84.latitude}, ${sourceBorehole.location.wgs84.longitude}`
1602
+ : null,
1603
+ warnings: sourceBorehole.location?.crs?.kind === 'unknown' ? ['Coordinate CRS is unknown.'] : [],
1604
+ });
1605
+ coordinateEvidenceIds.push(evidenceId);
1606
+ return {
1607
+ ...(sourceBorehole.location?.projected
1608
+ ? {
1609
+ easting: sourceBorehole.location.projected.easting,
1610
+ northing: sourceBorehole.location.projected.northing,
1611
+ }
1612
+ : {}),
1613
+ ...(sourceBorehole.location?.wgs84
1614
+ ? {
1615
+ latitude: sourceBorehole.location.wgs84.latitude,
1616
+ longitude: sourceBorehole.location.wgs84.longitude,
1617
+ }
1618
+ : {}),
1619
+ evidenceIds: [evidenceId],
1620
+ confidence: confidenceRatio(sourceBorehole.confidence),
1621
+ };
1622
+ })()
1623
+ : undefined;
1624
+ const borehole = {
1625
+ id: boreholeId,
1626
+ ...(coordinate ? { coordinates: coordinate } : {}),
1627
+ sptTests: [],
1628
+ strata: [],
1629
+ groundwater: [],
1630
+ evidenceIds: uniqueStrings([headerEvidenceId, ...coordinateEvidenceIds]),
1631
+ confidence: confidenceRatio(sourceBorehole.confidence),
1632
+ warnings: sourceBorehole.warnings,
1633
+ };
1634
+ for (const [layerIndex, layer] of sourceBorehole.layers.entries()) {
1635
+ const depthFrom = layer.depthFrom ?? (layerIndex === 0 ? 0 : null);
1636
+ const depthTo = layer.depthTo ?? sourceBorehole.totalDepth;
1637
+ if (depthFrom == null || depthTo == null || depthTo <= depthFrom) {
1638
+ adapterWarnings.push(`Skipped invalid layer interval for ${boreholeId}; verify source log depths.`);
1639
+ continue;
1640
+ }
1641
+ const layerWarnings = layer.depthFrom == null || layer.depthTo == null
1642
+ ? ['Layer boundary inferred from borehole log context.']
1643
+ : [];
1644
+ const stratumEvidenceId = addBoreholeEvidenceRef(evidenceState, {
1645
+ pageNumber,
1646
+ rawValue: layer.description,
1647
+ normalizedValue: `${depthFrom}-${depthTo}m ${layer.description ?? ''}`.trim(),
1648
+ warnings: layerWarnings,
1649
+ });
1650
+ const stratum = {
1651
+ boreholeId,
1652
+ topDepth: depthFrom,
1653
+ bottomDepth: depthTo,
1654
+ description: displayTableText(layer.description, 160) || layer.uscsSymbol || `Layer ${layerIndex + 1}`,
1655
+ evidenceIds: [stratumEvidenceId],
1656
+ confidence: confidenceRatio(sourceBorehole.confidence),
1657
+ warnings: layerWarnings,
1658
+ };
1659
+ strata.push(stratum);
1660
+ borehole.strata.push(stratum);
1661
+ borehole.evidenceIds = uniqueStrings([...borehole.evidenceIds, stratumEvidenceId]);
1662
+ if (layer.sptN != null && Number.isFinite(layer.sptN) && layer.sptN >= 0 && layer.sptN <= 100) {
1663
+ const inferredDepth = midpointDepth(depthFrom, depthTo);
1664
+ const warnings = ['SPT depth inferred from host layer interval; verify against source log.'];
1665
+ const evidenceId = addBoreholeEvidenceRef(evidenceState, {
1666
+ pageNumber,
1667
+ rawValue: layer.sptN,
1668
+ normalizedValue: layer.sptN,
1669
+ unit: 'blows/300mm',
1670
+ warnings,
1671
+ });
1672
+ borehole.sptTests.push({
1673
+ depth: inferredDepth,
1674
+ nValue: layer.sptN,
1675
+ unit: 'blows/300mm',
1676
+ evidenceIds: [evidenceId],
1677
+ confidence: confidenceRatio(sourceBorehole.confidence),
1678
+ warnings,
1679
+ });
1680
+ borehole.evidenceIds = uniqueStrings([...borehole.evidenceIds, evidenceId]);
1681
+ adapterWarnings.push(`SPT depth for ${boreholeId} was inferred from layer ${depthFrom}-${depthTo} m.`);
1682
+ }
1683
+ if (layer.waterContent != null && Number.isFinite(layer.waterContent)) {
1684
+ const depth = midpointDepth(depthFrom, depthTo);
1685
+ const evidenceId = addBoreholeEvidenceRef(evidenceState, {
1686
+ pageNumber,
1687
+ rawValue: layer.waterContent,
1688
+ normalizedValue: layer.waterContent,
1689
+ unit: '%',
1690
+ });
1691
+ parameters.push({
1692
+ name: 'waterContent',
1693
+ value: layer.waterContent,
1694
+ unit: '%',
1695
+ boreholeId,
1696
+ depth,
1697
+ evidenceIds: [evidenceId],
1698
+ confidence: confidenceRatio(sourceBorehole.confidence),
1699
+ warnings: [],
1700
+ });
1701
+ borehole.evidenceIds = uniqueStrings([...borehole.evidenceIds, evidenceId]);
1702
+ }
1703
+ }
1704
+ if (sourceBorehole.waterTableDepth != null && Number.isFinite(sourceBorehole.waterTableDepth)) {
1705
+ const evidenceId = addBoreholeEvidenceRef(evidenceState, {
1706
+ pageNumber,
1707
+ rawValue: sourceBorehole.waterTableDepth,
1708
+ normalizedValue: sourceBorehole.waterTableDepth,
1709
+ unit: 'm bgl',
1710
+ warnings: [],
1711
+ });
1712
+ const observation = {
1713
+ boreholeId,
1714
+ depth: sourceBorehole.waterTableDepth,
1715
+ evidenceIds: [evidenceId],
1716
+ confidence: confidenceRatio(sourceBorehole.confidence),
1717
+ warnings: [],
1718
+ };
1719
+ groundwater.push(observation);
1720
+ borehole.groundwater.push(observation);
1721
+ borehole.evidenceIds = uniqueStrings([...borehole.evidenceIds, evidenceId]);
1722
+ }
1723
+ return borehole;
1724
+ }).sort((left, right) => left.id.localeCompare(right.id, undefined, { numeric: true }));
1725
+ const sptTestCount = boreholes.reduce((count, borehole) => count + borehole.sptTests.length, 0);
1726
+ const labTests = parameters
1727
+ .filter((parameter) => parameter.depth != null)
1728
+ .map((parameter, index) => ({
1729
+ sampleId: `borehole-sample-${index + 1}`,
1730
+ ...(parameter.boreholeId ? { boreholeId: parameter.boreholeId } : {}),
1731
+ depth: parameter.depth,
1732
+ parameters: [parameter],
1733
+ evidenceIds: parameter.evidenceIds,
1734
+ confidence: parameter.confidence,
1735
+ warnings: parameter.warnings,
1736
+ }));
1737
+ const coordinateSystem = boreholeCoordinateSystem(result);
1738
+ const model = {
1739
+ schemaVersion: 'ground-model.v1',
1740
+ generatedAt: result.generatedAt,
1741
+ project: {
1742
+ rootPath: result.source.filePath ?? result.source.fileName ?? sourceLabel,
1743
+ },
1744
+ coordinateSystem,
1745
+ boreholes,
1746
+ strata,
1747
+ groundwater,
1748
+ labTests,
1749
+ parameters,
1750
+ monitoringSeries: [],
1751
+ evidence: evidenceState.refs,
1752
+ rejectedObservations: [],
1753
+ warnings: uniqueStrings([
1754
+ ...result.warnings,
1755
+ ...coordinateSystem.warnings,
1756
+ ...adapterWarnings,
1757
+ 'GroundModel visual review was adapted from borehole-log ingest evidence; source-page verification is required before design use.',
1758
+ ]),
1759
+ stats: {
1760
+ boreholes: boreholes.length,
1761
+ sptTests: sptTestCount,
1762
+ strata: strata.length,
1763
+ groundwaterObservations: groundwater.length,
1764
+ labTests: labTests.length,
1765
+ parameters: parameters.length,
1766
+ monitoringSeries: 0,
1767
+ evidenceRefs: evidenceState.refs.length,
1768
+ rejectedObservations: 0,
1769
+ },
1770
+ };
1771
+ return {
1772
+ ...model,
1773
+ map: buildGroundModelMap(model),
1774
+ };
1775
+ }
1490
1776
  function buildBoreholeProfile(result) {
1491
1777
  if (result.boreholes.length === 0) {
1492
1778
  return undefined;
@@ -1545,10 +1831,86 @@ function buildFooterNotes(result) {
1545
1831
  function sourceLabelFromResult(result, override) {
1546
1832
  return override ?? result.source.fileName ?? result.source.filePath ?? 'Unknown source';
1547
1833
  }
1834
+ function normalizedLayoutMatchText(value) {
1835
+ return String(value ?? '')
1836
+ .toLowerCase()
1837
+ .replace(/[^a-z0-9]+/g, '')
1838
+ .trim();
1839
+ }
1840
+ function evidenceMatchCandidates(ref) {
1841
+ return uniqueStrings([
1842
+ typeof ref.normalizedValue === 'string' || typeof ref.normalizedValue === 'number'
1843
+ ? String(ref.normalizedValue)
1844
+ : null,
1845
+ typeof ref.rawValue === 'string' || typeof ref.rawValue === 'number'
1846
+ ? String(ref.rawValue)
1847
+ : null,
1848
+ ])
1849
+ .map(normalizedLayoutMatchText)
1850
+ .filter((value) => value.length >= 6);
1851
+ }
1852
+ function layoutElementMatchesCandidate(element, candidate) {
1853
+ return normalizedLayoutMatchText(element.content).includes(candidate);
1854
+ }
1855
+ function buildLayoutEvidenceLinks(pages, evidenceRefs) {
1856
+ const links = [];
1857
+ const assignedElements = new Set();
1858
+ for (const ref of evidenceRefs) {
1859
+ const pageNumber = ref.location.pageNumber;
1860
+ if (pageNumber == null || !Number.isInteger(pageNumber) || pageNumber <= 0) {
1861
+ continue;
1862
+ }
1863
+ const candidates = evidenceMatchCandidates(ref);
1864
+ if (candidates.length === 0) {
1865
+ continue;
1866
+ }
1867
+ const page = pages.find((candidate) => candidate.pageNumber === pageNumber);
1868
+ if (!page) {
1869
+ continue;
1870
+ }
1871
+ for (const candidate of candidates) {
1872
+ const matches = page.elements
1873
+ .map((element, elementOrdinal) => ({ element, elementOrdinal }))
1874
+ .filter(({ element }) => element.bbox2d && layoutElementMatchesCandidate(element, candidate));
1875
+ if (matches.length !== 1) {
1876
+ continue;
1877
+ }
1878
+ const match = matches[0];
1879
+ const key = `${pageNumber}:${match.elementOrdinal}`;
1880
+ if (assignedElements.has(key)) {
1881
+ continue;
1882
+ }
1883
+ assignedElements.add(key);
1884
+ links.push({
1885
+ pageNumber,
1886
+ evidenceId: ref.id,
1887
+ elementOrdinal: match.elementOrdinal,
1888
+ contentIncludes: candidate,
1889
+ confidence: ref.confidence,
1890
+ method: 'glm-ocr-layout',
1891
+ status: ref.warnings.length > 0 ? 'review_recommended' : 'accepted',
1892
+ });
1893
+ break;
1894
+ }
1895
+ }
1896
+ return links;
1897
+ }
1898
+ function buildIntegratedSourcePagesFromPageAudits(audits, sourcePath, evidenceRefs = []) {
1899
+ const layoutPages = audits.flatMap((audit) => audit.layoutPages ?? []);
1900
+ if (layoutPages.length === 0) {
1901
+ return [];
1902
+ }
1903
+ return buildIntegratedSourcePagesFromLayout(layoutPages, {
1904
+ sourcePath,
1905
+ links: buildLayoutEvidenceLinks(layoutPages, evidenceRefs),
1906
+ });
1907
+ }
1548
1908
  export function buildIngestDossier(result, options) {
1549
1909
  const sourceLabel = sourceLabelFromResult(result, options?.sourceLabel);
1550
1910
  const storedReview = options?.storedReview ?? undefined;
1551
1911
  const approval = options?.approval ?? undefined;
1912
+ const agentReviews = options?.agentReviews;
1913
+ const explicitSourcePages = options?.sourcePages;
1552
1914
  if (result.documentType === 'geotech-document') {
1553
1915
  const geotechResult = result;
1554
1916
  const title = deriveGeotechDisplayTitle(geotechResult);
@@ -1556,6 +1918,9 @@ export function buildIngestDossier(result, options) {
1556
1918
  const summary = cleanSummary
1557
1919
  || geotechOutcomeSummary(geotechResult);
1558
1920
  const boreholeProfile = buildGeotechBoreholeProfile(geotechResult);
1921
+ const groundModel = buildGroundModelFromGeotechReport(geotechResult, boreholeProfile, sourceLabel);
1922
+ const sourcePages = explicitSourcePages
1923
+ ?? buildIntegratedSourcePagesFromPageAudits(geotechResult.pageAudits, sourceLabel, groundModel?.evidence ?? []);
1559
1924
  return {
1560
1925
  title,
1561
1926
  subtitle: geotechResult.documentClass
@@ -1576,13 +1941,19 @@ export function buildIngestDossier(result, options) {
1576
1941
  trustItems: buildGeotechTrustItems(geotechResult),
1577
1942
  confidenceBreakdown: buildGeotechConfidenceItems(geotechResult),
1578
1943
  boreholeProfile,
1579
- groundModel: buildGroundModelFromGeotechReport(geotechResult, boreholeProfile, sourceLabel),
1944
+ groundModel,
1945
+ agentReviews,
1946
+ sourcePages,
1580
1947
  storedReview,
1581
1948
  approval,
1582
1949
  footerNotes: buildFooterNotes(geotechResult),
1583
1950
  };
1584
1951
  }
1585
1952
  const boreholeResult = result;
1953
+ const boreholeProfile = buildBoreholeProfile(boreholeResult);
1954
+ const groundModel = buildGroundModelFromBoreholeIngest(boreholeResult, sourceLabel);
1955
+ const sourcePages = explicitSourcePages
1956
+ ?? buildIntegratedSourcePagesFromPageAudits(boreholeResult.pageAudits, sourceLabel, groundModel?.evidence ?? []);
1586
1957
  const firstBorehole = boreholeResult.boreholes[0];
1587
1958
  return {
1588
1959
  title: firstBorehole?.boreholeId
@@ -1603,7 +1974,10 @@ export function buildIngestDossier(result, options) {
1603
1974
  executiveItems: buildBoreholeExecutiveItems(boreholeResult, sourceLabel),
1604
1975
  insightCards: buildBoreholeInsightCards(boreholeResult),
1605
1976
  trustItems: buildBoreholeTrustItems(boreholeResult),
1606
- boreholeProfile: buildBoreholeProfile(boreholeResult),
1977
+ boreholeProfile,
1978
+ groundModel,
1979
+ agentReviews,
1980
+ sourcePages,
1607
1981
  storedReview,
1608
1982
  approval,
1609
1983
  footerNotes: buildFooterNotes(boreholeResult),