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(", ")}`)