A 3201dw is monitored and alerts correctly - Zabbix reports its cartridge names and levels straight off the printer - but the Toner Report had no reorder chip for it, because part numbers come only from modelsupplies and no family matched. lookupsupplies returns [] for a model with no rows, so the printer showed levels with nothing to order. Adds the 218 family: 218A standard and 218X high yield in all four colours, matched on 3201 and 3301 so the MFP siblings are covered by the same entry. Part numbers and yields cross-checked against three independent sources rather than inferred from the series - a wrong toner number means someone orders a cartridge that does not fit, which is worse than the blank chip it replaces. No drum or waste unit: none is listed as a separately orderable part for this series, so the family is toners only.
377 lines
17 KiB
Python
377 lines
17 KiB
Python
"""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) -----
|
|
{
|
|
# 3201dw / 3301 series, the 218 cartridge family. Part numbers and
|
|
# yields cross-checked against three independent sources (2026-08-13);
|
|
# no drum or waste unit is listed as a separately orderable part for
|
|
# this series, so toners only.
|
|
'vendor': 'HP', 'canonical': 'HP Color LaserJet Pro 3201 / 3301',
|
|
'matchkeys': ['3201', '3301'],
|
|
'supplies': [
|
|
_toner('black', 'standard', 'W2180A', '218A Black', 1300),
|
|
_toner('black', 'high', 'W2180X', '218X Black', 3200),
|
|
_toner('cyan', 'standard', 'W2181A', '218A Cyan', 1200),
|
|
_toner('cyan', 'high', 'W2181X', '218X Cyan', 2500),
|
|
_toner('yellow', 'standard', 'W2182A', '218A Yellow', 1200),
|
|
_toner('yellow', 'high', 'W2182X', '218X Yellow', 2500),
|
|
_toner('magenta', 'standard', 'W2183A', '218A Magenta', 1200),
|
|
_toner('magenta', 'high', 'W2183X', '218X Magenta', 2500),
|
|
],
|
|
},
|
|
{
|
|
'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}
|