Devesh36/key-finder

A compact BLE key finder for keys, bags and remotes: replaceable CR2032, piezo alert, status LED and pair button in a 38 mm black keyring enclosure. Includes routed PCB, enclosed/exploded assembly and BOM. Firmware/app and physical qualification remain separate.

Version
0.1.2
License
unset
Stars
0

scripts/validate.ts

import {readFileSync} from 'node:fs'
import {runAllChecks} from '@tscircuit/checks'
import {any_circuit_element, pcb_component, type AnyCircuitElement} from 'circuit-json'
import {nets} from '../index.circuit'
const raw=JSON.parse(readFileSync('dist/index/circuit.json','utf8')) as AnyCircuitElement[]
const schemaErrors=raw.flatMap((e,i)=>{
 const result=(e.type==='pcb_component'?pcb_component:any_circuit_element).safeParse(e)
 return result.success ? [] : [{index:i,type:e.type,issues:result.error.issues}]
})
const emitted=raw.filter(e=>e.type.endsWith('_error') || e.type.endsWith('_warning'))
const independent=await runAllChecks(raw)
const components=raw.filter(e=>e.type==='source_component')
const ports=raw.filter(e=>e.type==='source_port')
const sourceNets=raw.filter(e=>e.type==='source_net')
const sourceTraces=raw.filter(e=>e.type==='source_trace')
const connectivityErrors:string[]=[]
for(const [name,pins] of Object.entries(nets)){
 const net=sourceNets.find(n=>n.name===name)
 if(!net){connectivityErrors.push(`Missing net ${name}`);continue}
 for(const token of pins){
  const [ref,pin]=token.split('.')
  const component=components.find(c=>c.name===ref)
  const port=ports.find(p=>p.source_component_id===component?.source_component_id && (p.name===pin || p.port_hints?.includes(pin)))
  if(!port || !sourceTraces.some(t=>t.connected_source_port_ids.includes(port.source_port_id) && t.connected_source_net_ids?.includes(net.source_net_id))){
   connectivityErrors.push(`${token} missing from ${name}`)
  }
 }
}
const board=raw.find(e=>e.type==='pcb_board')!
const traces=raw.filter(e=>e.type==='pcb_trace')
const vias=raw.filter(e=>e.type==='pcb_via')
const antennaViolations=traces.flatMap(t=>t.route.filter(p=>p.route_type==='wire' && p.x>=-9 && p.x<=9 && p.y < -10.71).map(()=>t.pcb_trace_id))
const labelErrors:string[]=[]
const stats={components:components.length,populatedComponents:components.filter(c=>c.ftype!=='simple_test_point').length,nets:sourceNets.length,traces:traces.length,vias:vias.length,width:board.width,height:board.height,layers:board.num_layers,
 errorCount:emitted.filter(e=>e.type.endsWith('_error')).length,warningCount:emitted.filter(e=>e.type.endsWith('_warning')).length,
 independentErrorCount:independent.filter(e=>e.type.endsWith('_error')).length,schemaErrorCount:schemaErrors.length,connectivityErrorCount:connectivityErrors.length,antennaViolationCount:antennaViolations.length,labelPositionErrorCount:labelErrors.length}
const assemblyReports=[]
for(const entry of ['assembly','exploded']){
 const elements=JSON.parse(readFileSync(`dist/${entry}/circuit.json`,'utf8')) as AnyCircuitElement[]
 const checked=await runAllChecks(elements)
 assemblyReports.push({entry,schemaErrors:elements.filter(e=>!any_circuit_element.safeParse(e).success).length,errors:elements.filter(e=>e.type.endsWith('_error')).length,independentErrors:checked.filter(e=>e.type.endsWith('_error')).length,warnings:elements.filter(e=>e.type.endsWith('_warning')).length})
}
const report={stats,assemblyReports,schemaErrors,connectivityErrors,antennaViolations,labelErrors,emitted,independent,scope:'Software checks only. Not bench, RF, thermal, assembly-tolerance or production qualification.'}
console.log(JSON.stringify(report,null,2))
const ok=stats.errorCount===0 && stats.independentErrorCount===0 && !schemaErrors.length && !connectivityErrors.length && !antennaViolations.length && !labelErrors.length && traces.length>0 && assemblyReports.every(r=>!r.schemaErrors && !r.errors && !r.independentErrors)
process.exitCode=ok?0:1