feat: establish reproducible FreeCAD web compatibility baseline
This commit is contained in:
39
scripts/freecad-semantic-comparator.mjs
Normal file
39
scripts/freecad-semantic-comparator.mjs
Normal file
@@ -0,0 +1,39 @@
|
||||
const finite = (value) => typeof value === 'number' && Number.isFinite(value)
|
||||
const toleranceFor = (scenario, path) => path.includes('boundingBox') ? scenario.tolerance.linear : scenario.tolerance.scalar
|
||||
const domainFor = (path) => path.includes('boundingBox') ? 'shape' : ['solids', 'faces', 'edges', 'vertices'].some((name) => path.endsWith(`.${name}`)) ? 'topology' : ['volume', 'area'].some((name) => path.endsWith(`.${name}`)) ? 'shape' : path.includes('objects') || path.includes('tree') ? 'objectTree' : path.includes('properties') ? 'properties' : 'identity'
|
||||
|
||||
export function compareSemanticResult(scenario, actual, { source = 'FreeCAD', target = 'Web' } = {}) {
|
||||
const differences = []
|
||||
const comparedDomains = new Set()
|
||||
const compare = (expected, received, path) => {
|
||||
const domain = domainFor(path)
|
||||
comparedDomains.add(domain)
|
||||
if (typeof expected === 'number') {
|
||||
if (!finite(received) || Math.abs(expected - received) > toleranceFor(scenario, path)) differences.push({ kind: 'numeric', classification: 'unknown', domain, path, expected, actual: received, tolerance: toleranceFor(scenario, path) })
|
||||
return
|
||||
}
|
||||
if (Array.isArray(expected)) {
|
||||
if (!Array.isArray(received) || received.length !== expected.length) differences.push({ kind: 'structure', classification: 'unknown', domain, path, expected: `array(${expected.length})`, actual: Array.isArray(received) ? `array(${received.length})` : typeof received })
|
||||
else expected.forEach((value, index) => compare(value, received[index], `${path}[${index}]`))
|
||||
return
|
||||
}
|
||||
if (expected && typeof expected === 'object') {
|
||||
if (!received || typeof received !== 'object' || Array.isArray(received)) { differences.push({ kind: 'structure', classification: 'unknown', domain, path, expected: 'object', actual: typeof received }); return }
|
||||
for (const [key, value] of Object.entries(expected)) compare(value, received[key], `${path}.${key}`)
|
||||
return
|
||||
}
|
||||
if (received !== expected) differences.push({ kind: 'value', classification: 'unknown', domain, path, expected, actual: received })
|
||||
}
|
||||
compare(scenario.expected, actual, scenario.id)
|
||||
const allowedRuntimeFields = new Set(['schemaVersion', 'fixtureId', 'freecadVersion', 'freecadVersion', 'runtimeStatus'])
|
||||
for (const key of Object.keys(actual || {})) if (!Object.prototype.hasOwnProperty.call(scenario.expected || {}, key) && !allowedRuntimeFields.has(key)) differences.push({ kind: 'field', classification: 'unknown', domain: domainFor(key), path: `${scenario.id}.${key}`, expected: 'absent', actual: actual[key] })
|
||||
return {
|
||||
schemaVersion: 1,
|
||||
fixtureId: scenario.id,
|
||||
source,
|
||||
target,
|
||||
status: differences.length === 0 ? 'pass' : 'fail-unknown-difference',
|
||||
domains: Object.fromEntries(['identity', 'shape', 'topology', 'objectTree', 'properties'].map((domain) => [domain, comparedDomains.has(domain) ? 'compared' : 'no-fixture-field'])),
|
||||
differences,
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user