Only zero is empty, and the last day is worth reading
Two separate floors were collapsing a live cartridge into a spent one, and removing either alone changes nothing. EMPTY_LEVEL was 5, so everything at or below 5% was assigned daysleft 0 outright. The reasoning was that a printer at 1% is out of toner as far as anyone standing at it is concerned. But the row exists to say how long is left, and 2% draining a point a day has two days in it. Flooring put that cartridge beside ones that genuinely are empty with no way back to the difference. It is 0 now: empty means empty. int(level / rate) then truncated the division, so anything under a full day arrived as 0 whatever the floor did - a cartridge with six hours in it was indistinguishable from one with nothing, and the band read it as empty. daysleft is fractional now, rounded to two places, which is about a quarter of an hour: finer than the estimate deserves, but it costs nothing and keeps the ordering of two nearly-spent cartridges meaningful. daysText reads in whatever unit carries meaning: "6 hours", "1 hour", "29 min", "4 days". Below an hour it goes to minutes with a floor of one, because rounding hours would land back on "empty" - the same bug one rung down. BEHAVIOUR CHANGE worth knowing: a cartridge at 1-5% with NO history used to get daysleft 0 from the floor and land on the order list. A rate needs two readings; with none there is nothing to divide, and the old answer was right by accident - it said "empty" about a level nobody had watched move. It now reports 'no history' and shows under "No estimate yet". This reaches only printers newly added to Zabbix; anything that got to 3% the ordinary way has the history to forecast from. Five existing tests pinned the old rule. They recorded a real decision, so they are rewritten to the new one rather than deleted. One of them was passing for the wrong reason: its series (2.0, 1.7, 1.4, 1.1) drops 0.9, under MIN_DROP_FOR_ESTIMATE, so it never had a rate at all and only passed because the floor short-circuited ahead of the rate check. It now uses a real 20-day drop at a tenth of a point a day and asserts the ten days its docstring always described. The forecast fixture's black cartridge moves from 1% to 0% so the empty band keeps its API-level coverage, and a magenta at 3% covers hours-left end to end.
This commit is contained in:
@@ -74,12 +74,16 @@ MIN_DROP_FOR_ESTIMATE = 2
|
||||
# precise-looking figure invites more trust than it has earned.
|
||||
RATE_SPREAD_FACTOR = 5
|
||||
|
||||
# At or below this, the cartridge is done and the arithmetic stops being the
|
||||
# useful answer. A supply sitting at 1% that drains a tenth of a point a day
|
||||
# computes to ten days; a printer at 1% is out of toner as far as anyone
|
||||
# standing at it is concerned, and it is what should be ordered first. Rate is
|
||||
# still reported - only the days-left figure is floored.
|
||||
EMPTY_LEVEL = 5
|
||||
# Empty means empty. This was 5, on the reasoning that a printer at 1% is out
|
||||
# of toner as far as anyone standing at it is concerned - but flooring the
|
||||
# days-left of everything at or below 5% threw away the only number the row
|
||||
# exists to give. A cartridge at 2% draining a point a day has two days left,
|
||||
# and two days is worth knowing; reporting it as already gone puts it beside
|
||||
# cartridges that genuinely are, and there is no way back to the difference.
|
||||
#
|
||||
# Anything above zero gets its real estimate, in hours if that is what it comes
|
||||
# to. Only a reading of zero is empty.
|
||||
EMPTY_LEVEL = 0
|
||||
|
||||
|
||||
def _asfloat(value):
|
||||
@@ -345,8 +349,8 @@ def analyse(points, rise=REPLACEMENT_RISE, currentlevel=None):
|
||||
|
||||
rate = burn_rate(run)
|
||||
|
||||
# Empty is empty. Ordering by a rate below this level ranks a dead
|
||||
# cartridge behind a healthy one that happens to be draining faster.
|
||||
# Empty is empty, and only zero is empty. Ordering by a rate at this level
|
||||
# ranks a dead cartridge behind a healthy one that happens to drain faster.
|
||||
if level <= EMPTY_LEVEL:
|
||||
result['daysleft'] = 0
|
||||
result['burnrateperday'] = round(rate, 2) if rate is not None else None
|
||||
@@ -363,7 +367,13 @@ def analyse(points, rise=REPLACEMENT_RISE, currentlevel=None):
|
||||
return result
|
||||
|
||||
result['burnrateperday'] = round(rate, 2)
|
||||
result['daysleft'] = max(0, int(level / rate))
|
||||
# Fractional, NOT int(). Truncating meant every cartridge with less than a
|
||||
# full day left reported 0, which the bands read as empty and the view
|
||||
# printed as "empty" - a cartridge with six hours in it was indistinguishable
|
||||
# from one with nothing. Two decimal places is a quarter of an hour, which is
|
||||
# finer than the estimate deserves but costs nothing and keeps the ordering
|
||||
# of two nearly-spent cartridges meaningful.
|
||||
result['daysleft'] = round(max(0.0, level / rate), 2)
|
||||
# Say so when the intervals disagree wildly. The number is still the best
|
||||
# estimate available; the flag stops it reading as a measurement.
|
||||
result['rateunstable'] = rate_is_unstable(run)
|
||||
|
||||
Reference in New Issue
Block a user