Zabbix supply backend rebuild + data-driven model supplies
Rewrite the printer Zabbix integration (Bearer auth, host-by-IP, tag-based supply lookup, ping) and replace the hardcoded toner table with a modelsupplies table + CRUD + seed. Add mock Zabbix server, live test harness, and the Playwright screenshot tooling. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
107
plugins/printers/services/supply_parts.py
Normal file
107
plugins/printers/services/supply_parts.py
Normal file
@@ -0,0 +1,107 @@
|
||||
"""Printer supply classification and part-number lookup.
|
||||
|
||||
Part numbers now live in the modelsupplies table (see seed_supplies.py),
|
||||
managed through the API/UI. This module keeps the runtime logic that is not
|
||||
per-model data: classifying a reported level into ok/low/critical (waste
|
||||
cartridges invert), deriving supply type and color from a Zabbix item name,
|
||||
and reading the matching part numbers out of the database.
|
||||
"""
|
||||
|
||||
from typing import Dict, List, Optional
|
||||
|
||||
|
||||
# alert thresholds (percent remaining)
|
||||
CRITICAL_THRESHOLD = 5
|
||||
LOW_THRESHOLD = 10
|
||||
|
||||
|
||||
def derivesupplytype(name: str) -> str:
|
||||
"""Map a Zabbix item name to a supply type."""
|
||||
lowername = (name or "").lower()
|
||||
if "waste" in lowername:
|
||||
return "waste"
|
||||
if "drum" in lowername or "imaging" in lowername:
|
||||
return "drum"
|
||||
if "maintenance" in lowername or "fuser" in lowername:
|
||||
return "maintenance"
|
||||
return "toner"
|
||||
|
||||
|
||||
def derivecolor(name: str, tagcolor: Optional[str] = None) -> str:
|
||||
"""Best-effort supply color from a Zabbix color tag, then the item name."""
|
||||
color = (tagcolor or "").lower()
|
||||
if "black" in color:
|
||||
return "black"
|
||||
if color in ("cyan", "magenta", "yellow"):
|
||||
return color
|
||||
if color in ("grey", "gray"):
|
||||
return "gray"
|
||||
|
||||
lowername = (name or "").lower()
|
||||
for candidate in ("cyan", "magenta", "yellow", "black"):
|
||||
if candidate in lowername:
|
||||
return candidate
|
||||
return "none"
|
||||
|
||||
|
||||
def classifysupply(level: float, name: str, vendor: Optional[str]) -> Dict:
|
||||
"""Classify one supply item into ok/low/critical.
|
||||
|
||||
Waste cartridge fill is inverted vs a toner level: a full waste cartridge
|
||||
is bad. Standard vendors report waste as percent FULL (high = bad). Xerox
|
||||
EC/AltaLink series report waste as percent capacity REMAINING (low = bad),
|
||||
same direction as toner. Normalise everything to percent remaining first.
|
||||
"""
|
||||
lowername = (name or "").lower()
|
||||
iswaste = "waste" in lowername
|
||||
isdrum = "drum" in lowername or "imaging" in lowername
|
||||
isxerox = bool(vendor) and "xerox" in vendor.lower()
|
||||
|
||||
if iswaste and not isxerox:
|
||||
remaining = 100 - level
|
||||
else:
|
||||
remaining = level
|
||||
|
||||
if remaining <= CRITICAL_THRESHOLD:
|
||||
status = "critical"
|
||||
elif remaining <= LOW_THRESHOLD:
|
||||
status = "low"
|
||||
else:
|
||||
status = "ok"
|
||||
|
||||
return {
|
||||
"status": status,
|
||||
"remaining": round(remaining, 1),
|
||||
"iswaste": iswaste,
|
||||
"isdrum": isdrum,
|
||||
}
|
||||
|
||||
|
||||
def lookupsupplies(modelnumberid: Optional[int], color: str,
|
||||
supplytype: str) -> List[Dict]:
|
||||
"""Part-number options for a model + color + supply type, from the DB.
|
||||
|
||||
Returns every matching capacity tier (standard / high / metered / ...) so
|
||||
the report can show all reorder options, like the classic report did.
|
||||
"""
|
||||
if not modelnumberid:
|
||||
return []
|
||||
|
||||
from ..models import ModelSupply
|
||||
|
||||
query = ModelSupply.query.filter_by(
|
||||
modelnumberid=modelnumberid,
|
||||
supplytype=supplytype,
|
||||
isactive=True,
|
||||
)
|
||||
# toners are color-specific; drum/waste/maintenance are not
|
||||
if supplytype == 'toner' and color and color != 'none':
|
||||
query = query.filter_by(color=color)
|
||||
|
||||
rows = query.order_by(ModelSupply.capacitytier).all()
|
||||
return [{
|
||||
'partnumber': row.partnumber,
|
||||
'marketingname': row.marketingname,
|
||||
'capacitytier': row.capacitytier,
|
||||
'pageyield': row.pageyield,
|
||||
} for row in rows]
|
||||
Reference in New Issue
Block a user