ShiboSoftwareDev/linux-gameboy-advance
Restores PCB assembly metadata by assigning part numbers and 3D CAD models, removing test-point bodies, and validating top-side SMT solder-paste features without altering copper or pads.
- Version
- 1.0.17
- License
- unset
- Stars
- 0
scripts/audit-jlc.mjs
import assert from "node:assert/strict"
import fs from "node:fs"
const circuit = JSON.parse(fs.readFileSync("dist/index/circuit.json", "utf8"))
const sourceComponents = circuit.filter((record) => record.type === "source_component")
const sourceById = new Map(sourceComponents.map((component) => [component.source_component_id, component]))
const fitted = circuit
.filter((record) => record.type === "pcb_component" && !record.do_not_place)
.map((pcb) => sourceById.get(pcb.source_component_id))
.filter((source) => source && source.ftype !== "simple_test_point")
const referencesByPart = new Map()
for (const component of fitted) {
const parts = component.supplier_part_numbers?.jlcpcb ?? []
assert.ok(parts.length > 0, `${component.name} has no JLCPCB part number`)
for (const part of parts) {
assert.match(part, /^C\d+$/, `${component.name}: invalid JLCPCB part number ${part}`)
if (!referencesByPart.has(part)) referencesByPart.set(part, new Set())
referencesByPart.get(part).add(component.name)
}
}
const partNumbers = [...referencesByPart.keys()].sort((a, b) => Number(a.slice(1)) - Number(b.slice(1)))
const results = []
const batchSize = 8
for (let start = 0; start < partNumbers.length; start += batchSize) {
const batch = partNumbers.slice(start, start + batchSize)
results.push(...await Promise.all(batch.map(async (partNumber) => {
const url = `https://jlcsearch.tscircuit.com/api/search?q=${partNumber}`
const response = await fetch(url, { headers: { accept: "application/json" } })
if (!response.ok) throw new Error(`${partNumber}: inventory request returned HTTP ${response.status}`)
const payload = await response.json()
const numericId = Number(partNumber.slice(1))
const match = payload.components?.find((component) => component.lcsc === numericId)
const references = [...referencesByPart.get(partNumber)].sort()
const requiredQuantity = references.length
return {
partNumber,
references,
requiredQuantity,
found: Boolean(match),
manufacturerPartNumber: match?.mfr ?? null,
package: match?.package ?? null,
stock: match?.stock ?? 0,
sufficientStock: Boolean(match) && (match?.stock ?? 0) >= requiredQuantity,
basic: match?.is_basic ?? false,
preferred: match?.is_preferred ?? false,
queryUrl: url,
}
})))
}
const unavailable = results.filter((part) => !part.sufficientStock)
const report = {
auditedAt: new Date().toISOString(),
source: "jlcsearch.tscircuit.com live index",
note: "Indexed stock is checked against this board's fitted quantity, but is only a point-in-time availability check and not an inventory reservation. Recheck immediately before ordering.",
fittedComponents: fitted.length,
uniqueJlcpcbPartNumbers: results.length,
availablePartNumbers: results.length - unavailable.length,
unavailablePartNumbers: unavailable.map((part) => part.partNumber),
parts: results,
}
fs.writeFileSync("jlc-availability.json", `${JSON.stringify(report, null, 2)}\n`)
console.log(JSON.stringify({
auditedAt: report.auditedAt,
fittedComponents: report.fittedComponents,
uniqueJlcpcbPartNumbers: report.uniqueJlcpcbPartNumbers,
availablePartNumbers: report.availablePartNumbers,
unavailablePartNumbers: report.unavailablePartNumbers,
}, null, 2))
assert.deepEqual(unavailable, [], `unavailable JLCPCB parts: ${unavailable.map((part) => part.partNumber).join(", ")}`)