@geotechcli/core 0.4.109 → 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 (45) 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/demo.d.ts.map +1 -1
  11. package/dist/fem/demo.js +4 -0
  12. package/dist/fem/demo.js.map +1 -1
  13. package/dist/fem/engineering-evidence.d.ts +21 -2
  14. package/dist/fem/engineering-evidence.d.ts.map +1 -1
  15. package/dist/fem/engineering-evidence.js +354 -6
  16. package/dist/fem/engineering-evidence.js.map +1 -1
  17. package/dist/fem/index.d.ts +4 -2
  18. package/dist/fem/index.d.ts.map +1 -1
  19. package/dist/fem/index.js +2 -0
  20. package/dist/fem/index.js.map +1 -1
  21. package/dist/fem/nonlinear-plane-strain-solver.d.ts +10 -0
  22. package/dist/fem/nonlinear-plane-strain-solver.d.ts.map +1 -0
  23. package/dist/fem/nonlinear-plane-strain-solver.js +358 -0
  24. package/dist/fem/nonlinear-plane-strain-solver.js.map +1 -0
  25. package/dist/fem/plane-strain-assembly.d.ts +54 -0
  26. package/dist/fem/plane-strain-assembly.d.ts.map +1 -1
  27. package/dist/fem/plane-strain-assembly.js +277 -46
  28. package/dist/fem/plane-strain-assembly.js.map +1 -1
  29. package/dist/fem/production-readiness.js +5 -5
  30. package/dist/fem/production-readiness.js.map +1 -1
  31. package/dist/fem/support-design.d.ts +124 -0
  32. package/dist/fem/support-design.d.ts.map +1 -0
  33. package/dist/fem/support-design.js +380 -0
  34. package/dist/fem/support-design.js.map +1 -0
  35. package/dist/fem/types.d.ts +68 -3
  36. package/dist/fem/types.d.ts.map +1 -1
  37. package/dist/fem/validation.d.ts.map +1 -1
  38. package/dist/fem/validation.js +346 -13
  39. package/dist/fem/validation.js.map +1 -1
  40. package/dist/ingest/document-evidence-packet.d.ts +30 -30
  41. package/dist/ingest/job-worker.d.ts.map +1 -1
  42. package/dist/ingest/job-worker.js +4 -0
  43. package/dist/ingest/job-worker.js.map +1 -1
  44. package/dist/meta/metadata.json +1 -1
  45. package/package.json +1 -1
@@ -1,5 +1,6 @@
1
1
  import { calculateLateralEarthPressure } from '../geo/lateral-earth-pressure.js';
2
2
  import { buildPlaneStrainRectangularMesh, runPlaneStrainBiotConsolidation, runPlaneStrainDruckerPragerLoadSteps, runPlaneStrainQuad4Assembly, runPlaneStrainSteadySeepage, } from './plane-strain-assembly.js';
3
+ import { runFemSupportMemberDesignCheck } from './support-design.js';
3
4
  export const DEFAULT_FEM_CONVERGENCE_POLICY = {
4
5
  schemaVersion: 'fem-convergence-policy.v1',
5
6
  residualTolerance: 1e-6,
@@ -68,6 +69,81 @@ const DEFAULT_EXTERNAL_BENCHMARK_REQUIRED_QUANTITIES = [
68
69
  requiredReferenceSourceTypes: REQUIRED_EXTERNAL_BENCHMARK_SOURCE_TYPES,
69
70
  },
70
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
+ ];
71
147
  function degToRad(degrees) {
72
148
  return (degrees * Math.PI) / 180;
73
149
  }
@@ -104,6 +180,9 @@ function hasReferenceSolverCitation(citation) {
104
180
  isNonEmptyString(citation.name) &&
105
181
  isNonEmptyString(citation.version);
106
182
  }
183
+ function hasValidSha256(value) {
184
+ return typeof value === 'string' && /^[a-f0-9]{64}$/i.test(value);
185
+ }
107
186
  function copyExternalBenchmarkReference(reference) {
108
187
  return {
109
188
  ...reference,
@@ -128,11 +207,22 @@ function copyExternalBenchmarkQuantityRequirement(requirement) {
128
207
  requiredReferenceSourceTypes: [...new Set(requirement.requiredReferenceSourceTypes)],
129
208
  };
130
209
  }
210
+ function copyExternalBenchmarkComparisonResult(result) {
211
+ return {
212
+ ...result,
213
+ ...(result.notes ? { notes: [...result.notes] } : {}),
214
+ };
215
+ }
131
216
  export function buildFemExternalBenchmarkAcceptanceContract(options = {}) {
132
- const references = (options.references ?? []).map(copyExternalBenchmarkReference);
217
+ const references = (options.references ?? DEFAULT_EXTERNAL_BENCHMARK_REFERENCES)
218
+ .map(copyExternalBenchmarkReference);
133
219
  const requiredQuantities = (options.requiredQuantities ?? DEFAULT_EXTERNAL_BENCHMARK_REQUIRED_QUANTITIES)
134
220
  .map(copyExternalBenchmarkQuantityRequirement);
221
+ const comparisonResults = (options.comparisonResults ?? [])
222
+ .map(copyExternalBenchmarkComparisonResult);
135
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]));
136
226
  if (references.length === 0) {
137
227
  blockerCodes.push('external-benchmark-reference-corpus-missing');
138
228
  }
@@ -164,6 +254,9 @@ export function buildFemExternalBenchmarkAcceptanceContract(options = {}) {
164
254
  const hasPublishedReference = references.some((reference) => reference.sourceType === 'published-source' &&
165
255
  isNonEmptyString(reference.citation) &&
166
256
  hasPublishedCitation(reference.publishedSource));
257
+ const hasCommercialReference = references.some((reference) => reference.sourceType === 'commercial-solver' &&
258
+ isNonEmptyString(reference.citation) &&
259
+ hasReferenceSolverCitation(reference.referenceSolver));
167
260
  const hasReferenceSolver = references.some((reference) => (reference.sourceType === 'commercial-solver' || reference.sourceType === 'open-source-solver') &&
168
261
  isNonEmptyString(reference.citation) &&
169
262
  hasReferenceSolverCitation(reference.referenceSolver));
@@ -173,9 +266,15 @@ export function buildFemExternalBenchmarkAcceptanceContract(options = {}) {
173
266
  if (!hasReferenceSolver) {
174
267
  blockerCodes.push('external-benchmark-reference-solver-citation-missing');
175
268
  }
269
+ if (!hasCommercialReference) {
270
+ blockerCodes.push('external-benchmark-commercial-solver-citation-missing');
271
+ }
176
272
  if (requiredQuantities.length === 0) {
177
273
  blockerCodes.push('external-benchmark-required-quantities-missing');
178
274
  }
275
+ if (comparisonResults.length === 0) {
276
+ blockerCodes.push('external-benchmark-comparison-results-missing');
277
+ }
179
278
  for (const [index, requirement] of requiredQuantities.entries()) {
180
279
  const quantityCode = isNonEmptyString(requirement.id)
181
280
  ? requirement.id
@@ -208,25 +307,119 @@ export function buildFemExternalBenchmarkAcceptanceContract(options = {}) {
208
307
  }
209
308
  }
210
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
+ }
211
403
  const uniqueBlockerCodes = [...new Set(blockerCodes)];
212
404
  const productionReadinessBlocked = uniqueBlockerCodes.length > 0;
213
405
  return {
214
- schemaVersion: 'fem-external-benchmark-acceptance-metadata.v1',
215
- status: productionReadinessBlocked ? 'blocked' : 'metadata-ready',
406
+ schemaVersion: 'fem-external-benchmark-acceptance.v2',
407
+ status: productionReadinessBlocked ? 'blocked' : 'accepted-comparisons-ready',
216
408
  productionReadinessBlocked,
217
409
  requiredSourceTypes: [...REQUIRED_EXTERNAL_BENCHMARK_SOURCE_TYPES],
218
410
  references,
219
411
  requiredQuantities,
412
+ comparisonResults,
220
413
  blockerCodes: uniqueBlockerCodes,
221
414
  acceptanceStatement: productionReadinessBlocked
222
- ? 'External benchmark metadata is incomplete; production readiness remains blocked until published source citations, reference-solver citations, and required quantity tolerances are registered.'
223
- : '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.',
224
417
  };
225
418
  }
226
419
  export function evaluateFemTolerance(quantity, actual, expected, absoluteTolerance, options = {}) {
227
420
  const error = Math.abs(actual - expected);
228
421
  const relativeError = Math.abs(expected) > 0 ? error / Math.abs(expected) : error;
229
- const relativeAccepted = options.relativeTolerance == null || relativeError <= options.relativeTolerance;
422
+ const relativeAccepted = options.relativeTolerance != null && relativeError <= options.relativeTolerance;
230
423
  const accepted = error <= absoluteTolerance || relativeAccepted;
231
424
  return {
232
425
  quantity,
@@ -1133,6 +1326,65 @@ export function runFemEngineeringEvidenceSuite(policy = DEFAULT_FEM_CONVERGENCE_
1133
1326
  loadStepFractions: [0.25, 0.5, 0.75, 1],
1134
1327
  });
1135
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.'));
1136
1388
  const finalTimeYears = 0.197 * 25;
1137
1389
  const consolidationTimes = Array.from({ length: 80 }, (_, index) => finalTimeYears * ((index + 1) / 80));
1138
1390
  const consolidation = runTerzaghiConsolidationTimeStepper({
@@ -1244,6 +1496,14 @@ export function runFemEngineeringEvidenceSuite(policy = DEFAULT_FEM_CONVERGENCE_
1244
1496
  benchmarks.push(benchmark('quad4-plane-strain-biot-u-p-effective-stress-coupling', 'seepage-pore-pressure-coupling', 'internal-balance', 'effectiveMinusTotalStressError', biotStressSignError, 0, 1e-7, 'Biot u-p evidence kernel must apply positive pore pressure as total-stress reduction in the tension-positive plane-strain convention.', 'kPa'));
1245
1497
  benchmarks.push(benchmark('quad4-plane-strain-biot-u-p-free-residual', 'solver-convergence-and-tolerance', 'internal-balance', 'residualNormRatio', biot.residualNormRatio, 0, policy.forceBalanceTolerance, 'Biot u-p evidence kernel must satisfy the coupled mechanical free-DOF residual policy.'));
1246
1498
  benchmarks.push(benchmark('quad4-plane-strain-biot-u-p-mass-residual', 'coupled-biot-plane-strain', 'internal-balance', 'massBalanceErrorRatio', biot.massBalanceErrorRatio, 0, policy.porePressureMassBalanceTolerance, 'Biot u-p evidence kernel must satisfy the free pore-pressure residual policy for the backward-Euler pressure equation.'));
1499
+ benchmarks.push(benchmark('quad4-plane-strain-biot-u-p-transient-acceptance-policy', 'solver-convergence-and-tolerance', 'internal-balance', 'transientAcceptance', biot.transientAcceptance.accepted &&
1500
+ biot.transientAcceptance.acceptedStepCount >= policy.minAcceptedSteps &&
1501
+ biot.transientAcceptance.dissipationCheckMode === 'prescribed-gradient-relaxation' &&
1502
+ biot.transientAcceptance.maxResidualNormRatio <= policy.forceBalanceTolerance &&
1503
+ biot.transientAcceptance.maxMassBalanceErrorRatio <= policy.porePressureMassBalanceTolerance &&
1504
+ biot.transientAcceptance.maxPressureOvershootKpa === 0
1505
+ ? 1
1506
+ : 0, 1, 0, 'Biot u-p evidence kernel must report an aggregate transient acceptance audit for residual, mass-balance, pressure-envelope, and prescribed-gradient relaxation checks.'));
1247
1507
  const biotPatchMesh = buildPlaneStrainRectangularMesh({
1248
1508
  widthM: 2,
1249
1509
  heightM: 1,
@@ -1371,6 +1631,13 @@ export function runFemEngineeringEvidenceSuite(policy = DEFAULT_FEM_CONVERGENCE_
1371
1631
  }, 0) / biotTerzaghiVolumeM3;
1372
1632
  const biotTerzaghiDegreeOfConsolidation = 1 - (biotTerzaghiAveragePressureKpa / biotTerzaghiInitialPressureKpa);
1373
1633
  benchmarks.push(benchmark('quad4-plane-strain-biot-u-p-terzaghi-pressure-dissipation', 'consolidation', 'closed-form', 'degreeOfConsolidation', biotTerzaghiDegreeOfConsolidation, terzaghiAverageConsolidation(biotTerzaghiTimeFactor), 0.005, 'Alpha-zero Quad4 Biot pressure diffusion must match Terzaghi average consolidation at Tv = 0.197 for a top-drained column.'));
1634
+ benchmarks.push(benchmark('quad4-plane-strain-biot-u-p-drained-dissipation-acceptance', 'solver-convergence-and-tolerance', 'internal-balance', 'drainedDissipationAccepted', biotTerzaghi.transientAcceptance.accepted &&
1635
+ biotTerzaghi.transientAcceptance.dissipationCheckMode === 'drained-dissipation' &&
1636
+ biotTerzaghi.transientAcceptance.monotonicAverageFreePressureDissipationRequired &&
1637
+ biotTerzaghi.transientAcceptance.monotonicAverageFreePressureDissipation &&
1638
+ biotTerzaghi.transientAcceptance.monotonicMaxPressureEnvelope
1639
+ ? 1
1640
+ : 0, 1, 0, 'Top-drained alpha-zero Biot Terzaghi fixture must pass the stricter drained-dissipation transient acceptance gate.'));
1374
1641
  const coupling = runHydroMechanicalCoupling1D({
1375
1642
  totalVerticalStressKpa: 200,
1376
1643
  porePressureBeforeKpa: 80,
@@ -1401,6 +1668,87 @@ export function runFemEngineeringEvidenceSuite(policy = DEFAULT_FEM_CONVERGENCE_
1401
1668
  support.productionBlockers.includes('jurisdiction-specific-wall-strut-anchor-structural-design-not-implemented')
1402
1669
  ? 1
1403
1670
  : 0, 1, 0, 'Support screening must tie support reaction demand to staged excavation depths while preserving jurisdiction-specific structural design as a production blocker.'));
1671
+ const supportMember = runFemSupportMemberDesignCheck({
1672
+ schemaVersion: 'fem-support-member-design-input.v1',
1673
+ units: {
1674
+ length: 'm',
1675
+ force: 'kN',
1676
+ stress: 'MPa',
1677
+ area: 'm2',
1678
+ moment: 'kN-m',
1679
+ momentOfInertia: 'm4',
1680
+ sectionModulus: 'm3',
1681
+ utilization: 'ratio',
1682
+ },
1683
+ member: {
1684
+ id: 'support-strut-fixture',
1685
+ kind: 'strut',
1686
+ label: 'Benchmark excavation strut fixture',
1687
+ sectionLabel: 'Reviewed circular hollow strut proxy',
1688
+ unbracedLengthM: 4,
1689
+ effectiveLengthFactor: 1,
1690
+ areaM2: 0.015,
1691
+ weakAxisMomentOfInertiaM4: 1.2e-4,
1692
+ sectionModulusM3: 0.0012,
1693
+ yieldStrengthMpa: 250,
1694
+ elasticModulusMpa: 200_000,
1695
+ },
1696
+ demand: {
1697
+ axialCompressionDemandKn: 900,
1698
+ bendingMomentDemandKnM: 40,
1699
+ source: {
1700
+ source: 'support-reaction-screening',
1701
+ loadCombination: 'temporary-support-envelope',
1702
+ description: 'Fixture support reaction demand from deterministic excavation screening envelope.',
1703
+ caseId: 'support-fixture',
1704
+ stageId: 'stage-2',
1705
+ resultHashSha256: 'd'.repeat(64),
1706
+ },
1707
+ },
1708
+ factors: {
1709
+ demandFactor: 1.2,
1710
+ resistanceFactorCompression: 0.9,
1711
+ resistanceFactorFlexure: 0.9,
1712
+ maximumSlendernessRatio: 160,
1713
+ },
1714
+ review: {
1715
+ schemaVersion: 'fem-support-design-review-metadata.v1',
1716
+ reviewer: {
1717
+ name: 'Licensed Reviewer',
1718
+ licenseId: 'PE-12345',
1719
+ jurisdiction: 'US-CA',
1720
+ },
1721
+ reviewedAt: '2026-06-04T00:00:00.000Z',
1722
+ assumptions: [
1723
+ {
1724
+ id: 'support-effective-length',
1725
+ parameter: 'effective length factor',
1726
+ value: 1,
1727
+ unit: 'ratio',
1728
+ basis: 'Pinned temporary works end-restraint assumption for deterministic benchmark fixture.',
1729
+ confidence: 'review',
1730
+ reviewRequired: true,
1731
+ },
1732
+ {
1733
+ id: 'support-demand-source',
1734
+ parameter: 'support demand source',
1735
+ value: 'support-reaction-screening',
1736
+ basis: 'Demand is supplied explicitly from a reviewed support reaction screening envelope.',
1737
+ confidence: 'review',
1738
+ reviewRequired: true,
1739
+ },
1740
+ ],
1741
+ limitations: ['Connection, local buckling, and jurisdiction-specific code checks remain outside this benchmark fixture.'],
1742
+ },
1743
+ policy,
1744
+ });
1745
+ benchmarks.push(benchmark('support-member-yield-buckling-interaction', 'support-design', 'internal-balance', 'supportMemberAccepted', supportMember.status === 'accepted' &&
1746
+ supportMember.productionClaim === false &&
1747
+ supportMember.limitStates.every((limitState) => limitState.status === 'accepted') &&
1748
+ supportMember.controllingLimitState.id === 'combined-axial-flexure' &&
1749
+ supportMember.review.reviewer.licenseId.length > 0
1750
+ ? 1
1751
+ : 0, 1, 0, 'Support member fixture must pass deterministic axial yield, Euler buckling, flexural yield, slenderness, and combined utilization checks with reviewer metadata.'));
1404
1752
  const reviewerValidation = validateFemReviewerApprovalRecord({
1405
1753
  schemaVersion: 'fem-reviewer-approval.v1',
1406
1754
  recordId: 'review-fem-001',