main
noah amfm-receiver-molecule: KiCad project, fab files, 3D and README (6/7) 53707c4 1d ago
"""Grouped BOM (CSV) with JLCPCB part data: LCSC #, MPN, Basic/Extended, live stock and unit price."""
import csv, os, re
from collections import OrderedDict
from design import DESIGN
import jlc

HERE = os.path.dirname(os.path.abspath(__file__))
OUT = os.path.abspath(os.path.join(HERE, '..', 'fab'))


def natural(ref):
    m = re.match(r'([A-Z]+)(\d+)', ref)
    return (m.group(1), int(m.group(2))) if m else (ref, 0)


def main(boards=5):
    os.makedirs(OUT, exist_ok=True)
    groups = OrderedDict()
    for p in sorted(DESIGN.parts, key=lambda p: natural(p.ref)):
        if not p.in_bom:
            continue
        groups.setdefault(p.fields.get('LCSC') or p.fields.get('MPN'), []).append(p)
    rows = []
    ext = []
    low_stock = []
    adom = []
    for code, parts in groups.items():
        p = parts[0]
        if 'LCSC' not in p.fields:              # Adom machine pins / contacts
            adom.append((code, parts))
            continue
        info = jlc.part(code) or {}
        kind = {'base': 'Basic', 'expand': 'Extended'}.get(info.get('type'), '?')
        if kind == 'Extended' and info.get('preferred'):
            kind = 'Preferred Extended'
        if kind == 'Extended':
            ext.append(code)
        need = len(parts) * boards
        if info.get('stock', 0) < need * 2:
            low_stock.append('%s (%s): stock %s' % (code, info.get('mpn'), info.get('stock')))
        rows.append(OrderedDict([
            ('Qty', len(parts)),
            ('Designators', ' '.join(q.ref for q in parts)),
            ('Value', ' / '.join(sorted(set(q.value for q in parts)))),
            ('Footprint', p.footprint.split(':')[1]),
            ('Description', p.description),
            ('LCSC', code),
            ('MPN', info.get('mpn') or p.fields.get('MPN', '')),
            ('Manufacturer', info.get('brand') or ''),
            ('JLC type', kind),
            ('JLC stock', info.get('stock', '')),
            ('Unit price USD', info.get('price', '')),
        ]))
    for mpn, parts in adom:
        rows.append(OrderedDict([
            ('Qty', len(parts)), ('Designators', ' '.join(q.ref for q in parts)),
            ('Value', mpn), ('Footprint', parts[0].footprint.split(':')[1]),
            ('Description', 'Adom %s (press-fit by the Adom Factory)' % mpn), ('LCSC', ''),
            ('MPN', mpn), ('Manufacturer', 'Adom'), ('JLC type', 'Adom-supplied, not assembled by JLC'),
            ('JLC stock', ''), ('Unit price USD', ''),
        ]))
    with open(os.path.join(OUT, 'amfm_receiver_BOM.csv'), 'w', newline='') as f:
        w = csv.DictWriter(f, fieldnames=list(rows[0].keys()))
        w.writeheader()
        w.writerows(rows)
    total = sum(r['Qty'] * (r['Unit price USD'] or 0) for r in rows)
    print('Adom machine parts: %s' % ', '.join('%d x %s' % (len(ps), m) for m, ps in adom))
    print('%d line items, %d placed parts, parts cost ~$%.2f per board, %d extended (setup fee) parts' % (
        len(rows), sum(r['Qty'] for r in rows), total, len(ext)))
    for s in low_stock:
        print('  LOW STOCK for %d boards: %s' % (boards, s))
    return rows


if __name__ == '__main__':
    main()