recon_gen.common.spine.limit_breach
Limit-Breach family — deepest L2 coupling of the L1 spine.
LimitBreachInvariant fires when per-(account, business_day, rail, direction) Σ ABS(amount_money) exceeds the L2’s LimitSchedule.cap for that (parent_role, rail, direction) triple. The first spine invariant whose smart constructor reads BOTH:
An L2 entity (the cap from LimitSchedule)
The plant’s amount AS a function of the L2 value (cap + overshoot)
This is AP.3 finding #4’s from_instance smart constructor — the disproof of the “blind generator” hypothesis. The cap value itself is a load-bearing input to the emission, not just a discovery target.
Per AU.3.b’s TZ note: limit_breach’s matview is wall-clock- agnostic (groups by DATE(posting), not CURRENT_TIMESTAMP - posting). So the plant uses a static anchor day (2030-01-01 like drift/overdraft/expected_eod) — no TZ-skew concerns.
Sign convention from the CHECK constraint on <prefix>_transactions:
- amount_direction='Debit' requires amount_money <= 0
- amount_direction='Credit' requires amount_money >= 0
The matview’s SUM(ABS(amount_money)) makes both contribute positively
to the per-direction total. So:
- Outbound limit (Debit) plant: amount_money = -(cap + overshoot)
- Inbound limit (Credit) plant: amount_money = (cap + overshoot)
Empirical-edge prediction (same as stuck_unbundled): Posted leg with NO matching balance row doesn’t trip drift (no JOIN match in _computed_subledger_balance). Single-edge registry entry expected. Test verifies.
Classes
|
Emit a single Posted transaction whose ABS(amount_money) = cap + overshoot for the given (account, day, rail, direction). |
|
Per-rail per-direction flow-cap detector. |
- class recon_gen.common.spine.limit_breach.LimitBreachGenerator(account_id, account_role, account_parent_role, rail_name, direction, cap, overshoot, anchor_day, prefix='spec_example')[source]
Bases:
objectEmit a single Posted transaction whose ABS(amount_money) = cap + overshoot for the given (account, day, rail, direction). The matview’s GROUP BY collapses to this single row → SUM = cap + overshoot > cap → limit_breach fires.
Sign convention is locked by the transactions CHECK constraint — Debit ⇒ money ≤ 0; Credit ⇒ money ≥ 0. The matview’s SUM(ABS) makes both contribute positively.
- Parameters:
account_id (str)
account_role (str)
account_parent_role (str)
rail_name (str)
direction (Literal['Outbound', 'Inbound'])
cap (float)
overshoot (float)
anchor_day (date)
prefix (str)
- account_id: str
- account_parent_role: str
- account_role: str
- anchor_day: date
- cap: float
- property claimed_accounts: frozenset[str]
The single account_id this plant breaches a cap on. AV.5.
- direction: Literal['Outbound', 'Inbound']
- emit(conn, *, scenario_id=None)[source]
- Return type:
None- Parameters:
conn (SyncConnection)
scenario_id (str | None)
- property intended: RuleViolation
- overshoot: float
- prefix: str = 'spec_example'
- rail_name: str
- class recon_gen.common.spine.limit_breach.LimitBreachInvariant(prefix='spec_example')[source]
Bases:
objectPer-rail per-direction flow-cap detector. The matview gates on
cap IS NOT NULL(rail+parent_role+direction has a LimitSchedule) ANDSUM(ABS(amount_money)) > cap. Identity is (account_id, business_day, rail_name, direction) — analyst-facing diff readability.- Parameters:
prefix (str)
- name: ClassVar[str] = 'limit_breach'
- prefix: str = 'spec_example'
- scenario_for(parent_role, rail_name, *, direction='Outbound', overshoot=100.0, instance=None, account_id=None)[source]
Resolve (parent_role, rail_name, direction) against the L2’s LimitSchedule; return a generator that plants ONE Posted transaction on a child account whose account_parent_role = parent_role, with amount_money sized to overshoot the cap.
overshoot=0.0 ⇒ amount == cap ⇒ matview’s strict > filter excludes ⇒ no fire (AP.2 non-violating convention adapted to Money-unit knob). Positive fires.
- Return type:
- Parameters:
parent_role (str)
rail_name (str)
direction (Literal['Outbound', 'Inbound'])
overshoot (float)
instance (L2Instance | None)
account_id (str | None)
Raises ValueError if: - The L2 has no LimitSchedule matching `(parent_role, rail_name,
direction)`
The L2 has no child account with account_parent_role = parent_role (the matview filters account_parent_role IS NOT NULL; without a matching child the plant is inert)
AY.4.c — account_id overrides the default synthetic ID. The plant adapter (AY.4.c.3) threads OLD LimitBreachPlant.account_id through this kwarg so N plants on the same (parent_role, rail, direction) triple produce N distinct generators (the default f”acct-limit-breach-{rail_name}-{direction}” derivation would collide). Existing test callers can pass nothing → preserves the synthetic default byte-stable.
Note: LimitBreachGenerator carries only one account_id field (the breaching account); there is no counter_account_id — the matview groups solely on (account_id, business_day, rail_name, direction).