"""Seed data for model -> supply mappings. Corrected against official HP and Xerox sources (verification pass 2026-06-25). Replaces the old hardcoded supply_parts table, which had roughly ten wrong part numbers, scrambled colors, and HP high-yield cartridges mislabeled as metered. Each family matches printer models by substring (matchkeys), the same way the classic ASP report did, then attaches its supplies. seedsupplies() finds or creates the vendor and a canonical model row, then inserts any missing supply rows. Re-running is safe: existing part numbers are skipped. Key facts encoded here: - HP "X" = high yield, NOT metered. HP metered cartridges are contractual "C-suffix" SKUs and exist only on Enterprise/Managed hardware, so the Pro color families (M454/M479, M251/M252/M277, M254/M255) have none. - Xerox uses distinct sold / metered / dmo part numbers per color. """ import logging from shopdb.api import db, Vendor, Model from ..models import ModelSupply logger = logging.getLogger(__name__) def _toner(color, tier, partnumber, marketingname, pageyield=None): return { 'supplytype': 'toner', 'color': color, 'capacitytier': tier, 'partnumber': partnumber, 'marketingname': marketingname, 'pageyield': pageyield, } def _part(supplytype, partnumber, marketingname, notes=None): return { 'supplytype': supplytype, 'color': 'none', 'capacitytier': 'standard', 'partnumber': partnumber, 'marketingname': marketingname, 'pageyield': None, 'notes': notes, } # Corrected supply catalog. Each entry: vendor, canonical model name, # substring matchkeys, and the list of supplies. SEED = [ # ----- HP color, Pro (no metered variant exists) ----- { 'vendor': 'HP', 'canonical': 'HP Color LaserJet Pro M454 / M479', 'matchkeys': ['M454', 'M479'], 'supplies': [ _toner('black', 'standard', 'W2020A', '414A Black', 2400), _toner('black', 'high', 'W2020X', '414X Black', 7500), _toner('cyan', 'standard', 'W2021A', '414A Cyan', 2100), _toner('cyan', 'high', 'W2021X', '414X Cyan', 6000), _toner('yellow', 'standard', 'W2022A', '414A Yellow', 2100), _toner('yellow', 'high', 'W2022X', '414X Yellow', 6000), _toner('magenta', 'standard', 'W2023A', '414A Magenta', 2100), _toner('magenta', 'high', 'W2023X', '414X Magenta', 6000), ], }, { 'vendor': 'HP', 'canonical': 'HP Color LaserJet Pro M251 / M252 / M277', 'matchkeys': ['M251', 'M252', 'M277', 'M274'], 'supplies': [ _toner('black', 'standard', 'CF400A', '201A Black', 1500), _toner('black', 'high', 'CF400X', '201X Black', 2800), _toner('cyan', 'standard', 'CF401A', '201A Cyan', 1400), _toner('cyan', 'high', 'CF401X', '201X Cyan', 2500), _toner('yellow', 'standard', 'CF402A', '201A Yellow', 1400), _toner('yellow', 'high', 'CF402X', '201X Yellow', 2500), _toner('magenta', 'standard', 'CF403A', '201A Magenta', 1400), _toner('magenta', 'high', 'CF403X', '201X Magenta', 2500), ], }, { 'vendor': 'HP', 'canonical': 'HP Color LaserJet Pro M254 / M255', 'matchkeys': ['M254', 'M255', 'M280', 'M281'], 'supplies': [ _toner('black', 'standard', 'CF500A', '202A Black', 1400), _toner('black', 'high', 'CF500X', '202X Black', 3200), _toner('cyan', 'standard', 'CF501A', '202A Cyan', 1300), _toner('cyan', 'high', 'CF501X', '202X Cyan', 2500), _toner('yellow', 'standard', 'CF502A', '202A Yellow', 1300), _toner('yellow', 'high', 'CF502X', '202X Yellow', 2500), _toner('magenta', 'standard', 'CF503A', '202A Magenta', 1300), _toner('magenta', 'high', 'CF503X', '202X Magenta', 2500), ], }, # ----- HP mono (X = high yield; XC/YC = contractual metered) ----- { 'vendor': 'HP', 'canonical': 'HP LaserJet M404 / M406 / M428 / M430', 'matchkeys': ['M404', 'M406', 'M428', 'M430'], 'supplies': [ _toner('black', 'standard', 'CF258A', '58A Black', 3000), _toner('black', 'high', 'CF258X', '58X Black', 10000), _toner('black', 'metered', 'CF258XC', '58X Black (Contract)', 10000), ], }, { 'vendor': 'HP', 'canonical': 'HP LaserJet M607 / M608 / M609 / M631 / M632 / M633', 'matchkeys': ['M607', 'M608', 'M609', 'M631', 'M632', 'M633'], 'supplies': [ _toner('black', 'standard', 'CF237A', '37A Black', 11000), _toner('black', 'high', 'CF237X', '37X Black', 25000), # 37Y extra-high does NOT fit the M607 _toner('black', 'extrahigh', 'CF237Y', '37Y Black (not M607)', 41000), _toner('black', 'metered', 'CF237YC', '37Y Black (Contract)', 41000), ], }, { 'vendor': 'HP', 'canonical': 'HP LaserJet M506 / M527 / M501', 'matchkeys': ['M506', 'M527', 'M501'], 'supplies': [ _toner('black', 'standard', 'CF287A', '87A Black', 9000), _toner('black', 'high', 'CF287X', '87X Black', 18000), _toner('black', 'metered', 'CF287XC', '87X Black (Contract)', 18000), ], }, { 'vendor': 'HP', 'canonical': 'HP LaserJet M602 / M603 / M4555', 'matchkeys': ['M602', 'M603', 'M4555'], 'supplies': [ _toner('black', 'standard', 'CE390A', '90A Black', 10000), # 90X does NOT fit the M601 _toner('black', 'high', 'CE390X', '90X Black (not M601)', 24000), _toner('black', 'metered', 'CE390XC', '90X Black (Contract)', 24000), ], }, { 'vendor': 'HP', 'canonical': 'HP LaserJet P3015 / M521 / M525', 'matchkeys': ['P3015', 'M521', 'M525'], 'supplies': [ _toner('black', 'standard', 'CE255A', '55A Black', 6000), _toner('black', 'high', 'CE255X', '55X Black', 12500), _toner('black', 'metered', 'CE255XC', '55X Black (Contract)', 12500), ], }, { 'vendor': 'HP', 'canonical': 'HP LaserJet 4250 / 4350', 'matchkeys': ['4250', '4350'], 'supplies': [ _toner('black', 'standard', 'Q5942A', '42A Black', 10000), _toner('black', 'high', 'Q5942X', '42X Black', 20000), ], }, { 'vendor': 'HP', 'canonical': 'HP LaserJet M610 / M611 / M612 / M634 / M635 / M636', 'matchkeys': ['M610', 'M611', 'M612', 'M634', 'M635', 'M636'], 'supplies': [ _toner('black', 'standard', 'W1470A', '147A Black', 10500), _toner('black', 'high', 'W1470X', '147X Black', 25200), ], }, { 'vendor': 'HP', 'canonical': 'HP LaserJet Pro 4001 / 4101 (148 series)', 'matchkeys': ['4001', '4101', '4002', '4102'], 'supplies': [ _toner('black', 'high', 'W1480X', '148X Black', 9500), _toner('black', 'metered', 'W1020XC', '148 Black (Contract)', 9500), ], }, # ----- Xerox VersaLink color ----- { 'vendor': 'Xerox', 'canonical': 'Xerox VersaLink C415', 'matchkeys': ['C415'], 'supplies': [ _toner('black', 'standard', '006R04677', 'C415 Black', 2400), _toner('cyan', 'standard', '006R04678', 'C415 Cyan', 2000), _toner('magenta', 'standard', '006R04679', 'C415 Magenta', 2000), _toner('yellow', 'standard', '006R04680', 'C415 Yellow', 2000), _toner('black', 'high', '006R04685', 'C415 Black (High)', 10500), _toner('cyan', 'high', '006R04686', 'C415 Cyan (High)', 7000), _toner('magenta', 'high', '006R04687', 'C415 Magenta (High)', 7000), _toner('yellow', 'high', '006R04688', 'C415 Yellow (High)', 7000), _toner('black', 'metered', '006R04693', 'C415 Black (Metered)', 15000), _toner('cyan', 'metered', '006R04694', 'C415 Cyan (Metered)', 10000), _toner('magenta', 'metered', '006R04695', 'C415 Magenta (Metered)', 10000), _toner('yellow', 'metered', '006R04696', 'C415 Yellow (Metered)', 10000), _part('drum', '013R00701', 'C415 Drum / Imaging Unit'), _part('waste', '008R13325', 'C415 Waste Cartridge'), ], }, { 'vendor': 'Xerox', 'canonical': 'Xerox VersaLink C405', 'matchkeys': ['C405', 'C400'], 'supplies': [ _toner('black', 'standard', '106R03500', 'C405 Black', 2500), _toner('yellow', 'standard', '106R03501', 'C405 Yellow', 2500), _toner('cyan', 'standard', '106R03502', 'C405 Cyan', 2500), _toner('magenta', 'standard', '106R03503', 'C405 Magenta', 2500), _toner('black', 'high', '106R03512', 'C405 Black (High)', 5000), _toner('black', 'extrahigh', '106R03524', 'C405 Black (Extra High)', 10500), _part('drum', '108R01121', 'C400 / C405 Drum'), _part('waste', '108R01124', 'C400 / C405 Waste Cartridge'), ], }, { 'vendor': 'Xerox', 'canonical': 'Xerox VersaLink C7100 / C7120 / C7125 / C7130', 'matchkeys': ['C7100', 'C7120', 'C7125', 'C7130'], 'supplies': [ _toner('black', 'standard', '006R01824', 'C7125 Black (Sold)', 31300), _toner('cyan', 'standard', '006R01825', 'C7125 Cyan (Sold)', 18500), _toner('magenta', 'standard', '006R01826', 'C7125 Magenta (Sold)', 18500), _toner('yellow', 'standard', '006R01827', 'C7125 Yellow (Sold)', 18500), _toner('black', 'metered', '006R01820', 'C7125 Black (Metered)', 22500), _toner('cyan', 'metered', '006R01821', 'C7125 Cyan (Metered)', 15500), _toner('magenta', 'metered', '006R01822', 'C7125 Magenta (Metered)', 15500), _toner('yellow', 'metered', '006R01823', 'C7125 Yellow (Metered)', 15500), _part('drum', '013R00688', 'C7125 Drum'), _part('waste', '115R00129', 'C7000 / C7100 Waste Cartridge'), ], }, { 'vendor': 'Xerox', 'canonical': 'Xerox VersaLink B7100 / B7125 / B7130 / B7135', 'matchkeys': ['B7100', 'B7125', 'B7130', 'B7135'], 'supplies': [ _toner('black', 'standard', '006R01818', 'B7125 Black (Sold/High)', 34300), _toner('black', 'metered', '006R01817', 'B7125 Black (Metered)', 34300), _toner('black', 'dmo', '006R01819', 'B7125 Black (DMO)', 34300), _part('drum', '013R00687', 'B7125 Drum'), _part('waste', '115R00129', 'B7000 / B7100 Waste Cartridge'), ], }, { 'vendor': 'Xerox', 'canonical': 'Xerox VersaLink B400 / B405', 'matchkeys': ['B400', 'B405'], 'supplies': [ _toner('black', 'standard', '106R03580', 'B405 Black', 5900), _toner('black', 'high', '106R03582', 'B405 Black (High)', 13900), _toner('black', 'extrahigh', '106R03584', 'B405 Black (Extra High)', 24600), _toner('black', 'metered', '106R03586', 'B405 Black (Metered)', 24600), _part('drum', '101R00554', 'B400 / B405 Drum'), # note: B405 has no waste cartridge ], }, # ----- Xerox AltaLink color ----- { 'vendor': 'Xerox', 'canonical': 'Xerox AltaLink C8130 / C8135 / C8145 / C8155 / C8170', 'matchkeys': ['C8130', 'C8135', 'C8145', 'C8155', 'C8170'], 'supplies': [ _toner('black', 'standard', '006R01746', 'C8135 Black (Sold)'), _toner('cyan', 'standard', '006R01747', 'C8135 Cyan (Sold)'), _toner('magenta', 'standard', '006R01748', 'C8135 Magenta (Sold)'), _toner('yellow', 'standard', '006R01749', 'C8135 Yellow (Sold)'), _toner('black', 'metered', '006R01742', 'C8135 Black (Metered)'), _toner('cyan', 'metered', '006R01743', 'C8135 Cyan (Metered)'), _toner('magenta', 'metered', '006R01744', 'C8135 Magenta (Metered)'), _toner('yellow', 'metered', '006R01745', 'C8135 Yellow (Metered)'), _part('drum', '013R00681', 'C8135 Drum'), _part('waste', '008R08101', 'C8135 Waste Cartridge'), ], }, { 'vendor': 'Xerox', 'canonical': 'Xerox AltaLink C8030 / C8035 / C8045 / C8055 / EC8036', 'matchkeys': ['C8030', 'C8035', 'C8045', 'C8055', 'EC8036', 'EC8056', 'C8036'], 'supplies': [ _toner('black', 'standard', '006R01697', 'C8030 Black (Sold)'), _toner('cyan', 'standard', '006R01698', 'C8030 Cyan (Sold)'), _toner('magenta', 'standard', '006R01699', 'C8030 Magenta (Sold)'), _toner('yellow', 'standard', '006R01700', 'C8030 Yellow (Sold)'), _toner('black', 'metered', '006R01701', 'C8030 Black (Metered)'), _toner('cyan', 'metered', '006R01702', 'C8030 Cyan (Metered)'), _toner('magenta', 'metered', '006R01703', 'C8030 Magenta (Metered)'), _toner('yellow', 'metered', '006R01704', 'C8030 Yellow (Metered)'), # legacy WorkCentre 78xx cross-reference set, still compatible _toner('black', 'high', '006R01509', 'WC7800 Black (legacy)'), _toner('yellow', 'high', '006R01510', 'WC7800 Yellow (legacy)'), _toner('magenta', 'high', '006R01511', 'WC7800 Magenta (legacy)'), _toner('cyan', 'high', '006R01512', 'WC7800 Cyan (legacy)'), _part('drum', '013R00662', 'C8030 / EC8036 Drum'), _part('waste', '008R13061', 'C8030 / EC8036 Waste Cartridge'), ], }, ] def _find_or_create_vendor(name): vendor = Vendor.query.filter_by(vendor=name).first() if not vendor: vendor = Vendor(vendor=name) db.session.add(vendor) db.session.flush() return vendor def _matching_models(matchkeys, vendorid): """Existing models whose modelnumber contains any matchkey (this vendor).""" found = [] for key in matchkeys: rows = Model.query.filter( Model.vendorid == vendorid, Model.modelnumber.ilike(f'%{key}%'), ).all() for row in rows: if row not in found: found.append(row) return found def seedsupplies(): """Seed corrected model->supply data. Idempotent. Returns a summary dict with counts. Attaches supplies to every existing model that matches a family's keys; if a family matches no existing model, creates a canonical model row so its toners are still available. """ models_touched = 0 supplies_added = 0 for family in SEED: vendor = _find_or_create_vendor(family['vendor']) targets = _matching_models(family['matchkeys'], vendor.vendorid) if not targets: # modeltypeid is a legacy Model column (nullable); printers are # asset-based now and carry their type via PrinterType, not here. model = Model( modelnumber=family['canonical'], vendorid=vendor.vendorid, ) db.session.add(model) db.session.flush() targets = [model] for model in targets: models_touched += 1 existing = { supply.partnumber for supply in ModelSupply.query.filter_by( modelnumberid=model.modelnumberid ).all() } for spec in family['supplies']: if spec['partnumber'] in existing: continue db.session.add(ModelSupply( modelnumberid=model.modelnumberid, supplytype=spec['supplytype'], color=spec['color'], capacitytier=spec['capacitytier'], partnumber=spec['partnumber'], marketingname=spec.get('marketingname'), pageyield=spec.get('pageyield'), notes=spec.get('notes'), )) supplies_added += 1 db.session.commit() logger.info( "Seeded printer supplies: %d models touched, %d supplies added", models_touched, supplies_added ) return {'modelstouched': models_touched, 'suppliesadded': supplies_added}