"""Behavioral pin for the shared Quote/QuoteLineItem term function. The companion contract test proves the flow and header both call the same pack-owned SERVICE_WINDOW_TERM_MONTHS function. This test closes the remaining hole by EVALUATING that committed function body (both pack roots) with a small interpreter for the exact function subset the formula uses, and comparing against an independent reference implementation of the intended semantics: term(start, end) = k if addMonths(start, k) - 1 day == end for integer k>0 (addMonths clamps day-of-month to the target month's length - the source-CRM convention this inverts) = ROUND(((end - start) + 1) / 30.4375, 1) otherwise Coverage is a deterministic grid (a leap February inside, month-end starts, inverted and same-day windows) plus the named business vectors from the 2026-08-20 adjudication - including the clamp vector Jan 31 -> Feb 27, chosen because tier-1 and tier-2 genuinely disagree there (1 vs 0.9); the previously used Mar 31 -> Apr 29 shape is value-degenerate (both tiers say 1.0) and can certify a clamp-free mutant. The interpreter is ~80 lines and intentionally supports ONLY the constructs the committed formula uses - a formula rewritten with other functions fails loudly here and gets reviewed, not silently accepted. """ import calendar import datetime import json import re import unittest from decimal import Decimal, ROUND_HALF_UP from pathlib import Path ROOT = Path(__file__).resolve().parents[1] PACK_ROOTS = [ ROOT / "packs" / "Salesforce-PingCAP-L2", ROOT / "packs" / "NOT-EDIT-final-merged-packs", ] def header_formula(pack_root: Path) -> str: combined = pack_root / "objects" / "standard_objects_spec.json" if combined.exists(): spec = json.loads(combined.read_text(encoding="utf-8")) objects = spec.get("objects") if isinstance(spec, dict) else spec obj = next(o for o in objects if o.get("apiName") == "Quote") else: payload = json.loads( (pack_root / "objects" / "Quote.json").read_text(encoding="utf-8") ) obj = payload[0] if isinstance(payload, list) else payload field = next(f for f in obj["fields"] if f.get("apiName") == "Term_months__c") return field["formula"] def term_function_body(pack_root: Path) -> str: payload = json.loads( (pack_root / "formula_functions" / "pingcap_quote_functions.json").read_text( encoding="utf-8" ) ) function = next( f for f in payload["functions"] if f.get("name") == "SERVICE_WINDOW_TERM_MONTHS" ) return function["evaluator"]["body"] # ---------------- minimal formula interpreter ---------------- TOKEN = re.compile( r"\s*(?:(?P\d+\.\d+|\d+)|(?P[A-Za-z_][A-Za-z0-9_]*)" r"|(?P<>|<=|>=|[-+*/(),=<>]))" ) def tokenize(src: str): pos, out = 0, [] while pos < len(src): m = TOKEN.match(src, pos) if not m: raise SyntaxError(f"unexpected character at {src[pos:pos+20]!r}") pos = m.end() out.append(m.group().strip()) return out class Parser: """Precedence: comparison < additive < multiplicative < unary < call/atom.""" def __init__(self, tokens): self.toks, self.i = tokens, 0 def peek(self): return self.toks[self.i] if self.i < len(self.toks) else None def take(self, expect=None): tok = self.toks[self.i] if expect is not None and tok != expect: raise SyntaxError(f"expected {expect!r}, got {tok!r}") self.i += 1 return tok def parse(self): node = self.comparison() if self.peek() is not None: raise SyntaxError(f"trailing tokens at {self.toks[self.i:][:5]}") return node def comparison(self): node = self.additive() while self.peek() in ("=", "<>", "<", ">", "<=", ">="): op = self.take() node = ("cmp", op, node, self.additive()) return node def additive(self): node = self.multiplicative() while self.peek() in ("+", "-"): op = self.take() node = ("bin", op, node, self.multiplicative()) return node def multiplicative(self): node = self.unary() while self.peek() in ("*", "/"): op = self.take() node = ("bin", op, node, self.unary()) return node def unary(self): if self.peek() == "-": self.take() return ("neg", self.unary()) return self.atom() def atom(self): tok = self.take() if tok == "(": node = self.comparison() self.take(")") return node if re.fullmatch(r"\d+\.\d+|\d+", tok): return ("num", Decimal(tok)) if self.peek() == "(": # function call self.take("(") args = [] if self.peek() != ")": args.append(self.comparison()) while self.peek() == ",": self.take(",") args.append(self.comparison()) self.take(")") return ("call", tok.upper(), args) return ("field", tok) def evaluate(node, env): kind = node[0] if kind == "num": return node[1] if kind == "field": return env[node[1]] if kind == "neg": return -evaluate(node[1], env) if kind == "cmp": _, op, a, b = node va, vb = evaluate(a, env), evaluate(b, env) return { "=": va == vb, "<>": va != vb, "<": va < vb, ">": va > vb, "<=": va <= vb, ">=": va >= vb, }[op] if kind == "bin": _, op, a, b = node va, vb = evaluate(a, env), evaluate(b, env) if isinstance(va, datetime.date) and isinstance(vb, datetime.date): if op != "-": raise TypeError("only date subtraction is defined") return Decimal((va - vb).days) if isinstance(va, datetime.date) or isinstance(vb, datetime.date): raise TypeError("date/number arithmetic is not used by this formula") if op == "+": return va + vb if op == "-": return va - vb if op == "*": return va * vb return va / vb if kind == "call": _, name, args = node if name == "IF": return evaluate(args[1] if evaluate(args[0], env) else args[2], env) if name == "AND": return all(evaluate(a, env) for a in args) if name == "OR": return any(evaluate(a, env) for a in args) if name == "CASE": subject = evaluate(args[0], env) pairs, default = args[1:-1], args[-1] for k, v in zip(pairs[0::2], pairs[1::2]): if evaluate(k, env) == subject: return evaluate(v, env) return evaluate(default, env) if name == "MOD": a, b = (evaluate(x, env) for x in args) return a - (a / b).to_integral_value(rounding="ROUND_FLOOR") * b if name == "ROUND": value, scale = evaluate(args[0], env), int(evaluate(args[1], env)) q = Decimal(1).scaleb(-scale) return value.quantize(q, rounding=ROUND_HALF_UP) if name == "YEAR": return Decimal(evaluate(args[0], env).year) if name == "MONTH": return Decimal(evaluate(args[0], env).month) if name == "DAY": return Decimal(evaluate(args[0], env).day) if name == "DATE_ADD": date, days = (evaluate(x, env) for x in args) return date + datetime.timedelta(days=int(days)) if name == "__DATE_ADD_MONTHS_TARGET_CLAMP__": date, months = (evaluate(x, env) for x in args) return add_months(date, int(months)) if name == "__DATE_DIFF_DAYS__": end, start = (evaluate(x, env) for x in args) return Decimal((end - start).days) raise NameError(f"function {name} is not part of the pinned subset") raise TypeError(node) # ---------------- independent reference semantics ---------------- def add_months(d: datetime.date, k: int) -> datetime.date: year = d.year + (d.month - 1 + k) // 12 month = (d.month - 1 + k) % 12 + 1 return datetime.date(year, month, min(d.day, calendar.monthrange(year, month)[1])) def reference_term(start: datetime.date, end: datetime.date) -> Decimal: target = end + datetime.timedelta(days=1) base = (end.year - start.year) * 12 + (end.month - start.month) for k in (base - 1, base, base + 1, base + 2): if k > 0 and add_months(start, k) == target: return Decimal(k) days = (end - start).days + 1 return (Decimal(days) / Decimal("30.4375")).quantize( Decimal("0.1"), rounding=ROUND_HALF_UP ) BUSINESS_VECTORS = [ # (start, end, expected) - named shapes from the 2026-08-20 adjudication ("2022-02-01", "2022-07-31", Decimal(6)), # was 5.9 pre-ruling ("2023-01-15", "2023-07-14", Decimal(6)), # mid-month anniversary ("2023-01-31", "2023-02-27", Decimal(1)), # clamp, tiers DISAGREE (0.9 without) ("2023-03-31", "2023-04-29", Decimal(1)), # clamp, JP prod shape ("2025-02-01", "2025-04-30", Decimal(3)), # was 2.9 ("2026-02-01", "2027-04-30", Decimal(15)), # Manus prod shape, was 14.9 ("2023-01-01", "2023-12-31", Decimal(12)), ("2025-03-15", "2025-05-31", Decimal("2.6")), # tier-2 unchanged (not 2.5) ("2024-11-01", "2025-05-24", Decimal("6.7")), # tier-2 unchanged (not 6.74) ("2025-01-31", "2025-02-28", Decimal("1.0")), # month-end end + day-31 start: NOT an anniversary; tier-2 value (29d), distinguishable from tier-1 by Decimal exponent only via the 0.1 quantum - the true clamp discriminator is Jan31->Feb27 above ] class QuoteTermFormulaSemantics(unittest.TestCase): def asts(self): for pack_root in PACK_ROOTS: self.assertEqual( header_formula(pack_root), "SERVICE_WINDOW_TERM_MONTHS(Overall_Start_Date__c, Overall_End_Date__c)", ) yield pack_root.name, Parser(tokenize(term_function_body(pack_root))).parse() def term(self, ast, start: datetime.date, end: datetime.date) -> Decimal: env = {"startDate": start, "endDate": end} return evaluate(ast, env) def test_business_vectors(self): for pack_name, ast in self.asts(): for start, end, expected in BUSINESS_VECTORS: s = datetime.date.fromisoformat(start) e = datetime.date.fromisoformat(end) with self.subTest(pack=pack_name, window=f"{start}..{end}"): self.assertEqual(self.term(ast, s, e), expected) def test_grid_against_reference(self): """92 consecutive starts spanning a leap February, plus every 31-day month-end start of 2023, against offsets covering inverted, same-day, sub-month, and multi-year windows.""" starts = [ datetime.date(2023, 12, 15) + datetime.timedelta(days=i) for i in range(92) ] + [ datetime.date(2023, m, calendar.monthrange(2023, m)[1]) for m in (1, 3, 5, 7, 8, 10, 12) ] offsets = list(range(-3, 64)) + list(range(64, 431, 7)) + [729, 730, 731] for pack_name, ast in self.asts(): mismatches = [] for s in starts: for off in offsets: e = s + datetime.timedelta(days=off) got, want = self.term(ast, s, e), reference_term(s, e) if got != want: mismatches.append((s.isoformat(), e.isoformat(), got, want)) self.assertEqual( mismatches[:10], [], f"{pack_name}: {len(mismatches)} grid mismatches vs reference", ) if __name__ == "__main__": unittest.main()