A percentage or trend sentence needs the right numerator, denominator, time window and calculation trace, not merely two nearby numbers.
Numeric claims: denominators, calculations and evidence chains
Define the calculation before selecting values
“Failures fell by 20%” is ambiguous between a relative decrease and a 20-point drop in a percentage rate. Write the intended operation and required fields first. For a failure rate, the numerator is failed requests and the denominator is eligible requests from the same service and window. Do not divide by total traffic from another region. Quantity spans keep each input attached to its unit and source.
Track each input and transformation
An answer should carry both source spans, their document revisions, normalized units, formula and final rounding policy. Decimal arithmetic makes a displayed value reproducible, but it cannot make wrong inputs correct. If one input is approximate or OCR uncertain, mark the result as uncertain. OCR review should resolve a doubtful digit before it enters a published comparison.
Handle conflicting and missing evidence
Two active notes may disagree about the number of requests in the same window. Stage a conflict with both sources rather than choosing the latest-looking number by default. A missing denominator means the rate is unanswered, not zero. If a metric is sampled, a direct ratio of reported counts may not estimate the full population. Multi-passage conflict handling prevents a fluent answer from hiding incompatible claims.
Evaluate the statement a reader sees
Check input selection, operation, unit conversion, rounding, wording and evidence links separately. Include “increase from” versus “increase by,” percentage points, zero denominators and windows that touch midnight. Ask reviewers whether the final sentence says exactly what the calculation supports. The applied project requires the output to show its calculation, not just a polished trend statement.
Implementation
from decimal import Decimal, ROUND_HALF_UP
def failure_rate(failed_requests, eligible_requests, evidence_ids):
failures = Decimal(failed_requests)
total = Decimal(eligible_requests)
if total <= 0 or failures < 0 or failures > total or len(evidence_ids) != 2:
return {"state": "review", "reason": "invalid-input-or-evidence"}
percent = (failures * Decimal("100") / total).quantize(
Decimal("0.01"), rounding=ROUND_HALF_UP)
return {"state": "calculated", "percent": percent,
"evidence_ids": tuple(evidence_ids)}
result = failure_rate("47", "235", ["failures-47", "traffic-82"])
assert result["percent"] == Decimal("20.00")
assert failure_rate("47", "0", ["failures-47", "traffic-82"])["state"] == "review"
Performance and operating cost
Decimal parsing and arithmetic scale with input digit length d rather than being truly constant-time for unbounded numbers; evidence validation is O(1) for the two expected IDs. Document retrieval and human verification dominate end-to-end cost. Retain the arithmetic trace so a changed source can invalidate a derived claim instead of leaving an old percentage in circulation.
Common Mistakes
- Calling a percentage-point change a relative percent change.
- Using numerator and denominator from different windows.
- Replacing a missing denominator with zero.
- Publishing a calculated rate without input evidence IDs.
