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>
108 lines
3.4 KiB
Python
108 lines
3.4 KiB
Python
"""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]
|