"""Classify singleton unconnected nets without declaring them electrically safe.""" from collections import Counter import csv import json import re def classify(board, ref, pin): if board == 2 and re.fullmatch(r'J7[1-5]', ref): return 'phone_tail_contact', 'Check Xiaomi J1401 signal assignment and retained phone functions before NC' if board == 1 and ref == 'U20': return 'mcu_recovery' if pin in ('27', '36', '37') else 'mcu_spare_or_strap', 'Review recovery interface, boot straps and module memory variant' if board == 1 and ref in ('U6', 'U7'): return 'unused_dac_channel', 'Unused by current rail map; verify Hi-Z at reset, NVM restore and faults before NC' if board == 2 and ref == 'U32': if pin in ('21', '22', '27', '28', '35', '37', '38'): return 'hub_overcurrent', 'Internal pull-up documented; review port power control and system overcurrent responsibility' if pin in ('20', '23', '26', '29', '30', '36', '39'): return 'hub_port_power', 'Review firmware-controlled TPS22918 sequencing versus hub port power requests' if pin in ('53', '55'): return 'hub_unused_usb_port', 'Verify port 6/7 disable straps and USB enumeration' return 'hub_led_or_strap', 'Review sampled strap default and enabled LED function before NC' if board == 2 and re.fullmatch(r'U4[1-5]', ref) and pin == '3': return 'edl_normally_closed', 'NC means normally closed switch port; confirm COM/NO wiring and default-open EDL behavior' if board == 2 and ref == 'U34': return 'temperature_alert', 'Firmware polls temperature; review fault coverage without OS interrupt' if ref in ('J22', 'J23'): return 'usb_unused_contact', 'Confirm USB2-only SBU policy or externally powered debug VBUS policy' if board == 1 and ref == 'J1': return 'input_connector_mechanical', 'Confirm connector pad 3/4 are mechanical anchors and soldering requirements' return 'unclassified', 'Manual schematic and datasheet review required' def inventory(board): rows = [] for net, endpoints in sorted(board.net_members.items()): if len(endpoints) != 1 or not net.startswith('unconnected-'): continue ref, pin = next(iter(endpoints)) kind = board.pin_types[(ref, pin)] if 'no_connect' in kind.split('+'): continue category, action = classify(board.number, ref, pin) rows.append({'board': board.number, 'sheet': board.parts[ref]['page'], 'reference': ref, 'pin': pin, 'function': board.functions[(ref, pin)], 'type': kind, 'net': net, 'category': category, 'status': 'REVIEW_REQUIRED', 'next_action': action}) if len(rows) != board.erc.get('pin_not_connected', 0): raise ValueError('Singleton inventory does not account for every unconnected-pin ERC item') return rows def write_inventory(out, rows): with (out / 'unconnected-pins.csv').open('w', encoding='utf-8-sig', newline='') as stream: writer = csv.DictWriter(stream, fieldnames=['board', 'sheet', 'reference', 'pin', 'function', 'type', 'net', 'category', 'status', 'next_action']) writer.writeheader() writer.writerows(rows) summary = {'total': len(rows), 'by_category': dict(Counter(r['category'] for r in rows)), 'by_board': dict(Counter(r['board'] for r in rows)), 'note': 'Every row remains open; classification is not an NC approval or hardware acceptance'} (out / 'unconnected-summary.json').write_text(json.dumps(summary, indent=2) + '\n', encoding='utf-8') return summary