recon_gen.common.spine.inv_fanout
Investigation fanout (seed-color) family — ViolationGenerator only.
AY.2.b promotion of common/l2/seed.py::InvFanoutPlant (N.4.h fuzzer Investigation coverage). This is a SEED-COLOR generator: it plants N two-leg transfers (each sender → one shared recipient) on the anchor day so the Investigation dashboard’s inv_money_trail_edges + inv_pair_rolling_anomalies matviews have data to operate on.
Per the AY.2.b evidence-currency layering:
intended returns a CoverageObservation keyed on (recipient_account_id, sender_count, rail_name, anchor_day). The seed claims “I planted a healthy N-sender fanout to recipient R.”
Edge to MoneyTrailInvariant (registered): every emitted transfer has transfer_parent_id=NULL → each is a depth-0 (root) edge the inv_money_trail_edges recursive CTE surfaces. The registered edge documents this deterministic side-effect; it does NOT make InvFanoutGenerator a “rule violation” generator (the primary intent is still seed-color coverage). AU.5’s gate permits coverage generators to carry edges; the bucket membership (ALL_COVERAGE_GENERATORS) is the discriminator on “this generator’s primary purpose.”
NOT registered for AnomalyInvariant: the rolling-window z-score is probabilistic. A 2-sender × $100 plant on a fresh DB won’t pop the bucket threshold (the OLD boost_inv_fanout_plants (5×) deliberately multiplies to push it over). The minimal- viable smart constructor stays under threshold; a deferred scenario_for_anomaly_spike(…) variant would emit at higher density + carry an Anomaly edge.
Recipient is caller-supplied (the factory provides a deterministic synthetic default + the leaf-internal fields the inv_pair_rolling_anomalies filter requires: account_scope=’internal’ AND account_parent_role IS NOT NULL).
Classes
|
Smart constructor namespace for InvFanoutGenerator. |
|
Emit N two-leg transfers (one per sender) all crediting one leaf-internal recipient on the anchor day. |
- class recon_gen.common.spine.inv_fanout.InvFanoutFactory(prefix='spec_example')[source]
Bases:
objectSmart constructor namespace for InvFanoutGenerator.
No L2-instance resolution needed — the Investigation matviews don’t read rail/template declarations (they walk transactions by account_scope + account_parent_role + transfer_id). The factory just supplies sensible defaults + synthetic accounts.
- Parameters:
prefix (str)
- name: ClassVar[str] = 'inv_fanout'
- prefix: str = 'spec_example'
- scenario_for_fanout(*, sender_count=2, recipient_account_id='acct-inv-fanout-recipient', rail_name='_spine_plant', amount_per_transfer=100.0, anchor_day=datetime.date(2030, 1, 1))[source]
Build a generator that plants sender_count distinct senders all crediting one recipient on the anchor day.
Defaults are minimal-viable: 2 senders × $100 stays under the anomaly z-score threshold (the OLD plant’s boost_inv_ fanout_plants(5×) multiplies for anomaly density; the spine generator delegates that decision to the picker layer).
- Return type:
- Parameters:
sender_count (int)
recipient_account_id (str)
rail_name (str)
amount_per_transfer (float)
anchor_day (date)
- class recon_gen.common.spine.inv_fanout.InvFanoutGenerator(recipient_account_id, sender_account_ids, rail_name, amount_per_transfer, anchor_day, prefix='spec_example', recipient_role='CustomerSubledger', recipient_parent_role='CustomerLedger', sender_role='ExternalCounterparty', sender_scope='external', sender_parent_role=None)[source]
Bases:
objectEmit N two-leg transfers (one per sender) all crediting one leaf-internal recipient on the anchor day. Each transfer is a debit on the sender + credit on the recipient summing to zero, sharing one transfer_id, both legs stamped with metadata.sender_id / recipient_id for downstream investigation.
Recipient’s account fields (role, parent_role) MUST satisfy the inv_pair_rolling_anomalies matview filter (account_scope=’internal’ AND account_parent_role IS NOT NULL) — defaults below populate them; production picker threads real TemplateInstance.account_id / role tuples post-AY.4.
intended returns a CoverageObservation. Registered edge to MoneyTrailInvariant: each transfer is a depth-0 edge the inv_money_trail_edges recursive CTE surfaces.
- Parameters:
recipient_account_id (str)
sender_account_ids (tuple[str, ...])
rail_name (str)
amount_per_transfer (float)
anchor_day (date)
prefix (str)
recipient_role (str)
recipient_parent_role (str)
sender_role (str)
sender_scope (str)
sender_parent_role (str | None)
- amount_per_transfer: float
- anchor_day: date
- property claimed_accounts: frozenset[str]
recipient + every sender. Two fanouts to the same recipient on the same day would collide via the recipient account_id.
- Type:
AV.5 contract
- emit(conn, *, scenario_id=None)[source]
- Return type:
None- Parameters:
conn (SyncConnection)
scenario_id (str | None)
- property intended: CoverageObservation
a fanout of sender_count senders to recipient_account_id landed on anchor_day. Identity carries the natural-key tuple a coverage detector would round-trip against if/when one lands.
- Type:
Presence evidence
- prefix: str = 'spec_example'
- rail_name: str
- recipient_account_id: str
- recipient_parent_role: str = 'CustomerLedger'
- recipient_role: str = 'CustomerSubledger'
- sender_account_ids: tuple[str, ...]
- sender_parent_role: str | None = None
- sender_role: str = 'ExternalCounterparty'
- sender_scope: str = 'external'
- property transfer_ids: tuple[str, ...]
The len(sender_account_ids) transfer_ids this plant emits, in stable sender-sorted order (matches the OLD emitter’s deterministic ordering).