feat: add conservative topology history fallback

This commit is contained in:
2026-08-02 23:46:29 -04:00
parent d3d88b297b
commit 4d94109627
6 changed files with 83 additions and 3 deletions

View File

@@ -8,6 +8,8 @@ export type { ApplyPlacementInput, BitBybitViewportAdapter, BitBybitWebCadFacade
export { createEdgeSubshapeRefs, createSubshapeRefs, createVertexSubshapeRefs, matchSubshapes, signatureForEdge, signatureForFace, signatureForVertex } from './topologyNaming'
export { createPersistedTopoRef, migrateTopoRefs, parseTopoRef, resolveTopoRef, serializeTopoRef } from './topologyReferences'
export type { PersistedTopoRef, TopoRefResolution, TopologyMigration } from './topologyReferences'
export { captureSignatureTopologyHistory } from './topologyHistory'
export type { TopologyHistoryEntry, TopologyHistoryRelation, TopologyHistoryResult } from './topologyHistory'
export { BasicSketchSolverAdapter, cloneSketch, createSketch, solveSketch } from './sketcher'
export type { SketchConstraint, SketchDiagnostic, SketchGeometry, SketchPoint, SketchPointRef, SketchSnapshot, SketchSolveOptions, SketchSolveResult, SketchSolverAdapter, SketchSolverStatus } from './sketcher'
export { createFacadeGeometryRecomputeExecutor, executeFacadeRecomputeNode, RecomputeCoordinator } from './recomputeEngine'

View File

@@ -0,0 +1,55 @@
import type { SubshapeRef } from './types'
import { matchSubshapes, type SubshapeSignature } from './topologyNaming'
export type TopologyHistoryEntry = { ref: SubshapeRef; signature: SubshapeSignature }
export type TopologyHistoryRelation = {
relation: 'preserved' | 'modified' | 'generated' | 'deleted' | 'ambiguous'
sourceObjectId?: string
sourcePersistentId?: string
resultPersistentId?: string
candidates?: Array<{ sourceObjectId: string; persistentId: string }>
score: number
}
export type TopologyHistoryResult = {
operationId: string
provider: 'signature-fallback'
relations: TopologyHistoryRelation[]
counts: Record<TopologyHistoryRelation['relation'], number>
}
const sourceKey = (objectId: string, persistentId: string) => `${encodeURIComponent(objectId)}::${persistentId}`
export const captureSignatureTopologyHistory = (
operationId: string,
inputs: Array<{ objectId: string; entries: TopologyHistoryEntry[] }>,
output: TopologyHistoryEntry[],
): TopologyHistoryResult => {
if (!operationId.trim()) throw new RangeError('Topology history operationId is required.')
const sources = new Map<string, { objectId: string; persistentId: string; signature: SubshapeSignature }>()
const previous = inputs.flatMap(({ objectId, entries }) => entries.map((entry) => {
const key = sourceKey(objectId, entry.ref.persistentId)
sources.set(key, { objectId, persistentId: entry.ref.persistentId, signature: entry.signature })
return { ref: { ...entry.ref, persistentId: key }, signature: entry.signature }
}))
const matches = matchSubshapes(previous, output)
const counts: TopologyHistoryResult['counts'] = { preserved: 0, modified: 0, generated: 0, deleted: 0, ambiguous: 0 }
const relations = matches.map((match): TopologyHistoryRelation => {
if (match.status === 'new') return { relation: 'generated', resultPersistentId: match.current.persistentId, score: match.score }
if (match.status === 'deleted') {
const source = match.previousId ? sources.get(match.previousId) : undefined
return { relation: 'deleted', sourceObjectId: source?.objectId, sourcePersistentId: source?.persistentId, score: 0 }
}
if (match.status === 'ambiguous') {
const candidates = (match.current.candidates ?? []).map((id) => sources.get(id)).filter((candidate): candidate is NonNullable<typeof candidate> => Boolean(candidate)).map((candidate) => ({ sourceObjectId: candidate.objectId, persistentId: candidate.persistentId }))
return { relation: 'ambiguous', resultPersistentId: match.current.persistentId, candidates, score: match.score }
}
const source = match.previousId ? sources.get(match.previousId) : undefined
const outputEntry = output.find((entry) => entry.ref.persistentId === match.current.persistentId || entry.ref.signature === match.current.signature)
const relation = source && outputEntry && source.signature.hash === outputEntry.signature.hash ? 'preserved' : 'modified'
return { relation, sourceObjectId: source?.objectId, sourcePersistentId: source?.persistentId, resultPersistentId: match.current.persistentId, score: match.score }
})
for (const relation of relations) counts[relation.relation] += 1
return { operationId, provider: 'signature-fallback', relations, counts }
}