From 96a9c2b4279d380d275b45541c46d76cc7c0c389 Mon Sep 17 00:00:00 2001 From: akhilles Date: Thu, 13 Nov 2025 16:58:34 -0500 Subject: [PATCH 1/2] Add power to Resistor, use it for matching --- bom/helpers.zen | 30 ++++++++++++- bom/match_generics.zen | 95 ++++++++++++++++++++++++++++++++++-------- generics/Resistor.zen | 8 +++- 3 files changed, 112 insertions(+), 21 deletions(-) diff --git a/bom/helpers.zen b/bom/helpers.zen index ff07329..dfef742 100644 --- a/bom/helpers.zen +++ b/bom/helpers.zen @@ -89,7 +89,7 @@ def iec60062_3digit(value): return sig_digits + str(mult) -def resistor_series(series, min_val, max_val, max_v, tolerance, e_series, encode, suffix): +def resistor_series(series, min_val, max_val, max_v, power, tolerance, e_series, encode, suffix): """Helper to create resistor series entry. Args: @@ -97,6 +97,7 @@ def resistor_series(series, min_val, max_val, max_v, tolerance, e_series, encode min_val: Min resistance (e.g., "10Ohm") max_val: Max resistance (e.g., "1MOhm") max_v: Max voltage (e.g., "50V") + power: Power rating (e.g., "0.1W") tolerance: Tolerance string (e.g., "1%") e_series: List of E-series (e.g., ["e96", "e24"]) encode: Encoding function (iec60062_3digit or iec60062_4digit) @@ -110,6 +111,7 @@ def resistor_series(series, min_val, max_val, max_v, tolerance, e_series, encode "min": min_val, "max": max_val, "max_v": max_v, + "power": power, "tolerance": tolerance, "e_series": e_series, "encode": encode, @@ -117,6 +119,32 @@ def resistor_series(series, min_val, max_val, max_v, tolerance, e_series, encode } +def discrete_resistor_series(manufacturer, series, values_with_mpns, max_v, power, tolerance): + """Helper to create discrete resistor series (non-E-series, exact values only). + + Args: + manufacturer: Manufacturer name (e.g., "Stackpole Electronics Inc") + series: Series name (e.g., "CSNL2512") + values_with_mpns: List of tuples [(resistance_str, mpn), ...] + e.g., [("0.5mOhm", "CSNL2512FTL500"), ("1mOhm", "CSNL2512FT1L00")] + max_v: Max voltage (e.g., "170V") + power: Power rating (e.g., "2W") + tolerance: Tolerance string (e.g., "1%") + + Returns: + Discrete resistor series dictionary + """ + return { + "type": "discrete", + "manufacturer": manufacturer, + "series": series, + "values": values_with_mpns, + "max_v": max_v, + "power": power, + "tolerance": tolerance, + } + + def murata_cap(dielectric, voltage, cap, base_mpn, suffixes, manufacturer="Murata Electronics"): """Helper to create Murata capacitor entry with auto-generated packaging variants. diff --git a/bom/match_generics.zen b/bom/match_generics.zen index ca4a84c..3c28c98 100644 --- a/bom/match_generics.zen +++ b/bom/match_generics.zen @@ -3,7 +3,7 @@ Automatically assigns house MPNs to resistors and capacitors. """ -load("../units.zen", "Voltage", "Resistance", "Capacitance") +load("../units.zen", "Voltage", "Resistance", "Capacitance", "Power") load( "helpers.zen", "prop", @@ -12,6 +12,7 @@ load( "iec60062_3digit", "iec60062_4digit", "resistor_series", + "discrete_resistor_series", "murata_cap", ) @@ -31,16 +32,50 @@ _DIELECTRIC_RANK = { # Resistor series by package SERIES_BY_PKG = { "0402": [ - resistor_series("ERJ-H2CF", "1Ohm", "10Ohm", "50V", "1%", ["e96", "e24"], iec60062_4digit, "X"), - resistor_series("ERJ-2RKF", "10Ohm", "1MOhm", "50V", "1%", ["e96", "e24"], iec60062_4digit, "X"), - resistor_series("ERJ-U02J", "1MOhm", "10MOhm", "50V", "5%", ["e24"], iec60062_3digit, "X"), - resistor_series("ERJ-PA2F", "1Ohm", "1MOhm", "50V", "1%", ["e96", "e24"], iec60062_4digit, "X"), + resistor_series("ERJ-H2CF", "1Ohm", "10Ohm", "50V", "0.1W", "1%", ["e96", "e24"], iec60062_4digit, "X"), + resistor_series("ERJ-2RKF", "10Ohm", "1MOhm", "50V", "0.1W", "1%", ["e96", "e24"], iec60062_4digit, "X"), + resistor_series("ERJ-U02J", "1MOhm", "10MOhm", "50V", "0.1W", "5%", ["e24"], iec60062_3digit, "X"), + resistor_series("ERJ-PA2F", "1Ohm", "1MOhm", "50V", "0.1W", "1%", ["e96", "e24"], iec60062_4digit, "X"), ], "0603": [ - resistor_series("ERJ-H3QF", "1Ohm", "10Ohm", "75V", "1%", ["e96", "e24"], iec60062_4digit, "V"), - resistor_series("ERJ-3EKF", "10Ohm", "1MOhm", "75V", "1%", ["e96", "e24"], iec60062_4digit, "V"), - resistor_series("ERJ-U03J", "1MOhm", "10MOhm", "75V", "5%", ["e24"], iec60062_3digit, "V"), - resistor_series("ERJ-PA3F", "1Ohm", "1MOhm", "75V", "1%", ["e96", "e24"], iec60062_4digit, "V"), + resistor_series("ERJ-H3QF", "1Ohm", "10Ohm", "75V", "0.333W", "1%", ["e96", "e24"], iec60062_4digit, "V"), + resistor_series("ERJ-3EKF", "10Ohm", "1MOhm", "75V", "0.333W", "1%", ["e96", "e24"], iec60062_4digit, "V"), + resistor_series("ERJ-U03J", "1MOhm", "10MOhm", "75V", "0.333W", "5%", ["e24"], iec60062_3digit, "V"), + resistor_series("ERJ-PA3F", "1Ohm", "1MOhm", "75V", "0.333W", "1%", ["e96", "e24"], iec60062_4digit, "V"), + ], + "2512": [ + # Stackpole CSNL series: Current sense resistors (discrete values) + discrete_resistor_series( + "Stackpole Electronics Inc", + "CSNL2512", + [ + ("1mOhm", "CSNL2512FT1L00"), + ("2mOhm", "CSNL2512FT2L00"), + ("3mOhm", "CSNL2512FT3L00"), + ("4mOhm", "CSNL2512FT4L00"), + ("5mOhm", "CSNL2512FT5L00"), + ("10mOhm", "CSNL2512FT10L0"), + ], + "170V", + "2W", + "1%", + ), + # YAGEO PA_E series: AEC-Q200 current sense resistors (discrete values) + discrete_resistor_series( + "YAGEO", + "PA_E", + [ + ("1mOhm", "PA2512FKF7W0R001E"), + ("2mOhm", "PA2512FKF7W0R002E"), + ("3mOhm", "PA2512FKF7W0R003E"), + ("4mOhm", "PA2512FKF7W0R004E"), + ("5mOhm", "PA2512FKF7W0R005E"), + ("10mOhm", "PA2512FKF7W0R01E"), + ], + "170V", + "2W", + "1%", + ), ], } @@ -105,6 +140,8 @@ def assign_house_resistor(c, series_by_pkg): pkg = prop(c, ["package", "Package"]) v_req = prop(c, ["Voltage", "voltage"]) voltage_constraint = Voltage(v_req).value if v_req else None + p_req = prop(c, ["Power", "power"]) + power_constraint = Power(p_req).value if p_req else None # Skip 0 ohm resistors - they can't be matched to E-series if resistance.value <= 0: @@ -112,26 +149,48 @@ def assign_house_resistor(c, series_by_pkg): series_list = series_by_pkg.get(pkg, []) matches = [] + manufacturer = None + for spec in series_list: - spec_min = Resistance(spec["min"]).value - spec_max = Resistance(spec["max"]).value + spec_type = spec.get("type", "e_series") spec_max_v = Voltage(spec["max_v"]).value + spec_power = Power(spec["power"]).value spec_tol = float(spec["tolerance"].replace("%", "")) / 100.0 if voltage_constraint and voltage_constraint > spec_max_v: continue - matched = find_e_series_match(resistance, spec_tol, spec["e_series"]) - if not matched or matched.value < spec_min or matched.value > spec_max: + if power_constraint and power_constraint > spec_power: continue - encoder = spec.get("encode", iec60062_4digit) - code = encoder(matched.value) - mpn = spec["series"] + code + spec.get("suffix", "X") - matches.append(mpn) + if spec_type == "discrete": + # Discrete series: exact value match with tolerance + r_with_tol = resistance.with_tolerance(spec_tol) if resistance.tolerance <= 0 else resistance + + for val_str, mpn in spec["values"]: + val_r = Resistance(val_str).with_tolerance(spec_tol) + if val_r.within(r_with_tol): + matches.append(mpn) + if manufacturer == None: + manufacturer = spec["manufacturer"] + else: + # E-series: standard matching logic + spec_min = Resistance(spec["min"]).value + spec_max = Resistance(spec["max"]).value + + matched = find_e_series_match(resistance, spec_tol, spec["e_series"]) + if not matched or matched.value < spec_min or matched.value > spec_max: + continue + + encoder = spec.get("encode", iec60062_4digit) + code = encoder(matched.value) + mpn = spec["series"] + code + spec.get("suffix", "X") + matches.append(mpn) + if manufacturer == None: + manufacturer = "Panasonic Electronic Components" if matches: - set_primary_and_alts(c, matches[0], "Panasonic Electronic Components", matches[1:]) + set_primary_and_alts(c, matches[0], manufacturer, matches[1:]) c.matcher = "assign_house_resistor" return diff --git a/generics/Resistor.zen b/generics/Resistor.zen index 7dc034f..95b77b2 100644 --- a/generics/Resistor.zen +++ b/generics/Resistor.zen @@ -1,5 +1,5 @@ load("../config.zen", "config_unit") -load("../units.zen", "Resistance", "Voltage") +load("../units.zen", "Resistance", "Voltage", "Power") load("../utils.zen", "format_value") # ----------------------------------------------------------------------------- @@ -22,6 +22,7 @@ mpn = config("mpn", str, optional=True) manufacturer = config("manufacturer", str, optional=True) mount = config("mount", Mount, default=Mount("SMD"), optional=True) voltage = config_unit("voltage", Voltage, optional=True) +power = config_unit("power", Power, optional=True) use_us_symbol = config("use_us_symbol", bool, default=False, optional=True) do_not_populate = config("do_not_populate", bool, default=False) @@ -34,12 +35,15 @@ if exclude_from_bom: # Properties – combined and normalized properties = { - "value": format_value(value, voltage), + "value": format_value(value, voltage, power), "package": package, "resistance": value, "voltage": voltage, } +if power: + properties["power"] = power + # ----------------------------------------------------------------------------- # IO ports # ----------------------------------------------------------------------------- From 23cff558ad7b80c48c34ccb393839542672eb642 Mon Sep 17 00:00:00 2001 From: akhilles Date: Thu, 13 Nov 2025 18:08:12 -0500 Subject: [PATCH 2/2] Use more physical value/range APIs --- bom/helpers.zen | 58 +++++++++++++++++++++++++++++--------- bom/match_generics.zen | 64 ++++++++++++++++++++---------------------- 2 files changed, 75 insertions(+), 47 deletions(-) diff --git a/bom/helpers.zen b/bom/helpers.zen index dfef742..daa18cd 100644 --- a/bom/helpers.zen +++ b/bom/helpers.zen @@ -1,6 +1,7 @@ """BOM matching helper functions""" load("../utils.zen", "e96", "e24") +load("../units.zen", "ResistanceRange", "Voltage", "Power", "Capacitance") def prop(c, names): @@ -89,13 +90,13 @@ def iec60062_3digit(value): return sig_digits + str(mult) -def resistor_series(series, min_val, max_val, max_v, power, tolerance, e_series, encode, suffix): +def resistor_series(manufacturer, series, resistance_range, max_v, power, tolerance, e_series, encode, suffix): """Helper to create resistor series entry. Args: + manufacturer: Manufacturer name (e.g., "Panasonic Electronic Components") series: Series prefix (e.g., "ERJ-2RKF") - min_val: Min resistance (e.g., "10Ohm") - max_val: Max resistance (e.g., "1MOhm") + resistance_range: Resistance range (e.g., "10Ohm – 1MOhm") max_v: Max voltage (e.g., "50V") power: Power rating (e.g., "0.1W") tolerance: Tolerance string (e.g., "1%") @@ -107,11 +108,11 @@ def resistor_series(series, min_val, max_val, max_v, power, tolerance, e_series, Resistor series dictionary """ return { + "manufacturer": manufacturer, "series": series, - "min": min_val, - "max": max_val, - "max_v": max_v, - "power": power, + "resistance_range": ResistanceRange(resistance_range), + "max_v": Voltage(max_v), + "power": Power(power), "tolerance": tolerance, "e_series": e_series, "encode": encode, @@ -119,17 +120,46 @@ def resistor_series(series, min_val, max_val, max_v, power, tolerance, e_series, } -def discrete_resistor_series(manufacturer, series, values_with_mpns, max_v, power, tolerance): +def panasonic_erj(series, resistance_range, voltage, power, tolerance, e_series, suffix, encode): + """Shorthand for Panasonic ERJ resistor series. + + Args: + series: Series name (e.g., "ERJ-3EKF") + resistance_range: Resistance range (e.g., "10Ohm – 1MOhm") + voltage: Max voltage (e.g., "75V") + power: Power rating (e.g., "0.333W") + tolerance: Tolerance string (e.g., "1%") + e_series: List of E-series (e.g., ["e96", "e24"]) + suffix: MPN suffix (e.g., "V") + encode: Encoding function (iec60062_3digit or iec60062_4digit) + + Returns: + Resistor series dictionary + """ + return resistor_series( + "Panasonic Electronic Components", + series, + resistance_range, + voltage, + power, + tolerance, + e_series, + encode, + suffix, + ) + + +def discrete_resistor_series(manufacturer, series, max_v, power, tolerance, values_with_mpns): """Helper to create discrete resistor series (non-E-series, exact values only). Args: manufacturer: Manufacturer name (e.g., "Stackpole Electronics Inc") series: Series name (e.g., "CSNL2512") - values_with_mpns: List of tuples [(resistance_str, mpn), ...] - e.g., [("0.5mOhm", "CSNL2512FTL500"), ("1mOhm", "CSNL2512FT1L00")] max_v: Max voltage (e.g., "170V") power: Power rating (e.g., "2W") tolerance: Tolerance string (e.g., "1%") + values_with_mpns: List of tuples [(resistance_str, mpn), ...] + e.g., [("0.5mOhm", "CSNL2512FTL500"), ("1mOhm", "CSNL2512FT1L00")] Returns: Discrete resistor series dictionary @@ -139,8 +169,8 @@ def discrete_resistor_series(manufacturer, series, values_with_mpns, max_v, powe "manufacturer": manufacturer, "series": series, "values": values_with_mpns, - "max_v": max_v, - "power": power, + "max_v": Voltage(max_v), + "power": Power(power), "tolerance": tolerance, } @@ -163,8 +193,8 @@ def murata_cap(dielectric, voltage, cap, base_mpn, suffixes, manufacturer="Murat return { "dielectric": dielectric, - "voltage": voltage, - "cap": cap, + "voltage": Voltage(voltage), + "cap": Capacitance(cap), "mpns": mpns, "manufacturer": manufacturer, } diff --git a/bom/match_generics.zen b/bom/match_generics.zen index 3c28c98..1591516 100644 --- a/bom/match_generics.zen +++ b/bom/match_generics.zen @@ -3,7 +3,7 @@ Automatically assigns house MPNs to resistors and capacitors. """ -load("../units.zen", "Voltage", "Resistance", "Capacitance", "Power") +load("../units.zen", "Voltage", "Resistance", "ResistanceRange", "Capacitance", "Power") load( "helpers.zen", "prop", @@ -11,7 +11,7 @@ load( "find_e_series_match", "iec60062_3digit", "iec60062_4digit", - "resistor_series", + "panasonic_erj", "discrete_resistor_series", "murata_cap", ) @@ -32,22 +32,25 @@ _DIELECTRIC_RANK = { # Resistor series by package SERIES_BY_PKG = { "0402": [ - resistor_series("ERJ-H2CF", "1Ohm", "10Ohm", "50V", "0.1W", "1%", ["e96", "e24"], iec60062_4digit, "X"), - resistor_series("ERJ-2RKF", "10Ohm", "1MOhm", "50V", "0.1W", "1%", ["e96", "e24"], iec60062_4digit, "X"), - resistor_series("ERJ-U02J", "1MOhm", "10MOhm", "50V", "0.1W", "5%", ["e24"], iec60062_3digit, "X"), - resistor_series("ERJ-PA2F", "1Ohm", "1MOhm", "50V", "0.1W", "1%", ["e96", "e24"], iec60062_4digit, "X"), + panasonic_erj("ERJ-H2CF", "1Ohm – 10Ohm", "50V", "0.1W", "1%", ["e96", "e24"], "X", iec60062_4digit), + panasonic_erj("ERJ-2RKF", "10Ohm – 1MOhm", "50V", "0.1W", "1%", ["e96", "e24"], "X", iec60062_4digit), + panasonic_erj("ERJ-U02J", "1MOhm – 10MOhm", "50V", "0.1W", "5%", ["e24"], "X", iec60062_3digit), + panasonic_erj("ERJ-PA2F", "1Ohm – 1MOhm", "50V", "0.1W", "1%", ["e96", "e24"], "X", iec60062_4digit), ], "0603": [ - resistor_series("ERJ-H3QF", "1Ohm", "10Ohm", "75V", "0.333W", "1%", ["e96", "e24"], iec60062_4digit, "V"), - resistor_series("ERJ-3EKF", "10Ohm", "1MOhm", "75V", "0.333W", "1%", ["e96", "e24"], iec60062_4digit, "V"), - resistor_series("ERJ-U03J", "1MOhm", "10MOhm", "75V", "0.333W", "5%", ["e24"], iec60062_3digit, "V"), - resistor_series("ERJ-PA3F", "1Ohm", "1MOhm", "75V", "0.333W", "1%", ["e96", "e24"], iec60062_4digit, "V"), + panasonic_erj("ERJ-H3QF", "1Ohm – 10Ohm", "75V", "0.333W", "1%", ["e96", "e24"], "V", iec60062_4digit), + panasonic_erj("ERJ-3EKF", "10Ohm – 1MOhm", "75V", "0.333W", "1%", ["e96", "e24"], "V", iec60062_4digit), + panasonic_erj("ERJ-U03J", "1MOhm – 10MOhm", "75V", "0.333W", "5%", ["e24"], "V", iec60062_3digit), + panasonic_erj("ERJ-PA3F", "1Ohm – 1MOhm", "75V", "0.333W", "1%", ["e96", "e24"], "V", iec60062_4digit), ], "2512": [ - # Stackpole CSNL series: Current sense resistors (discrete values) + # Stackpole CSNL series: Current sense resistors discrete_resistor_series( "Stackpole Electronics Inc", "CSNL2512", + "170V", + "2W", + "1%", [ ("1mOhm", "CSNL2512FT1L00"), ("2mOhm", "CSNL2512FT2L00"), @@ -56,14 +59,14 @@ SERIES_BY_PKG = { ("5mOhm", "CSNL2512FT5L00"), ("10mOhm", "CSNL2512FT10L0"), ], - "170V", - "2W", - "1%", ), - # YAGEO PA_E series: AEC-Q200 current sense resistors (discrete values) + # YAGEO PA_E series: AEC-Q200 current sense resistors discrete_resistor_series( "YAGEO", "PA_E", + "170V", + "2W", + "1%", [ ("1mOhm", "PA2512FKF7W0R001E"), ("2mOhm", "PA2512FKF7W0R002E"), @@ -72,9 +75,6 @@ SERIES_BY_PKG = { ("5mOhm", "PA2512FKF7W0R005E"), ("10mOhm", "PA2512FKF7W0R01E"), ], - "170V", - "2W", - "1%", ), ], } @@ -139,12 +139,12 @@ def assign_house_resistor(c, series_by_pkg): resistance = Resistance(prop(c, ["resistance", "Resistance"])) pkg = prop(c, ["package", "Package"]) v_req = prop(c, ["Voltage", "voltage"]) - voltage_constraint = Voltage(v_req).value if v_req else None + voltage_constraint = Voltage(v_req) if v_req else None p_req = prop(c, ["Power", "power"]) - power_constraint = Power(p_req).value if p_req else None + power_constraint = Power(p_req) if p_req else None # Skip 0 ohm resistors - they can't be matched to E-series - if resistance.value <= 0: + if resistance.value <= 0.0: return series_list = series_by_pkg.get(pkg, []) @@ -153,8 +153,8 @@ def assign_house_resistor(c, series_by_pkg): for spec in series_list: spec_type = spec.get("type", "e_series") - spec_max_v = Voltage(spec["max_v"]).value - spec_power = Power(spec["power"]).value + spec_max_v = spec["max_v"] + spec_power = spec["power"] spec_tol = float(spec["tolerance"].replace("%", "")) / 100.0 if voltage_constraint and voltage_constraint > spec_max_v: @@ -175,11 +175,9 @@ def assign_house_resistor(c, series_by_pkg): manufacturer = spec["manufacturer"] else: # E-series: standard matching logic - spec_min = Resistance(spec["min"]).value - spec_max = Resistance(spec["max"]).value - + spec_range = spec["resistance_range"] matched = find_e_series_match(resistance, spec_tol, spec["e_series"]) - if not matched or matched.value < spec_min or matched.value > spec_max: + if not matched or matched.value < spec_range.min or matched.value > spec_range.max: continue encoder = spec.get("encode", iec60062_4digit) @@ -187,7 +185,7 @@ def assign_house_resistor(c, series_by_pkg): mpn = spec["series"] + code + spec.get("suffix", "X") matches.append(mpn) if manufacturer == None: - manufacturer = "Panasonic Electronic Components" + manufacturer = spec["manufacturer"] if matches: set_primary_and_alts(c, matches[0], manufacturer, matches[1:]) @@ -215,7 +213,7 @@ def assign_house_capacitor(c, house_caps_by_pkg): pkg = prop(c, ["package", "Package"]) req_diel = prop(c, ["dielectric", "Dielectric"]) v_req = prop(c, ["voltage", "Voltage"]) - req_voltage = Voltage(v_req).value if v_req else None + req_voltage = Voltage(v_req) if v_req else None if cap.tolerance <= 0: cap = cap.with_tolerance(0.2) @@ -224,8 +222,8 @@ def assign_house_capacitor(c, house_caps_by_pkg): matches = [] for p in parts: - p_cap = Capacitance(p["cap"]) - p_voltage = Voltage(p["voltage"]).value + p_cap = p["cap"] + p_voltage = p["voltage"] if req_diel: h_rank = _DIELECTRIC_RANK.get(p["dielectric"], 99) @@ -239,9 +237,9 @@ def assign_house_capacitor(c, house_caps_by_pkg): if not p_cap.within(cap): continue - err = abs(p_cap.value - cap.value) / cap.value + err = p_cap.diff(cap) / cap diel_rank = _DIELECTRIC_RANK.get(p["dielectric"], 99) - score = (err, p_cap.tolerance, -p_voltage, diel_rank) + score = (err, p_cap.tolerance, p_voltage * -1, diel_rank) matches.append((score, p))