Files
Web_FreeCAD_Bitbybit/scripts/freecad-semantic-comparator.mjs

40 lines
2.9 KiB
JavaScript

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,
}
}