@geotechcli/core 0.4.110 → 0.4.111

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 (35) hide show
  1. package/dist/agents/brain.d.ts.map +1 -1
  2. package/dist/agents/brain.js +31 -2
  3. package/dist/agents/brain.js.map +1 -1
  4. package/dist/agents/fem-tools.js +5 -0
  5. package/dist/agents/fem-tools.js.map +1 -1
  6. package/dist/agents/safety.d.ts +1 -0
  7. package/dist/agents/safety.d.ts.map +1 -1
  8. package/dist/agents/safety.js +62 -0
  9. package/dist/agents/safety.js.map +1 -1
  10. package/dist/fem/engineering-evidence.d.ts +21 -2
  11. package/dist/fem/engineering-evidence.d.ts.map +1 -1
  12. package/dist/fem/engineering-evidence.js +257 -6
  13. package/dist/fem/engineering-evidence.js.map +1 -1
  14. package/dist/fem/index.d.ts +3 -2
  15. package/dist/fem/index.d.ts.map +1 -1
  16. package/dist/fem/index.js +1 -0
  17. package/dist/fem/index.js.map +1 -1
  18. package/dist/fem/nonlinear-plane-strain-solver.d.ts +10 -0
  19. package/dist/fem/nonlinear-plane-strain-solver.d.ts.map +1 -0
  20. package/dist/fem/nonlinear-plane-strain-solver.js +358 -0
  21. package/dist/fem/nonlinear-plane-strain-solver.js.map +1 -0
  22. package/dist/fem/plane-strain-assembly.d.ts +37 -0
  23. package/dist/fem/plane-strain-assembly.d.ts.map +1 -1
  24. package/dist/fem/plane-strain-assembly.js +209 -43
  25. package/dist/fem/plane-strain-assembly.js.map +1 -1
  26. package/dist/fem/production-readiness.js +2 -2
  27. package/dist/fem/production-readiness.js.map +1 -1
  28. package/dist/fem/types.d.ts +52 -3
  29. package/dist/fem/types.d.ts.map +1 -1
  30. package/dist/fem/validation.d.ts.map +1 -1
  31. package/dist/fem/validation.js +281 -10
  32. package/dist/fem/validation.js.map +1 -1
  33. package/dist/ingest/document-evidence-packet.d.ts +6 -6
  34. package/dist/meta/metadata.json +1 -1
  35. package/package.json +1 -1
@@ -69,6 +69,81 @@ const DEFAULT_EXTERNAL_BENCHMARK_REQUIRED_QUANTITIES = [
69
69
  requiredReferenceSourceTypes: REQUIRED_EXTERNAL_BENCHMARK_SOURCE_TYPES,
70
70
  },
71
71
  ];
72
+ const DEFAULT_EXTERNAL_BENCHMARK_REFERENCES = [
73
+ {
74
+ id: 'terzaghi-1943-theoretical-soil-mechanics',
75
+ sourceType: 'published-source',
76
+ label: 'Terzaghi 1D consolidation and effective-stress source reference',
77
+ citation: 'Terzaghi, K. (1943). Theoretical Soil Mechanics. John Wiley & Sons. doi:10.1002/9780470172766.',
78
+ publishedSource: {
79
+ title: 'Theoretical Soil Mechanics',
80
+ authors: ['Karl Terzaghi'],
81
+ year: 1943,
82
+ publication: 'John Wiley & Sons',
83
+ doi: '10.1002/9780470172766',
84
+ url: 'https://doi.org/10.1002/9780470172766',
85
+ },
86
+ },
87
+ {
88
+ id: 'biot-1941-three-dimensional-consolidation',
89
+ sourceType: 'published-source',
90
+ label: 'Biot three-dimensional consolidation theory source reference',
91
+ citation: 'Biot, M. A. (1941). General Theory of Three-Dimensional Consolidation. Journal of Applied Physics, 12(2), 155-164. doi:10.1063/1.1712886.',
92
+ publishedSource: {
93
+ title: 'General Theory of Three-Dimensional Consolidation',
94
+ authors: ['Maurice A. Biot'],
95
+ year: 1941,
96
+ publication: 'Journal of Applied Physics',
97
+ doi: '10.1063/1.1712886',
98
+ url: 'https://doi.org/10.1063/1.1712886',
99
+ },
100
+ },
101
+ {
102
+ id: 'opensees-drucker-prager-material',
103
+ sourceType: 'open-source-solver',
104
+ label: 'OpenSees Drucker-Prager material and triaxial example reference',
105
+ citation: 'OpenSees Documentation, Drucker Prager Material, nDMaterial DruckerPrager plane-strain/3D formulation and confined triaxial compression example.',
106
+ referenceSolver: {
107
+ name: 'OpenSees',
108
+ version: 'documentation-current',
109
+ vendor: 'OpenSees project',
110
+ analysisProcedure: 'Drucker-Prager material point and confined triaxial compression example',
111
+ elementType: 'nDMaterial DruckerPrager',
112
+ url: 'https://opensees.github.io/OpenSeesDocumentation/user/manual/material/ndMaterials/DruckerPrager.html',
113
+ retrievedAt: '2026-06-04',
114
+ },
115
+ },
116
+ {
117
+ id: 'opengeosys-hydro-mechanics-benchmarks',
118
+ sourceType: 'open-source-solver',
119
+ label: 'OpenGeoSys hydro-mechanics benchmark suite reference',
120
+ citation: 'OpenGeoSys stable documentation, Hydro Mechanics benchmark suite including consolidation, Mandel-Cryer, injection/production, and HM drainage excavation examples.',
121
+ referenceSolver: {
122
+ name: 'OpenGeoSys',
123
+ version: 'stable documentation',
124
+ vendor: 'OpenGeoSys project',
125
+ analysisProcedure: 'hydro-mechanics benchmark suite',
126
+ elementType: 'HM and LIE/HM finite elements',
127
+ url: 'https://www.opengeosys.org/docs/benchmarks/hydro-mechanics/',
128
+ retrievedAt: '2026-06-04',
129
+ },
130
+ },
131
+ {
132
+ id: 'opengeosys-richards-flow-benchmarks',
133
+ sourceType: 'open-source-solver',
134
+ label: 'OpenGeoSys Richards flow benchmark reference',
135
+ citation: 'OpenGeoSys documentation, Richards Flow benchmark suite for saturated/unsaturated transient flow verification.',
136
+ referenceSolver: {
137
+ name: 'OpenGeoSys',
138
+ version: '6.x documentation',
139
+ vendor: 'OpenGeoSys project',
140
+ analysisProcedure: 'Richards flow benchmark',
141
+ elementType: 'Richards flow finite elements',
142
+ url: 'https://www.opengeosys.org/docs/benchmarks/richards-flow/',
143
+ retrievedAt: '2026-06-04',
144
+ },
145
+ },
146
+ ];
72
147
  function degToRad(degrees) {
73
148
  return (degrees * Math.PI) / 180;
74
149
  }
@@ -105,6 +180,9 @@ function hasReferenceSolverCitation(citation) {
105
180
  isNonEmptyString(citation.name) &&
106
181
  isNonEmptyString(citation.version);
107
182
  }
183
+ function hasValidSha256(value) {
184
+ return typeof value === 'string' && /^[a-f0-9]{64}$/i.test(value);
185
+ }
108
186
  function copyExternalBenchmarkReference(reference) {
109
187
  return {
110
188
  ...reference,
@@ -129,11 +207,22 @@ function copyExternalBenchmarkQuantityRequirement(requirement) {
129
207
  requiredReferenceSourceTypes: [...new Set(requirement.requiredReferenceSourceTypes)],
130
208
  };
131
209
  }
210
+ function copyExternalBenchmarkComparisonResult(result) {
211
+ return {
212
+ ...result,
213
+ ...(result.notes ? { notes: [...result.notes] } : {}),
214
+ };
215
+ }
132
216
  export function buildFemExternalBenchmarkAcceptanceContract(options = {}) {
133
- const references = (options.references ?? []).map(copyExternalBenchmarkReference);
217
+ const references = (options.references ?? DEFAULT_EXTERNAL_BENCHMARK_REFERENCES)
218
+ .map(copyExternalBenchmarkReference);
134
219
  const requiredQuantities = (options.requiredQuantities ?? DEFAULT_EXTERNAL_BENCHMARK_REQUIRED_QUANTITIES)
135
220
  .map(copyExternalBenchmarkQuantityRequirement);
221
+ const comparisonResults = (options.comparisonResults ?? [])
222
+ .map(copyExternalBenchmarkComparisonResult);
136
223
  const blockerCodes = [];
224
+ const referenceById = new Map(references.map((reference) => [reference.id, reference]));
225
+ const quantityById = new Map(requiredQuantities.map((requirement) => [requirement.id, requirement]));
137
226
  if (references.length === 0) {
138
227
  blockerCodes.push('external-benchmark-reference-corpus-missing');
139
228
  }
@@ -165,6 +254,9 @@ export function buildFemExternalBenchmarkAcceptanceContract(options = {}) {
165
254
  const hasPublishedReference = references.some((reference) => reference.sourceType === 'published-source' &&
166
255
  isNonEmptyString(reference.citation) &&
167
256
  hasPublishedCitation(reference.publishedSource));
257
+ const hasCommercialReference = references.some((reference) => reference.sourceType === 'commercial-solver' &&
258
+ isNonEmptyString(reference.citation) &&
259
+ hasReferenceSolverCitation(reference.referenceSolver));
168
260
  const hasReferenceSolver = references.some((reference) => (reference.sourceType === 'commercial-solver' || reference.sourceType === 'open-source-solver') &&
169
261
  isNonEmptyString(reference.citation) &&
170
262
  hasReferenceSolverCitation(reference.referenceSolver));
@@ -174,9 +266,15 @@ export function buildFemExternalBenchmarkAcceptanceContract(options = {}) {
174
266
  if (!hasReferenceSolver) {
175
267
  blockerCodes.push('external-benchmark-reference-solver-citation-missing');
176
268
  }
269
+ if (!hasCommercialReference) {
270
+ blockerCodes.push('external-benchmark-commercial-solver-citation-missing');
271
+ }
177
272
  if (requiredQuantities.length === 0) {
178
273
  blockerCodes.push('external-benchmark-required-quantities-missing');
179
274
  }
275
+ if (comparisonResults.length === 0) {
276
+ blockerCodes.push('external-benchmark-comparison-results-missing');
277
+ }
180
278
  for (const [index, requirement] of requiredQuantities.entries()) {
181
279
  const quantityCode = isNonEmptyString(requirement.id)
182
280
  ? requirement.id
@@ -209,25 +307,119 @@ export function buildFemExternalBenchmarkAcceptanceContract(options = {}) {
209
307
  }
210
308
  }
211
309
  }
310
+ for (const [index, result] of comparisonResults.entries()) {
311
+ const resultCode = isNonEmptyString(result.id) ? result.id : String(index);
312
+ if (!isNonEmptyString(result.id)) {
313
+ blockerCodes.push(`external-benchmark.comparison-results.${index}.id-missing`);
314
+ }
315
+ if (!isNonEmptyString(result.quantityRequirementId) || !quantityById.has(result.quantityRequirementId)) {
316
+ blockerCodes.push(`external-benchmark.comparison-results.${resultCode}.quantity-requirement-missing`);
317
+ }
318
+ if (!isNonEmptyString(result.referenceId) || !referenceById.has(result.referenceId)) {
319
+ blockerCodes.push(`external-benchmark.comparison-results.${resultCode}.reference-missing`);
320
+ }
321
+ if (!isNonEmptyString(result.caseId)) {
322
+ blockerCodes.push(`external-benchmark.comparison-results.${resultCode}.case-id-missing`);
323
+ }
324
+ if (!isNonEmptyString(result.quantity)) {
325
+ blockerCodes.push(`external-benchmark.comparison-results.${resultCode}.quantity-missing`);
326
+ }
327
+ if (!isNonEmptyString(result.unit)) {
328
+ blockerCodes.push(`external-benchmark.comparison-results.${resultCode}.unit-missing`);
329
+ }
330
+ if (!Number.isFinite(result.actual) || !Number.isFinite(result.expected)) {
331
+ blockerCodes.push(`external-benchmark.comparison-results.${resultCode}.value-invalid`);
332
+ }
333
+ if (!Number.isFinite(result.tolerance) || result.tolerance < 0) {
334
+ blockerCodes.push(`external-benchmark.comparison-results.${resultCode}.tolerance-invalid`);
335
+ }
336
+ if (result.toleranceType !== 'absolute' &&
337
+ result.toleranceType !== 'relative' &&
338
+ result.toleranceType !== 'absolute-or-relative') {
339
+ blockerCodes.push(`external-benchmark.comparison-results.${resultCode}.tolerance-type-invalid`);
340
+ }
341
+ if (!hasValidSha256(result.evidenceHashSha256)) {
342
+ blockerCodes.push(`external-benchmark.comparison-results.${resultCode}.evidence-hash-missing`);
343
+ }
344
+ const requirement = quantityById.get(result.quantityRequirementId);
345
+ if (requirement) {
346
+ if (result.quantity !== requirement.quantity) {
347
+ blockerCodes.push(`external-benchmark.comparison-results.${resultCode}.quantity-mismatch`);
348
+ }
349
+ if (result.unit !== requirement.unit) {
350
+ blockerCodes.push(`external-benchmark.comparison-results.${resultCode}.unit-mismatch`);
351
+ }
352
+ if (result.toleranceType !== requirement.toleranceType) {
353
+ blockerCodes.push(`external-benchmark.comparison-results.${resultCode}.tolerance-type-mismatch`);
354
+ }
355
+ if (result.tolerance > requirement.tolerance) {
356
+ blockerCodes.push(`external-benchmark.comparison-results.${resultCode}.tolerance-too-loose`);
357
+ }
358
+ }
359
+ if (Number.isFinite(result.actual) &&
360
+ Number.isFinite(result.expected) &&
361
+ Number.isFinite(result.tolerance) &&
362
+ result.tolerance >= 0) {
363
+ const acceptanceTolerance = requirement?.tolerance ?? result.tolerance;
364
+ const acceptanceToleranceType = requirement?.toleranceType ?? result.toleranceType;
365
+ const acceptanceUnit = requirement?.unit ?? result.unit;
366
+ const tolerance = evaluateFemTolerance(requirement?.quantity ?? result.quantity, result.actual, result.expected, acceptanceToleranceType === 'relative' ? 0 : acceptanceTolerance, acceptanceToleranceType === 'absolute'
367
+ ? { unit: acceptanceUnit }
368
+ : { relativeTolerance: acceptanceTolerance, unit: acceptanceUnit });
369
+ if (!result.accepted || !tolerance.accepted) {
370
+ blockerCodes.push(`external-benchmark.comparison-results.${resultCode}.not-accepted`);
371
+ }
372
+ }
373
+ }
374
+ for (const requirement of requiredQuantities) {
375
+ if (!isNonEmptyString(requirement.id))
376
+ continue;
377
+ for (const sourceType of requirement.requiredReferenceSourceTypes) {
378
+ const hasAcceptedComparison = comparisonResults.some((result) => {
379
+ const reference = referenceById.get(result.referenceId);
380
+ if (!reference || reference.sourceType !== sourceType)
381
+ return false;
382
+ if (result.quantityRequirementId !== requirement.id || !result.accepted)
383
+ return false;
384
+ if (!Number.isFinite(result.actual) || !Number.isFinite(result.expected))
385
+ return false;
386
+ if (!hasValidSha256(result.evidenceHashSha256))
387
+ return false;
388
+ if (result.quantity !== requirement.quantity || result.unit !== requirement.unit)
389
+ return false;
390
+ if (result.toleranceType !== requirement.toleranceType || result.tolerance > requirement.tolerance) {
391
+ return false;
392
+ }
393
+ const tolerance = evaluateFemTolerance(requirement.quantity, result.actual, result.expected, requirement.toleranceType === 'relative' ? 0 : requirement.tolerance, requirement.toleranceType === 'absolute'
394
+ ? { unit: requirement.unit }
395
+ : { relativeTolerance: requirement.tolerance, unit: requirement.unit });
396
+ return tolerance.accepted;
397
+ });
398
+ if (!hasAcceptedComparison) {
399
+ blockerCodes.push(`external-benchmark.required-quantities.${requirement.id}.accepted-comparison-missing.${sourceType}`);
400
+ }
401
+ }
402
+ }
212
403
  const uniqueBlockerCodes = [...new Set(blockerCodes)];
213
404
  const productionReadinessBlocked = uniqueBlockerCodes.length > 0;
214
405
  return {
215
- schemaVersion: 'fem-external-benchmark-acceptance-metadata.v1',
216
- status: productionReadinessBlocked ? 'blocked' : 'metadata-ready',
406
+ schemaVersion: 'fem-external-benchmark-acceptance.v2',
407
+ status: productionReadinessBlocked ? 'blocked' : 'accepted-comparisons-ready',
217
408
  productionReadinessBlocked,
218
409
  requiredSourceTypes: [...REQUIRED_EXTERNAL_BENCHMARK_SOURCE_TYPES],
219
410
  references,
220
411
  requiredQuantities,
412
+ comparisonResults,
221
413
  blockerCodes: uniqueBlockerCodes,
222
414
  acceptanceStatement: productionReadinessBlocked
223
- ? 'External benchmark metadata is incomplete; production readiness remains blocked until published source citations, reference-solver citations, and required quantity tolerances are registered.'
224
- : 'External benchmark metadata is ready for independent result comparison; this does not approve production FEM design use.',
415
+ ? 'External benchmark acceptance is incomplete; production readiness remains blocked until source citations, commercial solver references, and accepted comparison results cover every required FEM quantity.'
416
+ : 'External benchmark references and comparison results cover the required FEM quantities; this still does not approve production design without solver-route and reviewer-workflow acceptance.',
225
417
  };
226
418
  }
227
419
  export function evaluateFemTolerance(quantity, actual, expected, absoluteTolerance, options = {}) {
228
420
  const error = Math.abs(actual - expected);
229
421
  const relativeError = Math.abs(expected) > 0 ? error / Math.abs(expected) : error;
230
- const relativeAccepted = options.relativeTolerance == null || relativeError <= options.relativeTolerance;
422
+ const relativeAccepted = options.relativeTolerance != null && relativeError <= options.relativeTolerance;
231
423
  const accepted = error <= absoluteTolerance || relativeAccepted;
232
424
  return {
233
425
  quantity,
@@ -1134,6 +1326,65 @@ export function runFemEngineeringEvidenceSuite(policy = DEFAULT_FEM_CONVERGENCE_
1134
1326
  loadStepFractions: [0.25, 0.5, 0.75, 1],
1135
1327
  });
1136
1328
  benchmarks.push(benchmark('quad4-plane-strain-dp-collapse-detection', 'coupled-nonlinear-plane-strain', 'internal-balance', 'collapseDetected', dpCollapse.converged ? 0 : 1, 1, 0, 'Low-strength/high-load plane-strain fixture must report nonconvergence instead of a clean accepted nonlinear solve.'));
1329
+ const adaptiveCutbackPolicy = {
1330
+ ...policy,
1331
+ maxIterations: Math.min(policy.maxIterations, 8),
1332
+ minAcceptedSteps: 1,
1333
+ };
1334
+ const dpAdaptiveDirect = runPlaneStrainDruckerPragerLoadSteps({
1335
+ schemaVersion: 'fem-plane-strain-model.v1',
1336
+ nodes: dpElasticMesh.nodes,
1337
+ elements: dpElasticMesh.elements,
1338
+ materials: [{
1339
+ id: 'soil',
1340
+ elasticModulusKpa: 25_000,
1341
+ poissonRatio: 0.28,
1342
+ frictionAngleDeg: 32,
1343
+ cohesionKpa: 5,
1344
+ dilationAngleDeg: 0,
1345
+ }],
1346
+ boundaryConditions: dpElasticBottomNodes.flatMap((node) => [
1347
+ { nodeId: node.id, dof: 'ux' },
1348
+ { nodeId: node.id, dof: 'uy' },
1349
+ ]),
1350
+ nodalLoads: dpElasticTopNodes.map((node) => ({ nodeId: node.id, fyKn: -30 })),
1351
+ policy: adaptiveCutbackPolicy,
1352
+ }, {
1353
+ loadStepFractions: [1],
1354
+ });
1355
+ const dpAdaptiveRecovered = runPlaneStrainDruckerPragerLoadSteps({
1356
+ schemaVersion: 'fem-plane-strain-model.v1',
1357
+ nodes: dpElasticMesh.nodes,
1358
+ elements: dpElasticMesh.elements,
1359
+ materials: [{
1360
+ id: 'soil',
1361
+ elasticModulusKpa: 25_000,
1362
+ poissonRatio: 0.28,
1363
+ frictionAngleDeg: 32,
1364
+ cohesionKpa: 5,
1365
+ dilationAngleDeg: 0,
1366
+ }],
1367
+ boundaryConditions: dpElasticBottomNodes.flatMap((node) => [
1368
+ { nodeId: node.id, dof: 'ux' },
1369
+ { nodeId: node.id, dof: 'uy' },
1370
+ ]),
1371
+ nodalLoads: dpElasticTopNodes.map((node) => ({ nodeId: node.id, fyKn: -30 })),
1372
+ policy: adaptiveCutbackPolicy,
1373
+ }, {
1374
+ loadStepFractions: [1],
1375
+ adaptiveLoadStepping: {
1376
+ minLoadFactorIncrement: 1 / 64,
1377
+ maxCutbacks: 20,
1378
+ },
1379
+ });
1380
+ const adaptiveRollbackAccepted = dpAdaptiveRecovered.converged &&
1381
+ !dpAdaptiveDirect.converged &&
1382
+ dpAdaptiveRecovered.adaptiveLoadStepping.cutbackCount > 0 &&
1383
+ dpAdaptiveRecovered.adaptiveLoadStepping.attempts.some((attempt) => !attempt.accepted &&
1384
+ attempt.rollbackApplied &&
1385
+ attempt.committedStateSignatureAfter === attempt.committedStateSignatureBefore) &&
1386
+ dpAdaptiveRecovered.adaptiveLoadStepping.acceptedLoadFactors.at(-1) === 1;
1387
+ benchmarks.push(benchmark('quad4-plane-strain-dp-adaptive-cutback-rollback-recovery', 'solver-convergence-and-tolerance', 'internal-balance', 'adaptiveCutbackRollbackAccepted', adaptiveRollbackAccepted ? 1 : 0, 1, 0, 'Adaptive cutback-bisection load stepping must recover a nonlinear Drucker-Prager plane-strain solve that fails as one full increment, while rejected attempts leave committed Gauss-point state unchanged.'));
1137
1388
  const finalTimeYears = 0.197 * 25;
1138
1389
  const consolidationTimes = Array.from({ length: 80 }, (_, index) => finalTimeYears * ((index + 1) / 80));
1139
1390
  const consolidation = runTerzaghiConsolidationTimeStepper({