mirror of
https://github.com/ThearchyHelios/AgentLab.git
synced 2026-11-15 00:51:59 +00:00
数字推演:算出无穷大、非数时说清算不出来,不让接口在 JSON 编码时 500
改了输入之后溢出成无穷大(或算出非数),以前照 ok 交回,Starlette 编 JSON 时拒收这种浮点数,整个请求 500。 现在按计算出错处理(status: error,写明原因);老卡上本来就记着无穷大的值和输入,交回时换成 null(口径卡渲染成「—」)。
This commit is contained in:
@@ -473,6 +473,9 @@ def _compute(plan: _Plan, values: dict[str, Any], notes: dict[str, list[int]],
|
||||
if inp.row is not None and path in notes and path not in changes.inputs:
|
||||
return "excluded", None, f"{EXCLUDED}:{path} 所在的第 {inp.row + 1} 行已排除"
|
||||
value, reason = _evaluate(plan, values)
|
||||
if reason is None and isinstance(value, float) and not math.isfinite(value):
|
||||
# 溢出成无穷大、算出非数:口径卡渲染成「—」,这里也说清算不出来(JSON 里也放不下这种值)
|
||||
reason = "计算结果不是有限的数值(溢出或非数),无法推演"
|
||||
return ("ok", value, None) if reason is None else ("error", None, reason)
|
||||
|
||||
|
||||
@@ -545,7 +548,7 @@ def _result(plan: _Plan, status: str, new: Any, reason: str | None, values: dict
|
||||
return {
|
||||
"id": metric.get("id"), "name": metric.get("name") or metric.get("id"), "unit": metric.get("unit") or "",
|
||||
"decimals": metric.get("decimals"), "format": metric.get("format") or DEFAULT_FORMAT,
|
||||
"old": old, "new": new, "delta": delta,
|
||||
"old": _finite(old), "new": _finite(new), "delta": delta,
|
||||
"rendered_old": _render(metric, old), "rendered_new": _render(metric, new) if known else None,
|
||||
"rendered_delta": rendered_delta,
|
||||
"changed": (not _same(old, new)) if known else None,
|
||||
@@ -562,6 +565,11 @@ def _render(metric: dict[str, Any], value: Any, conv: str | None = None) -> str:
|
||||
return MISSING
|
||||
|
||||
|
||||
def _finite(value: Any) -> Any:
|
||||
"""无穷大、非数换成 None:老卡上可能记着(口径卡渲染成「—」),JSON 里放不下。"""
|
||||
return None if isinstance(value, float) and not math.isfinite(value) else value
|
||||
|
||||
|
||||
def _same(a: Any, b: Any) -> bool:
|
||||
if is_number(a) and is_number(b):
|
||||
return a == b or (isinstance(a, float) and isinstance(b, float) and math.isnan(a) and math.isnan(b))
|
||||
@@ -620,7 +628,7 @@ def _input_view(inp: _Input, values: dict[str, Any], notes: dict[str, list[int]]
|
||||
new = _outline(values.get(inp.path), inp.table) if inp.problem is None else None
|
||||
changed = inp.problem is None and not _same(old, new)
|
||||
else:
|
||||
old, new = item.get("value"), values.get(inp.path)
|
||||
old, new = _finite(item.get("value")), _finite(values.get(inp.path))
|
||||
changed = not _same(old, new)
|
||||
view.update(old=old, new=new, changed=changed)
|
||||
if inp.path in notes:
|
||||
|
||||
@@ -218,6 +218,21 @@ def test_division_by_zero_is_explained():
|
||||
assert wow["status"] == "error" and wow["new"] is None and wow["reason"] == "除以零"
|
||||
|
||||
|
||||
def test_an_overflow_is_an_error_not_a_crash():
|
||||
"""算出无穷大:说清算不出来;老卡上记着无穷大的也不往外送(JSON 放不下,接口会 500)。"""
|
||||
import json
|
||||
|
||||
result = whatif(CARD, Changes(inputs={"vars.kpi.refunds": 1e308, "vars.kpi.orders": 1e-10}), sources(),
|
||||
metric="ratio")
|
||||
[ratio] = result["metrics"]
|
||||
assert ratio["status"] == "error" and ratio["new"] is None and "有限" in ratio["reason"]
|
||||
card = {**CARD, "metrics": [metric("big", "vars.kpi.x * 2", "inf * 2", math.inf,
|
||||
[scalar("vars.kpi.x", math.inf)], rendered="—")]}
|
||||
big = whatif(card, Changes(), sources())["metrics"][0]
|
||||
assert big["old"] is None and big["inputs"][0]["old"] is None and big["rendered_old"] == "—"
|
||||
json.dumps([result, big], allow_nan=False)
|
||||
|
||||
|
||||
def test_a_missing_input_can_be_supplied():
|
||||
card = {**CARD, "metrics": [metric("cost", "vars.kpi.cost / vars.kpi.orders", "vars.kpi.cost / 1234", None,
|
||||
[scalar("vars.kpi.cost", None), scalar("vars.kpi.orders", 1234)], decimals=2,
|
||||
|
||||
Reference in New Issue
Block a user