Skip to content

feat(3jane): redefine junior-buffer alert around at-risk credit coverage - #324

Closed
spalen0 wants to merge 1 commit into
mainfrom
3jane-improve
Closed

feat(3jane): redefine junior-buffer alert around at-risk credit coverage#324
spalen0 wants to merge 1 commit into
mainfrom
3jane-improve

Conversation

@spalen0

@spalen0 spalen0 commented Jul 24, 2026

Copy link
Copy Markdown
Collaborator

Summary

Replaces the noisy "3Jane Junior Buffer Low" alert (which measured sUSD3 against the entire borrow book — a structural design constant, not a risk signal) with tiered, real-signal coverage checks based on severity-weighted at-risk credit.

Closes #323.

New monitors

  1. Junior Tranche Coverage (sUSD3 / weighted at-risk). HIGH when the sUSD3 stakers alone cannot absorb the impaired book (< 2.0x). The leading indicator that sUSD3 PPS is about to drop.
  2. Senior Tranche Coverage ((Insurance + sUSD3) / weighted at-risk). HIGH < 1.5x, CRITICAL < 1.0x. The USD3 holders' risk view. CRITICAL also dispatches the emergency-withdrawal hook (3jane is in DISPATCHABLE_PROTOCOLS).
  3. Junior Buffer Drifting (LOW) — demoted old whole-book ratio. Fires only on a structural floor breach (8%) or a sharp drop (≥3pp) vs a cached trailing baseline.

At-risk exposure aggregation

New AtRiskExposure dataclass + compute_at_risk_exposure() helper. Sums ending_balance * weight(default_bucket) across all Delinquent/Default borrowers from the existing BorrowerRepaymentSnapshot list. Weights are an expected-loss proxy (default=1.0, 1d=0.9, 3d=0.7, 7d=0.5, 14d=0.3, delinquent=0.3) and are module-level constants so they're easy to tune against historical cure/charge-off rates. Also exposes largest_borrower_exposure for a future concentration check (one whale defaulting can blow the buffer even if the aggregate looks fine).

Edge cases handled

  • No impaired borrowers: both coverage checks clear any outstanding alert and stay silent — eliminates the false alarm.
  • Envio unreachable (ENVIO_GRAPHQL_URL unset / request failed / GraphQL errors): load_borrower_default_watch_snapshots_from_envio() now returns None to signal data unavailable; coverage checks are skipped and only the demoted structural check runs. No coverage alert on missing data.
  • Same snapshot list is loaded once in main() and shared with the default-watch and coverage checks, so we never double-fetch from Envio.
  • Dedup uses should_alert_value_drop with new cache keys (3JANE_JUNIOR_COVERAGE_ALERTED, 3JANE_SENIOR_COVERAGE_ALERTED). Senior coverage escalates HIGH → CRITICAL on the same run when the value drops further; recovery above the 1.5x threshold re-arms.
  • Open question 1 (exposure basis): chose ending_balance_raw (with fallback to amount_due_raw when 0), as recommended in the issue.
  • Open question 2 (severity weights): starting values, ready to tune against historical data.
  • Open question 3 (waterfall order): matched the loss-waterfall order in the issue (Insurance Fund above sUSD3 above USD3).

Acceptance criteria

  • With no impaired borrowers, no junior/senior coverage alert fires regardless of the sUSD3/whole-book ratio.
  • When weighted at-risk credit exceeds the first-loss stack, a CRITICAL senior-protection alert fires (and would dispatch).
  • Junior-health HIGH fires before senior-protection CRITICAL as impairment grows (escalation ladder).
  • Alerts are deduped and re-arm on recovery.
  • Envio outage degrades gracefully to the demoted structural ratio without emitting coverage alerts.
  • README.md, monitoring.yaml, and tests/test_3jane.py updated; ruff, mypy, and tests pass (658 passed, 2 skipped; 0 new mypy errors).

Test coverage

16 new tests cover:

  • Weighted at-risk aggregation (per-bucket weights, exposure fallback, zero-exposure handling)
  • Junior coverage: HIGH < 2.0x, silent at/above threshold, dedup, rearm on recovery
  • Senior coverage: HIGH < 1.5x, CRITICAL < 1.0x, escalation, dedup, rearm
  • Independent cache keys for junior vs senior
  • at_risk <= 0 clears both coverage alerts
  • Envio-unavailable fallback path (skips coverage, only runs demoted check)
  • Breakdown composition in alert messages

Existing tests updated to reflect the new demoted LOW-severity behaviour (no longer alerts at 12.5% design value).

🤖 Generated with Claude Code

The 3Jane Junior Buffer Low alert was noisy because it measured sUSD3
against the entire borrow book, which is a structural design constant
(~10-15% first-loss tranche) and carries no information about a *change*
in risk. This replaces it with a tiered, real-signal coverage check.

New monitors (read from existing Envio snapshot list - no double-fetch):

* **Junior Tranche Coverage** - sUSD3 backing / severity-weighted at-risk
  credit. HIGH below 2.0x. The sUSD3 stakers' risk view and the leading
  indicator that sUSD3 PPS is about to drop.
* **Senior Tranche Coverage** - (Insurance Fund + sUSD3) / severity-
  weighted at-risk credit. HIGH below 1.5x, CRITICAL below 1.0x. The
  USD3 holders' risk view; CRITICAL also triggers the emergency
  withdrawal dispatch already wired to 3jane.
* **At-risk exposure aggregator** - sum(ending_balance * weight(bucket))
  across all Delinquent/Default borrowers. Weights map the existing
  default_bucket values to an expected-loss proxy: default=1.0,
  1d=0.9, 3d=0.7, 7d=0.5, 14d=0.3, delinquent=0.3. Also exposes
  largest_borrower_exposure for a future concentration check.

With no impaired borrowers the coverage alerts are silent regardless
of the sUSD3/whole-book ratio, eliminating the original false alarm.
When Envio is unreachable the coverage checks are skipped and only the
demoted structural check runs; no alerts on missing data.

The original whole-book ratio becomes a LOW-severity informational
'Junior Buffer Drifting' check that fires only on a structural floor
breach at 8% or a sharp drop of >=3pp versus a cached trailing baseline
- keeping the leverage-drift signal without paging on the steady state.

Deduplication, type annotations, docs, and monitoring.yaml are all
updated. Closes #323.
@spalen0 spalen0 closed this Jul 24, 2026
@spalen0
spalen0 deleted the 3jane-improve branch July 24, 2026 15:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3Jane: redefine junior-buffer alert around at-risk credit coverage

1 participant