Spaces:
Running
fix(formula): a formula constant by algebra is caught before generation, and the gate drops it without a recompute (R4)
Browse filesFrontline replay (run 5ec4b833c11b, test-mcp 1c5f261): the LLM authored
POLICY_UNDERWRITING.RATE_ON_LINE = WRITTEN_PREMIUM / (WRITTEN_PREMIUM / 0.55),
which is 0.55 on every row by algebra. The Wave 1 identity guard recognises
shapes (X, X*1, 1*X, X/1, X+0) and could not see this one; the column loaded
flat, and the post-load gate recomputed it from the same formula, re-profiled,
and posted a send-blocking repair_failed on top of measure_constant β two
findings and a capped grade for one dropped tile.
Blueprint time β formula.constant_value(), beside alias_target(): decide by
EVALUATION, not symbolic algebra. Probe the formula on twelve seeded random
input vectors (0.2x..5x of each identifier's scale, signed identifiers on both
sides of zero) and call it constant only when every output agrees within
1e-9; a literal has no inputs and is its own constant; anything that does not
parse or evaluate is None (validate() reports those). The blueprint passes its
siblings' scales and signs (_formula_constant), so a clamp or a rounding step
is judged where the data will be, not at 1.0. formula_problems() reports a
constant beside the identity report, so the LLM repair round sees it; the
repair loop's pending filter picks it up next to the alias check, and
_repair_one_formula demotes it with a message that names the formula and says
it is CONSTANT. A nonzero constant is the one thing the author told us β the
magnitude β so it is sampled in a Β±25% band around that value (a donor's
scale would have put a 0.55 rate at 15..85); a constant zero (X - X, X * 0)
takes the donor path or is dropped, like any other broken formula.
Gate β a `constant` defect takes the mirrors_other_column route: never a
recompute, so repair_failed cannot be raised for it; a derived constant is
dropped by _should_omit with one measure_constant finding whose detail names
the formula, a base constant is kept on the board and named.
Tests: constant_value cases for X/(X/c), X-X, X*0, X/X, a literal, signed
probing, scale-aware rounding, and the shapes that must stay variable;
blueprint demotion/drop paths for each; gate: a constant derived column
yields exactly one measure_constant finding, no UPDATE, no repair_failed,
and a base constant is kept.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
|
@@ -30,6 +30,7 @@ from __future__ import annotations
|
|
| 30 |
import ast
|
| 31 |
import math
|
| 32 |
import operator
|
|
|
|
| 33 |
from typing import Any, Callable, Iterable, Mapping, MutableSequence
|
| 34 |
|
| 35 |
|
|
@@ -582,6 +583,72 @@ def alias_target(formula: str) -> str:
|
|
| 582 |
return node.id if isinstance(node, ast.Name) else ""
|
| 583 |
|
| 584 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 585 |
def _literal(node: ast.AST) -> float | None:
|
| 586 |
"""The value of a bare numeric literal node, else None."""
|
| 587 |
if (isinstance(node, ast.Constant) and isinstance(node.value, (int, float))
|
|
|
|
| 30 |
import ast
|
| 31 |
import math
|
| 32 |
import operator
|
| 33 |
+
import random
|
| 34 |
from typing import Any, Callable, Iterable, Mapping, MutableSequence
|
| 35 |
|
| 36 |
|
|
|
|
| 583 |
return node.id if isinstance(node, ast.Name) else ""
|
| 584 |
|
| 585 |
|
| 586 |
+
def constant_value(
|
| 587 |
+
formula: str,
|
| 588 |
+
*,
|
| 589 |
+
scales: Mapping[str, float] | None = None,
|
| 590 |
+
signed: Iterable[str] = (),
|
| 591 |
+
samples: int = 12,
|
| 592 |
+
) -> float | None:
|
| 593 |
+
"""The one value this formula produces for EVERY input, or None if it varies.
|
| 594 |
+
|
| 595 |
+
Decided by EVALUATION, not algebra. alias_target() recognises the
|
| 596 |
+
identities an LLM writes by their shape (`X * 1`, `X + 0`); it cannot
|
| 597 |
+
recognise Frontline's RATE_ON_LINE = WRITTEN_PREMIUM / (WRITTEN_PREMIUM /
|
| 598 |
+
0.55) (2026-10-20, run 5ec4b833c11b), which is 0.55 on every row by
|
| 599 |
+
algebra, and there is no end to the shapes that are. So instead: evaluate
|
| 600 |
+
the formula on several random input vectors and ask whether the output
|
| 601 |
+
moved. A formula whose output is the same for every vector cannot produce
|
| 602 |
+
a measure β it is a flat tile β and belongs with the identity guard:
|
| 603 |
+
caught BEFORE generation, where the repair loop demotes or drops it, not
|
| 604 |
+
after the load, where the gate recomputed it from the same formula and
|
| 605 |
+
posted repair_failed on top of measure_constant.
|
| 606 |
+
|
| 607 |
+
A literal (`0.55`) has no inputs and is its own constant. Anything that
|
| 608 |
+
does not parse or cannot be evaluated returns None: validate() reports
|
| 609 |
+
those, and a constant verdict must never be reached by accident.
|
| 610 |
+
|
| 611 |
+
scales: {identifier: typical magnitude}, so a clamp or a rounding step is
|
| 612 |
+
probed at the measure's own scale β `MIN(MAX(A / B, 0), 1)` sits on
|
| 613 |
+
its cap for a 0.75 rate only now and then, and `ROUND(X / 1e6, 1)` is
|
| 614 |
+
a real computation for a seven-figure X and all zeros for a two-figure
|
| 615 |
+
one. An identifier with no scale is probed around 1.0.
|
| 616 |
+
signed: identifiers that may be negative. They are probed on both sides of
|
| 617 |
+
zero, so `MIN(VARIANCE, 0)` β the downside of a signed variance β is
|
| 618 |
+
not called constant for never having seen a negative input.
|
| 619 |
+
samples: how many vectors. A true constant is constant on all of them, so
|
| 620 |
+
every extra vector only makes a FALSE "constant" verdict less likely;
|
| 621 |
+
a capped ratio can sit on its cap for a few draws in a row, so twelve.
|
| 622 |
+
"""
|
| 623 |
+
try:
|
| 624 |
+
node = parse(formula)
|
| 625 |
+
except FormulaEvaluationError:
|
| 626 |
+
return None
|
| 627 |
+
names = sorted(identifiers(formula))
|
| 628 |
+
magnitudes = {str(name): abs(float(value)) or 1.0 for name, value in (scales or {}).items()}
|
| 629 |
+
negatives = {str(name) for name in signed}
|
| 630 |
+
# Seeded by the formula text, so a re-parse of the same blueprint reaches
|
| 631 |
+
# the same verdict. Each input spreads over 0.2x..5x of its scale.
|
| 632 |
+
rng = random.Random(f"constant_value:{formula}")
|
| 633 |
+
seen: float | None = None
|
| 634 |
+
for i in range(samples if names else 1):
|
| 635 |
+
values: dict[str, float] = {}
|
| 636 |
+
for name in names:
|
| 637 |
+
draw = magnitudes.get(name, 1.0) * math.exp(rng.uniform(math.log(0.2), math.log(5.0)))
|
| 638 |
+
values[name] = -draw if (name in negatives and i % 2) else draw
|
| 639 |
+
try:
|
| 640 |
+
value = float(_eval_node(node, values, measure="", formula=formula, warnings=[]))
|
| 641 |
+
except (FormulaEvaluationError, ArithmeticError, ValueError):
|
| 642 |
+
return None
|
| 643 |
+
if math.isnan(value) or math.isinf(value):
|
| 644 |
+
return None
|
| 645 |
+
if seen is None:
|
| 646 |
+
seen = value
|
| 647 |
+
elif not math.isclose(value, seen, rel_tol=1e-9, abs_tol=1e-9):
|
| 648 |
+
return None
|
| 649 |
+
return seen
|
| 650 |
+
|
| 651 |
+
|
| 652 |
def _literal(node: ast.AST) -> float | None:
|
| 653 |
"""The value of a bare numeric literal node, else None."""
|
| 654 |
if (isinstance(node, ast.Constant) and isinstance(node.value, (int, float))
|
|
@@ -555,13 +555,30 @@ def _column_defects(
|
|
| 555 |
})
|
| 556 |
continue
|
| 557 |
if s["min"] is not None and s["max"] is not None and s["min"] == s["max"] and row_count > 1:
|
| 558 |
-
|
| 559 |
-
|
| 560 |
-
|
| 561 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 562 |
f"give {col} real variation (params.sigma, or a formula whose inputs vary) β "
|
| 563 |
"a constant measure is a flat tile with no story"
|
| 564 |
-
)
|
|
|
|
|
|
|
|
|
|
| 565 |
})
|
| 566 |
if is_flag_column(col) and s.get("non_binary"):
|
| 567 |
defects.append({
|
|
@@ -753,14 +770,19 @@ def _repair_statements(
|
|
| 753 |
))
|
| 754 |
return statements
|
| 755 |
|
| 756 |
-
if defect["defect"]
|
| 757 |
-
# Never "repair" a mirror by recomputing it: the column
|
| 758 |
-
# over these inputs, so the recompute reproduces the
|
| 759 |
-
#
|
| 760 |
-
#
|
| 761 |
-
#
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 762 |
return []
|
| 763 |
-
# all_zero
|
| 764 |
return recompute()
|
| 765 |
|
| 766 |
|
|
@@ -772,8 +794,9 @@ _REPAIR_ORDER = {"part_exceeds_whole": 0, "flag_not_binary": 1,
|
|
| 772 |
|
| 773 |
|
| 774 |
def _repair_rank(defect: dict) -> int:
|
| 775 |
-
# Default 3: a pure recompute (all_zero
|
| 776 |
-
#
|
|
|
|
| 777 |
return _REPAIR_ORDER.get(defect["defect"], 3)
|
| 778 |
|
| 779 |
|
|
|
|
| 555 |
})
|
| 556 |
continue
|
| 557 |
if s["min"] is not None and s["max"] is not None and s["min"] == s["max"] and row_count > 1:
|
| 558 |
+
constant_formula = formulas.get(f"{table_name}.{col}", "")
|
| 559 |
+
if constant_formula:
|
| 560 |
+
# A derived constant is its formula's doing β Frontline's
|
| 561 |
+
# RATE_ON_LINE = WRITTEN_PREMIUM / (WRITTEN_PREMIUM / 0.55) is
|
| 562 |
+
# 0.55 on every row by algebra (2026-10-20). Name the formula:
|
| 563 |
+
# the fix is in the blueprint, and _should_omit drops the column.
|
| 564 |
+
detail = (
|
| 565 |
+
f"every row is {s['min']} β the formula '{constant_formula}' produced the "
|
| 566 |
+
"same value for every row"
|
| 567 |
+
)
|
| 568 |
+
suggestion = (
|
| 569 |
+
f"{col} = '{constant_formula}' is constant, so it is a flat tile, not a "
|
| 570 |
+
f"measure: give {col} a formula whose output changes with its inputs in the "
|
| 571 |
+
f"blueprint, or drop {col}"
|
| 572 |
+
)
|
| 573 |
+
else:
|
| 574 |
+
detail = f"every row is {s['min']}"
|
| 575 |
+
suggestion = (
|
| 576 |
f"give {col} real variation (params.sigma, or a formula whose inputs vary) β "
|
| 577 |
"a constant measure is a flat tile with no story"
|
| 578 |
+
)
|
| 579 |
+
defects.append({
|
| 580 |
+
"table": table_name, "column": col, "defect": "constant",
|
| 581 |
+
"detail": detail, "suggestion": suggestion,
|
| 582 |
})
|
| 583 |
if is_flag_column(col) and s.get("non_binary"):
|
| 584 |
defects.append({
|
|
|
|
| 770 |
))
|
| 771 |
return statements
|
| 772 |
|
| 773 |
+
if defect["defect"] in ("mirrors_other_column", "constant"):
|
| 774 |
+
# Never "repair" a mirror or a constant by recomputing it: the column
|
| 775 |
+
# IS its formula over these inputs, so the recompute reproduces the
|
| 776 |
+
# copy, the re-profile still shows the defect, and the run posts
|
| 777 |
+
# repair_failed on top of it β two findings and a capped grade for one
|
| 778 |
+
# dropped tile (Humpty Dump, 2026-10-13: three builds in 70 minutes on
|
| 779 |
+
# one mirror; Frontline, 2026-10-20: repair_failed on top of
|
| 780 |
+
# measure_constant for RATE_ON_LINE = X / (X / 0.55)). Both are
|
| 781 |
+
# blueprint defects: a mirror and a derived constant are dropped by
|
| 782 |
+
# _should_omit, a base constant (no formula, nothing to recompute
|
| 783 |
+
# anyway) is kept on the board and named.
|
| 784 |
return []
|
| 785 |
+
# all_zero: only a formula can fix it.
|
| 786 |
return recompute()
|
| 787 |
|
| 788 |
|
|
|
|
| 794 |
|
| 795 |
|
| 796 |
def _repair_rank(defect: dict) -> int:
|
| 797 |
+
# Default 3: a pure recompute (all_zero) runs LAST, so it reads inputs that
|
| 798 |
+
# have already been bounded or binarised. A mirror or a constant has no
|
| 799 |
+
# repair at all (see _repair_statements) and only takes this rank.
|
| 800 |
return _REPAIR_ORDER.get(defect["defect"], 3)
|
| 801 |
|
| 802 |
|
|
@@ -31,6 +31,7 @@ from demoprep_app.dataset.formula import (
|
|
| 31 |
difference_pairs as formula_difference_pairs,
|
| 32 |
identifiers as formula_identifiers,
|
| 33 |
alias_target as formula_alias_target,
|
|
|
|
| 34 |
is_signed as formula_is_signed,
|
| 35 |
parse as parse_formula,
|
| 36 |
ratio_pairs as formula_ratio_pairs,
|
|
@@ -949,11 +950,24 @@ class DemoBlueprint:
|
|
| 949 |
issues: list[str] = []
|
| 950 |
for fact in self.facts:
|
| 951 |
allowed = {m.name for m in fact.measures}
|
|
|
|
| 952 |
for m in fact.measures:
|
| 953 |
if m.kind != "derived" or not m.formula:
|
| 954 |
continue
|
| 955 |
-
|
|
|
|
| 956 |
issues.append(f"{problem} (fact {fact.name})")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 957 |
return issues
|
| 958 |
|
| 959 |
def repair_formulas(self) -> list[str]:
|
|
@@ -981,11 +995,17 @@ class DemoBlueprint:
|
|
| 981 |
# board shipped Baseline and Optimized Revenue equal to the cent
|
| 982 |
# on all eight categories. Repair it like any other broken
|
| 983 |
# formula rather than shipping the duplicate silently.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 984 |
pending = [
|
| 985 |
m for m in fact.measures
|
| 986 |
if m.kind == "derived" and (
|
| 987 |
validate_formula(m.formula, set(by_name) - {m.name}, measure=m.name)
|
| 988 |
or formula_alias_target(m.formula) in (set(by_name) - {m.name})
|
|
|
|
| 989 |
)
|
| 990 |
]
|
| 991 |
if not pending:
|
|
@@ -1813,6 +1833,49 @@ def _alias_reason(measure, by_name: dict) -> str:
|
|
| 1813 |
return ""
|
| 1814 |
|
| 1815 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1816 |
def _repair_one_formula(fact, measure, by_name: dict) -> list[str]:
|
| 1817 |
"""Case-fix, demote, or drop ONE uncomputable derived measure."""
|
| 1818 |
siblings = {name for name in by_name if name != measure.name}
|
|
@@ -1820,7 +1883,11 @@ def _repair_one_formula(fact, measure, by_name: dict) -> list[str]:
|
|
| 1820 |
# 0. A copy is not broken, so the case-fix path below cannot help it and the
|
| 1821 |
# message must not claim it is "not computable". Go straight to demotion,
|
| 1822 |
# which gives the measure its own variation off the source it copied.
|
|
|
|
| 1823 |
alias_reason = _alias_reason(measure, by_name)
|
|
|
|
|
|
|
|
|
|
| 1824 |
|
| 1825 |
# 1. case-only mismatches are a rewrite, not a loss
|
| 1826 |
upper_map = {name.upper(): name for name in siblings}
|
|
@@ -1834,11 +1901,35 @@ def _repair_one_formula(fact, measure, by_name: dict) -> list[str]:
|
|
| 1834 |
import re as _re
|
| 1835 |
rewritten = _re.sub(rf"\b{_re.escape(token)}\b", target, rewritten)
|
| 1836 |
renames.append(f"{token}->{target}")
|
| 1837 |
-
if not alias_reason and renames and not validate_formula(
|
|
|
|
|
|
|
| 1838 |
old = measure.formula
|
| 1839 |
measure.formula = rewritten
|
| 1840 |
return [f"{measure.name}: formula '{old}' -> '{rewritten}' ({', '.join(renames)})"]
|
| 1841 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1842 |
# 2. demote to a base measure scaled off a sibling the formula referenced
|
| 1843 |
donor = _pick_donor(measure, by_name)
|
| 1844 |
if donor is not None:
|
|
@@ -1893,7 +1984,7 @@ def _repair_one_formula(fact, measure, by_name: dict) -> list[str]:
|
|
| 1893 |
measure.params = {"low": round(low, 6), "high": round(high, 6)}
|
| 1894 |
shape = f"rate in {measure.params['low']}..{measure.params['high']}"
|
| 1895 |
return [
|
| 1896 |
-
f"{measure.name}:
|
| 1897 |
f"-> base measure on {donor.name}'s scale ({shape}). "
|
| 1898 |
"The tile stays on the board; the value is sampled, not derived."
|
| 1899 |
]
|
|
@@ -1913,7 +2004,7 @@ def _repair_one_formula(fact, measure, by_name: dict) -> list[str]:
|
|
| 1913 |
measure.params = {"mean": spread, "sigma": 0.5}
|
| 1914 |
shape = f"mean {spread}"
|
| 1915 |
return [
|
| 1916 |
-
f"{measure.name}:
|
| 1917 |
f"-> base measure scaled off {donor.name} ({shape}). "
|
| 1918 |
"The tile stays on the board; the value is sampled, not derived."
|
| 1919 |
]
|
|
@@ -1929,13 +2020,13 @@ def _repair_one_formula(fact, measure, by_name: dict) -> list[str]:
|
|
| 1929 |
measure.params = {"mean": 100.0, "sigma": 0.5}
|
| 1930 |
measure.share_of = ""
|
| 1931 |
return [
|
| 1932 |
-
f"{measure.name}:
|
| 1933 |
-
|
| 1934 |
]
|
| 1935 |
fact.measures = [m for m in fact.measures if m.name != measure.name]
|
| 1936 |
return [
|
| 1937 |
-
f"{measure.name}: dropped β
|
| 1938 |
-
"
|
| 1939 |
]
|
| 1940 |
|
| 1941 |
|
|
|
|
| 31 |
difference_pairs as formula_difference_pairs,
|
| 32 |
identifiers as formula_identifiers,
|
| 33 |
alias_target as formula_alias_target,
|
| 34 |
+
constant_value as formula_constant_value,
|
| 35 |
is_signed as formula_is_signed,
|
| 36 |
parse as parse_formula,
|
| 37 |
ratio_pairs as formula_ratio_pairs,
|
|
|
|
| 950 |
issues: list[str] = []
|
| 951 |
for fact in self.facts:
|
| 952 |
allowed = {m.name for m in fact.measures}
|
| 953 |
+
by_name = {m.name: m for m in fact.measures}
|
| 954 |
for m in fact.measures:
|
| 955 |
if m.kind != "derived" or not m.formula:
|
| 956 |
continue
|
| 957 |
+
problems = validate_formula(m.formula, allowed - {m.name}, measure=m.name)
|
| 958 |
+
for problem in problems:
|
| 959 |
issues.append(f"{problem} (fact {fact.name})")
|
| 960 |
+
if problems:
|
| 961 |
+
continue
|
| 962 |
+
# Valid, computable, and useless: the same value on every row.
|
| 963 |
+
# Reported here, beside validate()'s identity report, so the
|
| 964 |
+
# LLM repair round sees it before repair_formulas() demotes it.
|
| 965 |
+
reason, _ = _constant_reason(m, by_name)
|
| 966 |
+
if reason:
|
| 967 |
+
issues.append(
|
| 968 |
+
f"Measure {m.name}: {reason}. Compute something that changes with its "
|
| 969 |
+
f"inputs, or drop this measure. (fact {fact.name})"
|
| 970 |
+
)
|
| 971 |
return issues
|
| 972 |
|
| 973 |
def repair_formulas(self) -> list[str]:
|
|
|
|
| 995 |
# board shipped Baseline and Optimized Revenue equal to the cent
|
| 996 |
# on all eight categories. Repair it like any other broken
|
| 997 |
# formula rather than shipping the duplicate silently.
|
| 998 |
+
# A CONSTANT is the same defect in a shape no identity check
|
| 999 |
+
# can enumerate: Frontline's RATE_ON_LINE = WRITTEN_PREMIUM /
|
| 1000 |
+
# (WRITTEN_PREMIUM / 0.55) is 0.55 on every row by algebra
|
| 1001 |
+
# (R4, 2026-10-20). Found by evaluating the formula, not by
|
| 1002 |
+
# recognising it β see formula.constant_value().
|
| 1003 |
pending = [
|
| 1004 |
m for m in fact.measures
|
| 1005 |
if m.kind == "derived" and (
|
| 1006 |
validate_formula(m.formula, set(by_name) - {m.name}, measure=m.name)
|
| 1007 |
or formula_alias_target(m.formula) in (set(by_name) - {m.name})
|
| 1008 |
+
or _formula_constant(m, by_name) is not None
|
| 1009 |
)
|
| 1010 |
]
|
| 1011 |
if not pending:
|
|
|
|
| 1833 |
return ""
|
| 1834 |
|
| 1835 |
|
| 1836 |
+
def _measure_scale(measure) -> float:
|
| 1837 |
+
"""A positive magnitude typical of this measure, for probing a formula."""
|
| 1838 |
+
params = measure.params or {}
|
| 1839 |
+
|
| 1840 |
+
def number(key: str) -> float:
|
| 1841 |
+
value = params.get(key)
|
| 1842 |
+
if isinstance(value, (int, float)) and not isinstance(value, bool):
|
| 1843 |
+
return abs(float(value))
|
| 1844 |
+
return 0.0
|
| 1845 |
+
|
| 1846 |
+
low, high = number("low"), number("high")
|
| 1847 |
+
for candidate in (number("mean"), number("sigma"), (low + high) / 2.0, high):
|
| 1848 |
+
if candidate > 0:
|
| 1849 |
+
return candidate
|
| 1850 |
+
return 1.0
|
| 1851 |
+
|
| 1852 |
+
|
| 1853 |
+
def _formula_constant(measure, by_name: dict) -> float | None:
|
| 1854 |
+
"""The one value `measure.formula` takes on every row, or None if it varies.
|
| 1855 |
+
|
| 1856 |
+
Probes the formula at its siblings' own scales and signs (see
|
| 1857 |
+
formula.constant_value), so a clamp or a rounding step is judged where the
|
| 1858 |
+
data will actually be, not at 1.0.
|
| 1859 |
+
"""
|
| 1860 |
+
siblings = {name: m for name, m in by_name.items() if name != measure.name}
|
| 1861 |
+
return formula_constant_value(
|
| 1862 |
+
measure.formula,
|
| 1863 |
+
scales={name: _measure_scale(m) for name, m in siblings.items()},
|
| 1864 |
+
signed={name for name, m in siblings.items() if getattr(m, "signed", False)},
|
| 1865 |
+
)
|
| 1866 |
+
|
| 1867 |
+
|
| 1868 |
+
def _constant_reason(measure, by_name: dict) -> tuple[str, float | None]:
|
| 1869 |
+
"""(why, value) when this measure's formula is constant, else ("", None)."""
|
| 1870 |
+
value = _formula_constant(measure, by_name)
|
| 1871 |
+
if value is None:
|
| 1872 |
+
return "", None
|
| 1873 |
+
return (
|
| 1874 |
+
f"formula '{measure.formula}' is CONSTANT β it evaluates to {value:g} whatever its "
|
| 1875 |
+
"inputs are, so it would ship a flat column with no story"
|
| 1876 |
+
), value
|
| 1877 |
+
|
| 1878 |
+
|
| 1879 |
def _repair_one_formula(fact, measure, by_name: dict) -> list[str]:
|
| 1880 |
"""Case-fix, demote, or drop ONE uncomputable derived measure."""
|
| 1881 |
siblings = {name for name in by_name if name != measure.name}
|
|
|
|
| 1883 |
# 0. A copy is not broken, so the case-fix path below cannot help it and the
|
| 1884 |
# message must not claim it is "not computable". Go straight to demotion,
|
| 1885 |
# which gives the measure its own variation off the source it copied.
|
| 1886 |
+
# A CONSTANT is the same: valid, computable, and a flat column.
|
| 1887 |
alias_reason = _alias_reason(measure, by_name)
|
| 1888 |
+
constant_reason, constant = ("", None) if alias_reason else _constant_reason(measure, by_name)
|
| 1889 |
+
why = (alias_reason or constant_reason
|
| 1890 |
+
or f"formula '{measure.formula}' is not computable in fact {fact.name}")
|
| 1891 |
|
| 1892 |
# 1. case-only mismatches are a rewrite, not a loss
|
| 1893 |
upper_map = {name.upper(): name for name in siblings}
|
|
|
|
| 1901 |
import re as _re
|
| 1902 |
rewritten = _re.sub(rf"\b{_re.escape(token)}\b", target, rewritten)
|
| 1903 |
renames.append(f"{token}->{target}")
|
| 1904 |
+
if not (alias_reason or constant_reason) and renames and not validate_formula(
|
| 1905 |
+
rewritten, siblings, measure=measure.name
|
| 1906 |
+
):
|
| 1907 |
old = measure.formula
|
| 1908 |
measure.formula = rewritten
|
| 1909 |
return [f"{measure.name}: formula '{old}' -> '{rewritten}' ({', '.join(renames)})"]
|
| 1910 |
|
| 1911 |
+
# 1b. A formula constant at a NONZERO value has told us one thing only: the
|
| 1912 |
+
# magnitude its author had in mind. Frontline's RATE_ON_LINE =
|
| 1913 |
+
# WRITTEN_PREMIUM / (WRITTEN_PREMIUM / 0.55) means "about 0.55", and a
|
| 1914 |
+
# donor's scale (the premium, six figures) would put that rate at
|
| 1915 |
+
# 15..85. Sample around the value the formula produced instead; the
|
| 1916 |
+
# bound pass in harden() still applies the measure's own range. A
|
| 1917 |
+
# constant ZERO (`X - X`, `X * 0`) says nothing about magnitude and
|
| 1918 |
+
# takes the donor path below like any other broken formula.
|
| 1919 |
+
if constant:
|
| 1920 |
+
low, high = sorted((round(constant * 0.75, 6), round(constant * 1.25, 6)))
|
| 1921 |
+
measure.kind = "base"
|
| 1922 |
+
measure.formula = ""
|
| 1923 |
+
measure.share_of = ""
|
| 1924 |
+
measure.distribution = "uniform"
|
| 1925 |
+
measure.signed = constant < 0
|
| 1926 |
+
measure.params = {"low": low, "high": high}
|
| 1927 |
+
return [
|
| 1928 |
+
f"{measure.name}: {constant_reason} -> base measure sampled in {low:g}..{high:g}, "
|
| 1929 |
+
"around the one value the formula produced. The tile stays on the board; the "
|
| 1930 |
+
"value is sampled, not derived."
|
| 1931 |
+
]
|
| 1932 |
+
|
| 1933 |
# 2. demote to a base measure scaled off a sibling the formula referenced
|
| 1934 |
donor = _pick_donor(measure, by_name)
|
| 1935 |
if donor is not None:
|
|
|
|
| 1984 |
measure.params = {"low": round(low, 6), "high": round(high, 6)}
|
| 1985 |
shape = f"rate in {measure.params['low']}..{measure.params['high']}"
|
| 1986 |
return [
|
| 1987 |
+
f"{measure.name}: {why} "
|
| 1988 |
f"-> base measure on {donor.name}'s scale ({shape}). "
|
| 1989 |
"The tile stays on the board; the value is sampled, not derived."
|
| 1990 |
]
|
|
|
|
| 2004 |
measure.params = {"mean": spread, "sigma": 0.5}
|
| 2005 |
shape = f"mean {spread}"
|
| 2006 |
return [
|
| 2007 |
+
f"{measure.name}: {why} "
|
| 2008 |
f"-> base measure scaled off {donor.name} ({shape}). "
|
| 2009 |
"The tile stays on the board; the value is sampled, not derived."
|
| 2010 |
]
|
|
|
|
| 2020 |
measure.params = {"mean": 100.0, "sigma": 0.5}
|
| 2021 |
measure.share_of = ""
|
| 2022 |
return [
|
| 2023 |
+
f"{measure.name}: {why}, and it is the only measure in fact {fact.name} -> sampled "
|
| 2024 |
+
"base measure, so the fact still has something to chart."
|
| 2025 |
]
|
| 2026 |
fact.measures = [m for m in fact.measures if m.name != measure.name]
|
| 2027 |
return [
|
| 2028 |
+
f"{measure.name}: dropped β {why}, and no sibling measure to scale it off. "
|
| 2029 |
+
"The demo ships without this measure."
|
| 2030 |
]
|
| 2031 |
|
| 2032 |
|
|
@@ -384,3 +384,93 @@ def test_each_formula_repair_is_reported_exactly_once():
|
|
| 384 |
bp.autorepair()
|
| 385 |
repairs = [f for f in bp.metadata.get("autorepairs", []) if "MARGIN_ERROR" in f]
|
| 386 |
assert len(repairs) == 1, repairs
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 384 |
bp.autorepair()
|
| 385 |
repairs = [f for f in bp.metadata.get("autorepairs", []) if "MARGIN_ERROR" in f]
|
| 386 |
assert len(repairs) == 1, repairs
|
| 387 |
+
|
| 388 |
+
|
| 389 |
+
# --- constant formulas are demoted BEFORE generation (findings triage R4) ------
|
| 390 |
+
# Frontline (2026-10-20, run 5ec4b833c11b): RATE_ON_LINE = WRITTEN_PREMIUM /
|
| 391 |
+
# (WRITTEN_PREMIUM / 0.55) is 0.55 on every row by algebra. The identity guard
|
| 392 |
+
# did not recognise it, the column loaded flat, and the post-load gate recomputed
|
| 393 |
+
# it from the same formula and posted repair_failed on top of measure_constant.
|
| 394 |
+
# The repair loop now catches it where the identity guard lives.
|
| 395 |
+
|
| 396 |
+
|
| 397 |
+
def _frontline(formula, *, fmt="pct"):
|
| 398 |
+
return _blueprint([
|
| 399 |
+
{"name": "WRITTEN_PREMIUM", "fmt": "currency", "params": {"mean": 250000}},
|
| 400 |
+
{"name": "POLICY_LIMIT", "fmt": "currency", "params": {"mean": 1000000}},
|
| 401 |
+
{"name": "RATE_ON_LINE", "kind": "derived", "formula": formula, "fmt": fmt},
|
| 402 |
+
], insights=_insights("WRITTEN_PREMIUM"))
|
| 403 |
+
|
| 404 |
+
|
| 405 |
+
def test_a_formula_constant_by_algebra_is_demoted_around_its_value_before_generation():
|
| 406 |
+
bp = _frontline("WRITTEN_PREMIUM / (WRITTEN_PREMIUM / 0.55)")
|
| 407 |
+
# The LLM repair round sees it, beside the identity report.
|
| 408 |
+
problems = bp.formula_problems()
|
| 409 |
+
assert len(problems) == 1 and "RATE_ON_LINE" in problems[0]
|
| 410 |
+
assert "CONSTANT" in problems[0] and "0.55" in problems[0]
|
| 411 |
+
|
| 412 |
+
fixes = bp.repair_formulas()
|
| 413 |
+
m = _measure(bp, "RATE_ON_LINE")
|
| 414 |
+
assert m.kind == "base" and m.formula == ""
|
| 415 |
+
# Sampled around the value the formula produced, not off the premium's scale.
|
| 416 |
+
assert m.distribution == "uniform"
|
| 417 |
+
assert m.params["low"] < 0.55 < m.params["high"]
|
| 418 |
+
assert 0.3 <= m.params["low"] and m.params["high"] <= 0.8
|
| 419 |
+
fix = next(f for f in fixes if f.startswith("RATE_ON_LINE"))
|
| 420 |
+
assert "WRITTEN_PREMIUM / (WRITTEN_PREMIUM / 0.55)" in fix
|
| 421 |
+
assert "CONSTANT" in fix and "0.55" in fix and "not computable" not in fix
|
| 422 |
+
assert bp.formula_problems() == []
|
| 423 |
+
|
| 424 |
+
rows = BlueprintEngine().generate(bp, 500).table_map()["LOAD_TRANSACTION"].rows
|
| 425 |
+
values = {round(r["RATE_ON_LINE"], 6) for r in rows}
|
| 426 |
+
assert len(values) > 1, "the demoted measure must vary row to row"
|
| 427 |
+
assert all(0.0 < v < 1.0 for v in values)
|
| 428 |
+
|
| 429 |
+
|
| 430 |
+
def test_a_literal_formula_is_constant_too():
|
| 431 |
+
bp = _frontline("0.55")
|
| 432 |
+
# from_dict() already hardened the pct formula into its MIN/MAX wrap; the
|
| 433 |
+
# guard judges the formula as it stands and names that.
|
| 434 |
+
assert _measure(bp, "RATE_ON_LINE").formula == "MIN(MAX(0.55, 0), 1)"
|
| 435 |
+
fixes = bp.repair_formulas()
|
| 436 |
+
m = _measure(bp, "RATE_ON_LINE")
|
| 437 |
+
assert m.kind == "base" and m.distribution == "uniform"
|
| 438 |
+
assert m.params["low"] < 0.55 < m.params["high"]
|
| 439 |
+
assert any("'MIN(MAX(0.55, 0), 1)' is CONSTANT" in f and "0.55 whatever" in f for f in fixes)
|
| 440 |
+
|
| 441 |
+
|
| 442 |
+
def test_a_formula_constant_at_zero_takes_the_donor_path_and_says_why():
|
| 443 |
+
# X - X and X * 0 say nothing about magnitude, so they demote off a sibling
|
| 444 |
+
# like any other broken formula β a difference signed and centred on zero.
|
| 445 |
+
bp = _frontline("WRITTEN_PREMIUM - WRITTEN_PREMIUM", fmt="currency")
|
| 446 |
+
fixes = bp.repair_formulas()
|
| 447 |
+
m = _measure(bp, "RATE_ON_LINE")
|
| 448 |
+
assert m.kind == "base" and m.formula == "" and m.signed is True
|
| 449 |
+
fix = next(f for f in fixes if f.startswith("RATE_ON_LINE"))
|
| 450 |
+
assert "WRITTEN_PREMIUM - WRITTEN_PREMIUM" in fix and "CONSTANT" in fix
|
| 451 |
+
assert "not computable" not in fix
|
| 452 |
+
|
| 453 |
+
bp = _frontline("WRITTEN_PREMIUM * 0", fmt="currency")
|
| 454 |
+
fixes = bp.repair_formulas()
|
| 455 |
+
m = _measure(bp, "RATE_ON_LINE")
|
| 456 |
+
assert m.kind == "base" and m.signed is False
|
| 457 |
+
assert any("WRITTEN_PREMIUM * 0" in f and "CONSTANT" in f for f in fixes)
|
| 458 |
+
|
| 459 |
+
|
| 460 |
+
def test_a_constant_formula_with_nothing_to_scale_off_is_dropped_and_named():
|
| 461 |
+
bp = _blueprint([
|
| 462 |
+
{"name": "BASE_AMOUNT", "fmt": "currency", "params": {"mean": 100}},
|
| 463 |
+
{"name": "DOUBLED", "kind": "derived", "formula": "BASE_AMOUNT * 2"},
|
| 464 |
+
{"name": "ZEROED", "kind": "derived", "formula": "DOUBLED - DOUBLED"},
|
| 465 |
+
], insights=_insights("BASE_AMOUNT"))
|
| 466 |
+
fixes = bp.repair_formulas()
|
| 467 |
+
assert {m.name for m in bp.facts[0].measures} == {"BASE_AMOUNT", "DOUBLED"}
|
| 468 |
+
fix = next(f for f in fixes if f.startswith("ZEROED"))
|
| 469 |
+
assert "dropped" in fix and "DOUBLED - DOUBLED" in fix and "CONSTANT" in fix
|
| 470 |
+
|
| 471 |
+
|
| 472 |
+
def test_a_real_formula_is_left_alone_by_the_constant_guard():
|
| 473 |
+
bp = _frontline("WRITTEN_PREMIUM / POLICY_LIMIT")
|
| 474 |
+
assert bp.formula_problems() == []
|
| 475 |
+
assert bp.repair_formulas() == []
|
| 476 |
+
assert _measure(bp, "RATE_ON_LINE").kind == "derived"
|
|
@@ -13,6 +13,7 @@ import pytest
|
|
| 13 |
from demoprep_app.dataset.formula import (
|
| 14 |
FormulaEvaluationError,
|
| 15 |
alias_target,
|
|
|
|
| 16 |
evaluate,
|
| 17 |
identifiers,
|
| 18 |
ratio_pairs,
|
|
@@ -141,6 +142,71 @@ def test_a_real_computation_is_not_an_identity(formula):
|
|
| 141 |
assert validate(formula, {"RENTAL_DAYS", "IDLE_DAYS"}, measure="DAYS_ON_SITE") == []
|
| 142 |
|
| 143 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 144 |
# --- helpers ----------------------------------------------------------------
|
| 145 |
|
| 146 |
|
|
|
|
| 13 |
from demoprep_app.dataset.formula import (
|
| 14 |
FormulaEvaluationError,
|
| 15 |
alias_target,
|
| 16 |
+
constant_value,
|
| 17 |
evaluate,
|
| 18 |
identifiers,
|
| 19 |
ratio_pairs,
|
|
|
|
| 142 |
assert validate(formula, {"RENTAL_DAYS", "IDLE_DAYS"}, measure="DAYS_ON_SITE") == []
|
| 143 |
|
| 144 |
|
| 145 |
+
# --- constant formulas cannot produce a measure (findings triage 2026-10-20, R4)
|
| 146 |
+
#
|
| 147 |
+
# Frontline's RATE_ON_LINE = WRITTEN_PREMIUM / (WRITTEN_PREMIUM / 0.55) is 0.55
|
| 148 |
+
# on every row by algebra. The identity guard above recognises shapes; it
|
| 149 |
+
# cannot enumerate every algebraic identity, so constant_value() EVALUATES the
|
| 150 |
+
# formula on random inputs and asks whether the output moved. It sits beside
|
| 151 |
+
# alias_target() so there is one place for "this formula cannot produce a
|
| 152 |
+
# measure", and the blueprint repair loop demotes or drops it before generation.
|
| 153 |
+
|
| 154 |
+
|
| 155 |
+
@pytest.mark.parametrize("formula, value", [
|
| 156 |
+
("WRITTEN_PREMIUM / (WRITTEN_PREMIUM / 0.55)", 0.55), # the Frontline case
|
| 157 |
+
("WRITTEN_PREMIUM - WRITTEN_PREMIUM", 0.0),
|
| 158 |
+
("WRITTEN_PREMIUM * 0", 0.0),
|
| 159 |
+
("0.55", 0.55), # a literal: no inputs at all
|
| 160 |
+
("WRITTEN_PREMIUM / WRITTEN_PREMIUM", 1.0),
|
| 161 |
+
("(WRITTEN_PREMIUM + POLICY_LIMIT) * 0 + 7", 7.0),
|
| 162 |
+
("ROUND(WRITTEN_PREMIUM * 0.55 / WRITTEN_PREMIUM, 2)", 0.55),
|
| 163 |
+
])
|
| 164 |
+
def test_a_formula_that_cannot_vary_is_reported_with_its_value(formula, value):
|
| 165 |
+
assert constant_value(formula) == pytest.approx(value)
|
| 166 |
+
|
| 167 |
+
|
| 168 |
+
@pytest.mark.parametrize("formula", [
|
| 169 |
+
"WRITTEN_PREMIUM / POLICY_LIMIT", "WRITTEN_PREMIUM * 0.55", "WRITTEN_PREMIUM - POLICY_LIMIT",
|
| 170 |
+
"MIN(MAX(WRITTEN_PREMIUM / POLICY_LIMIT, 0), 1)", "ROUND(WRITTEN_PREMIUM / POLICY_LIMIT, 2)",
|
| 171 |
+
"1 / WRITTEN_PREMIUM", "ABS(WRITTEN_PREMIUM)", "COALESCE(WRITTEN_PREMIUM, 0)",
|
| 172 |
+
"WRITTEN_PREMIUM * 1", # an identity varies: that is alias_target's case
|
| 173 |
+
])
|
| 174 |
+
def test_a_formula_that_varies_with_its_inputs_is_not_constant(formula):
|
| 175 |
+
assert constant_value(formula) is None
|
| 176 |
+
|
| 177 |
+
|
| 178 |
+
def test_constant_detection_is_by_evaluation_so_it_never_guesses():
|
| 179 |
+
# Not parseable, or not evaluable: validate() reports those; a constant
|
| 180 |
+
# verdict is only ever reached on a formula that actually ran.
|
| 181 |
+
assert constant_value("this is not ( a formula") is None
|
| 182 |
+
assert constant_value("SQRT(WRITTEN_PREMIUM) * 0") is None
|
| 183 |
+
assert constant_value("") is None
|
| 184 |
+
# Deterministic for the same formula text: a re-parse reaches the same verdict.
|
| 185 |
+
assert constant_value("WRITTEN_PREMIUM / POLICY_LIMIT") is None
|
| 186 |
+
assert constant_value("WRITTEN_PREMIUM / (WRITTEN_PREMIUM / 0.55)") == pytest.approx(0.55)
|
| 187 |
+
|
| 188 |
+
|
| 189 |
+
def test_a_signed_input_is_probed_on_both_sides_of_zero():
|
| 190 |
+
# MIN(V, 0) is the downside of a signed variance. Probed only with positive
|
| 191 |
+
# inputs it would read as constant 0 and lose the tile.
|
| 192 |
+
assert constant_value("MIN(BUDGET_VARIANCE, 0)", signed={"BUDGET_VARIANCE"}) is None
|
| 193 |
+
# ...while a formula that really is constant on both sides stays caught.
|
| 194 |
+
assert constant_value("MAX(BUDGET_VARIANCE, 0) * MIN(BUDGET_VARIANCE, 0)",
|
| 195 |
+
signed={"BUDGET_VARIANCE"}) == pytest.approx(0.0)
|
| 196 |
+
|
| 197 |
+
|
| 198 |
+
def test_a_rounding_step_is_judged_at_the_measures_own_scale():
|
| 199 |
+
# ROUND(X / 1e6, 1) is a real computation for a seven-figure premium and a
|
| 200 |
+
# column of zeros for a two-figure one β the scale decides, not the shape.
|
| 201 |
+
formula = "ROUND(WRITTEN_PREMIUM / 1000000, 1)"
|
| 202 |
+
assert constant_value(formula, scales={"WRITTEN_PREMIUM": 5_000_000}) is None
|
| 203 |
+
assert constant_value(formula, scales={"WRITTEN_PREMIUM": 50}) == pytest.approx(0.0)
|
| 204 |
+
# The same for whole hundreds: a six-figure premium varies, an unscaled
|
| 205 |
+
# probe around 1.0 does not β which is why the blueprint passes its scales.
|
| 206 |
+
assert constant_value("ROUND(WRITTEN_PREMIUM / 100, 0)", scales={"WRITTEN_PREMIUM": 250000}) is None
|
| 207 |
+
assert constant_value("ROUND(WRITTEN_PREMIUM / 100, 0)") == pytest.approx(0.0)
|
| 208 |
+
|
| 209 |
+
|
| 210 |
# --- helpers ----------------------------------------------------------------
|
| 211 |
|
| 212 |
|
|
@@ -895,6 +895,78 @@ def test_a_mirror_is_omitted_without_a_recompute_and_names_the_identity():
|
|
| 895 |
assert "not a measure of" not in defect["suggestion"]
|
| 896 |
|
| 897 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 898 |
def test_a_hollow_difference_names_the_input_that_contributed_nothing():
|
| 899 |
from demoprep_app.integrations.snowflake.quality_gate import _mirror_explanation
|
| 900 |
|
|
|
|
| 895 |
assert "not a measure of" not in defect["suggestion"]
|
| 896 |
|
| 897 |
|
| 898 |
+
def _frontline_cursor(*, rate_formula_known: bool):
|
| 899 |
+
"""Frontline's POLICY_UNDERWRITING with RATE_ON_LINE flat at 0.55.
|
| 900 |
+
|
| 901 |
+
Five measures, so one dropped column (20%) stays under the 25% abort line.
|
| 902 |
+
"""
|
| 903 |
+
money = lambda lo, hi, avg: {"non_zero": 4000, "min": lo, "max": hi, "avg": avg}
|
| 904 |
+
cursor = FakeCursor(
|
| 905 |
+
columns_rows=[
|
| 906 |
+
("POLICY_UNDERWRITING", "WRITTEN_PREMIUM_USD", "NUMBER(16,2)"),
|
| 907 |
+
("POLICY_UNDERWRITING", "POLICY_LIMIT_USD", "NUMBER(16,2)"),
|
| 908 |
+
("POLICY_UNDERWRITING", "LOSS_PAID_USD", "NUMBER(16,2)"),
|
| 909 |
+
("POLICY_UNDERWRITING", "EXPENSE_USD", "NUMBER(16,2)"),
|
| 910 |
+
("POLICY_UNDERWRITING", "RATE_ON_LINE", "NUMBER(16,4)"),
|
| 911 |
+
],
|
| 912 |
+
profiles={"POLICY_UNDERWRITING": [profile_row(4000, [
|
| 913 |
+
("WRITTEN_PREMIUM_USD", money(1200.0, 980000.0, 142000.0)),
|
| 914 |
+
("POLICY_LIMIT_USD", money(10000.0, 5000000.0, 910000.0)),
|
| 915 |
+
("LOSS_PAID_USD", money(0.0, 640000.0, 61000.0)),
|
| 916 |
+
("EXPENSE_USD", money(90.0, 88000.0, 12500.0)),
|
| 917 |
+
("RATE_ON_LINE", {"non_zero": 4000, "min": 0.55, "max": 0.55, "avg": 0.55}),
|
| 918 |
+
])]},
|
| 919 |
+
part_whole={"POLICY_UNDERWRITING": [(0,), (0,)]},
|
| 920 |
+
)
|
| 921 |
+
formulas = (
|
| 922 |
+
{"POLICY_UNDERWRITING.RATE_ON_LINE": "WRITTEN_PREMIUM_USD / (WRITTEN_PREMIUM_USD / 0.55)"}
|
| 923 |
+
if rate_formula_known else {}
|
| 924 |
+
)
|
| 925 |
+
return cursor, formulas
|
| 926 |
+
|
| 927 |
+
|
| 928 |
+
def test_a_constant_derived_column_is_omitted_without_a_recompute():
|
| 929 |
+
"""Frontline (2026-10-20, run 5ec4b833c11b): RATE_ON_LINE = WRITTEN_PREMIUM
|
| 930 |
+
/ (WRITTEN_PREMIUM / 0.55) is 0.55 on every row. The gate recomputed it from
|
| 931 |
+
the same formula, re-profiled, and posted repair_failed on top of
|
| 932 |
+
measure_constant β two findings and a capped grade for one dropped tile."""
|
| 933 |
+
cursor, formulas = _frontline_cursor(rate_formula_known=True)
|
| 934 |
+
profile = run_measure_quality_gate(FakeConnection(cursor), "FRO_sch", formulas=formulas)
|
| 935 |
+
|
| 936 |
+
assert [d["defect"] for d in profile["defects"]] == ["constant"]
|
| 937 |
+
assert cursor.sql_of_kind("UPDATE") == [], "a constant is never recomputed"
|
| 938 |
+
assert profile["repair_failures"] == [] and profile["repairs"] == []
|
| 939 |
+
assert [o["column"] for o in profile["omissions"]] == ["RATE_ON_LINE"]
|
| 940 |
+
assert cursor.sql_of_kind("ALTER") == [
|
| 941 |
+
'ALTER TABLE "FRO_sch"."POLICY_UNDERWRITING" DROP COLUMN "RATE_ON_LINE"'
|
| 942 |
+
]
|
| 943 |
+
assert profile["failures"] == []
|
| 944 |
+
# Exactly one finding for the dropped measure β and it is not repair_failed.
|
| 945 |
+
findings = [f for f in profile["findings"] if f["severity"] != "info"]
|
| 946 |
+
assert [f["code"] for f in findings] == ["measure_constant"]
|
| 947 |
+
assert findings[0]["measure"] == "RATE_ON_LINE" and findings[0]["extra"]["omitted"] is True
|
| 948 |
+
assert "DROPPED" in findings[0]["message"]
|
| 949 |
+
defect = profile["defects"][0]
|
| 950 |
+
assert "WRITTEN_PREMIUM_USD / (WRITTEN_PREMIUM_USD / 0.55)" in defect["detail"]
|
| 951 |
+
assert "is constant" in defect["suggestion"] and "blueprint" in defect["suggestion"]
|
| 952 |
+
|
| 953 |
+
|
| 954 |
+
def test_a_constant_base_column_is_kept_on_the_board_and_named():
|
| 955 |
+
# No formula: nothing to recompute and nothing to blame the blueprint for β
|
| 956 |
+
# a flat attribute is reported and kept, not dropped.
|
| 957 |
+
cursor, formulas = _frontline_cursor(rate_formula_known=False)
|
| 958 |
+
profile = run_measure_quality_gate(FakeConnection(cursor), "FRO_sch", formulas=formulas)
|
| 959 |
+
|
| 960 |
+
assert [d["defect"] for d in profile["defects"]] == ["constant"]
|
| 961 |
+
assert cursor.sql_of_kind("UPDATE") == [] and cursor.sql_of_kind("ALTER") == []
|
| 962 |
+
assert profile["repair_failures"] == [] and profile["omissions"] == []
|
| 963 |
+
assert [d["column"] for d in profile["failures"]] == ["RATE_ON_LINE"]
|
| 964 |
+
findings = [f for f in profile["findings"] if f["severity"] != "info"]
|
| 965 |
+
assert [f["code"] for f in findings] == ["measure_constant"]
|
| 966 |
+
assert findings[0]["extra"]["omitted"] is False
|
| 967 |
+
assert profile["defects"][0]["detail"] == "every row is 0.55"
|
| 968 |
+
|
| 969 |
+
|
| 970 |
def test_a_hollow_difference_names_the_input_that_contributed_nothing():
|
| 971 |
from demoprep_app.integrations.snowflake.quality_gate import _mirror_explanation
|
| 972 |
|