feat: signal math upgrade — probabilistic, regime-aware scoring pipeline
ci/woodpecker/push/test Pipeline was successful
ci/woodpecker/push/build-1 Pipeline was successful
ci/woodpecker/push/build-2 Pipeline was successful
ci/woodpecker/push/build-3 Pipeline was successful
ci/woodpecker/push/finalize Pipeline was successful
Build and Push / lint-and-test (push) Has been cancelled
Build and Push / build-services (map[cmd:python -m services.adapters.broker_adapter name:broker-adapter]) (push) Has been cancelled
Build and Push / build-services (map[cmd:python -m services.aggregation.worker name:aggregation]) (push) Has been cancelled
Build and Push / build-services (map[cmd:python -m services.extractor.worker name:extractor]) (push) Has been cancelled
Build and Push / build-services (map[cmd:python -m services.ingestion.worker name:ingestion]) (push) Has been cancelled
Build and Push / build-services (map[cmd:python -m services.lake_publisher.worker name:lake-publisher]) (push) Has been cancelled
Build and Push / build-services (map[cmd:python -m services.parser.worker name:parser]) (push) Has been cancelled
Build and Push / build-services (map[cmd:python -m services.recommendation.worker name:recommendation]) (push) Has been cancelled
Build and Push / build-services (map[cmd:python -m services.scheduler.app name:scheduler]) (push) Has been cancelled
Build and Push / build-services (map[cmd:uvicorn services.api.app:app --host 0.0.0.0 --port 8000 name:query-api]) (push) Has been cancelled
Build and Push / build-services (map[cmd:uvicorn services.risk.app:app --host 0.0.0.0 --port 8000 name:risk]) (push) Has been cancelled
Build and Push / build-services (map[cmd:uvicorn services.symbol_registry.app:app --host 0.0.0.0 --port 8000 name:symbol-registry]) (push) Has been cancelled
Build and Push / build-services (map[cmd:uvicorn services.trading.app:app --host 0.0.0.0 --port 8000 name:trading-engine]) (push) Has been cancelled
Build and Push / build-dashboard (push) Has been cancelled
Build and Push / build-superset (push) Has been cancelled
Build and Push / integration-test (push) Has been cancelled
Build and Push / beta-gate (push) Has been cancelled

Implement full probabilistic signal processing pipeline gated behind
probabilistic_scoring_enabled feature flag in risk_configs:

- Bayesian log-likelihood accumulator with Beta posterior and entropy
- Regime detector (trend-following, panic, mean-reversion, uncertainty)
- Source accuracy tracker with per-source historical prediction accuracy
- Sigmoid confidence gate replacing binary gate
- Information gain surprise weighting for rare events
- Adaptive recency decay with event-specific half-lives
- Regime multiplier replacing market context multiplier
- Weighted disagreement entropy for contradiction detection
- Multiplicative macro exposure with conditional integration
- Graph-distance attenuated competitive signal propagation
- Exponentially weighted momentum with volatility scaling
- Expected value recommendation gate

All changes backward-compatible: flag=false preserves exact current behavior.
New outputs stored in existing JSONB columns (no schema changes except
source_accuracy table via migration 034).

Tests: 26 property-based tests (14 correctness properties), 99 unit tests,
1789 total tests passing with zero regressions.
This commit is contained in:
Celes Renata
2026-04-29 11:41:48 +00:00
parent 8c3c1aab43
commit 4e010bc048
24 changed files with 6058 additions and 60 deletions
+349
View File
@@ -0,0 +1,349 @@
# Implementation Plan: Signal Math Upgrade
## Overview
Upgrade the Stonks Oracle signal processing pipeline from deterministic heuristic formulas to a probabilistic, regime-aware, and adaptive mathematical framework. Implementation proceeds in layers: foundations (config, schemas, new modules), then each pipeline stage (scoring → trend assembly → macro → competitive → projection → recommendation), then integration wiring, and finally testing. All changes are gated behind the `probabilistic_scoring_enabled` feature flag.
## Tasks
- [ ] 1. Foundation: Configuration and schema extensions
- [x] 1.1 Extend `ScoringConfig` with probabilistic parameters in `services/aggregation/scoring.py`
- Add `probabilistic: bool = False` toggle field
- Add sigmoid gate parameters: `sigmoid_steepness`, `sigmoid_midpoint`
- Add information gain parameters: `info_gain_lambda`, `info_gain_max`, `default_base_rate`
- Add adaptive decay parameters: `adaptive_decay_impact_scale`, `adaptive_decay_surprise_scale`, `adaptive_decay_market_scale`
- Add regime multiplier parameters: `regime_return_weight`, `regime_volume_weight`, `regime_multiplier_max`
- All new fields must have defaults matching the design document values
- _Requirements: 2.5, 3.1, 5.1, 6.3, 16.1_
- [x] 1.2 Extend `SignalWeight` and `WeightedSignal` dataclasses in `services/aggregation/scoring.py`
- Add optional fields to `SignalWeight`: `sigmoid_gate`, `info_gain_factor`, `source_accuracy_factor`, `regime_multiplier`
- Add optional fields to `WeightedSignal`: `info_gain_factor`, `source_accuracy_factor`, `adaptive_half_life`
- All new fields must have defaults (None or 1.0) for backward compatibility
- _Requirements: 16.1, 2.5, 3.3, 4.2_
- [x] 1.3 Extend `TrendSummary` Pydantic model in `services/shared/schemas.py`
- Add optional fields: `p_bull`, `alpha`, `beta_param`, `bayesian_confidence`, `entropy`, `regime`, `pipeline_mode`
- `pipeline_mode` defaults to `"heuristic"`; all others default to `None`
- _Requirements: 16.1, 1.6, 9.6_
- [x] 1.4 Extend `Recommendation` model in `services/shared/schemas.py` (or `services/recommendation/eligibility.py`)
- Add optional fields: `expected_value`, `p_bull`, `pipeline_mode`
- `pipeline_mode` defaults to `"heuristic"`; all others default to `None`
- _Requirements: 16.1, 14.5_
- [x] 1.5 Add `probabilistic_scoring_enabled` feature flag support in `services/shared/config.py`
- Read `probabilistic_scoring_enabled` from `risk_configs.config` JSONB
- Default to `False` when key is missing, value is invalid, or DB is unreachable
- Propagate flag through `AggregationConfig` dataclass
- Log which pipeline mode is active at cycle start
- _Requirements: 16.3, 16.4, 16.5, 16.6, 16.7_
- [x] 1.6 Create database migration `infra/migrations/034_source_accuracy.sql`
- Create `source_accuracy` table with columns: `id UUID PRIMARY KEY DEFAULT gen_random_uuid()`, `source_id VARCHAR(200) NOT NULL`, `accuracy_ratio FLOAT NOT NULL DEFAULT 0.5`, `sample_count INTEGER NOT NULL DEFAULT 0`, `last_updated TIMESTAMPTZ`, `created_at TIMESTAMPTZ`
- Add `UNIQUE(source_id)` constraint and `idx_source_accuracy_source` index
- _Requirements: 4.5_
- [x] 2. Checkpoint — Verify foundation compiles and existing tests pass
- Ensure all tests pass, ask the user if questions arise.
- [ ] 3. New module: Bayesian Accumulator (`services/aggregation/bayesian.py`)
- [x] 3.1 Implement `BayesianPosterior` dataclass and `compute_bayesian_posterior` function
- Create frozen dataclass with fields: `p_bull`, `alpha`, `beta`, `log_likelihood`, `bayesian_confidence`, `entropy`, `signal_count`
- Define `PRIOR` class-level constant for uninformative prior (p_bull=0.5, α=1.0, β=1.0, C=0.0, H=1.0)
- Implement log-likelihood accumulation: `L_t = Σ(w_i · s_i)` using `weight.combined * sentiment_value`
- Compute `P_bull = σ(L_t)` via sigmoid function
- Compute Beta posterior: `α = 1 + W_bull`, `β = 1 + W_bear` from positive/negative weight sums
- Compute Bayesian confidence: `C = 1 - 4αβ/(α+β)²`
- Compute Shannon entropy via `compute_entropy`
- Return `PRIOR` for empty signal lists
- Skip signals with NaN weight or sentiment
- _Requirements: 1.1, 1.2, 1.3, 1.4, 1.5, 1.6_
- [x] 3.2 Implement `compute_entropy` function
- Shannon entropy: `H = -p·log₂(p) - (1-p)·log₂(1-p)`
- Return 0.0 for p ≤ 0 or p ≥ 1 (edge cases)
- Return value in [0, 1] with maximum 1.0 at p=0.5
- _Requirements: 9.1, 9.7_
- [x] 3.3 Write property test for sigmoid gate monotonicity
- **Property 1: Sigmoid Gate Monotonicity**
- **Validates: Requirements 2.6, 17.1**
- [x] 3.4 Write property test for Beta posterior evidence accumulation
- **Property 2: Beta Posterior Evidence Accumulation**
- **Validates: Requirements 1.3, 17.2**
- [x] 3.5 Write property test for Bayesian confidence symmetry and divergence
- **Property 3: Bayesian Confidence Symmetry and Divergence**
- **Validates: Requirements 1.4, 17.3**
- [x] 3.6 Write property test for Bayesian posterior round-trip consistency
- **Property 4: Bayesian Posterior Round-Trip Consistency**
- **Validates: Requirements 1.7, 17.7**
- [x] 3.7 Write property test for Shannon entropy range and maximum
- **Property 8: Shannon Entropy Range and Maximum**
- **Validates: Requirements 9.7**
- [x] 3.8 Write property test for Bayesian confidence monotonic with agreeing signals
- **Property 13: Bayesian Confidence Monotonic with Agreeing Signals**
- **Validates: Requirements 8.6**
- [ ] 4. New module: Regime Detector (`services/aggregation/regime.py`)
- [x] 4.1 Implement `MarketRegime` enum, `RegimeClassification` and `RegimeConfig` dataclasses
- `MarketRegime`: `TREND_FOLLOWING`, `PANIC`, `MEAN_REVERSION`, `UNCERTAINTY`
- `RegimeClassification`: `regime`, `trend_indicator`, `volatility_ratio`, `bullish_threshold`, `bearish_threshold`, `contradiction_penalty_multiplier`
- `RegimeConfig`: all configurable parameters with defaults from design
- _Requirements: 7.3_
- [x] 4.2 Implement `compute_ema` and `classify_regime` functions
- `compute_ema`: exponential moving average over last N values
- `classify_regime`: compute trend indicator `R = sign(EMA_20 - EMA_100)` and volatility ratio `V_r = σ_20 / σ_100`
- Classification rules: trend-following (R≠0 AND V_r<1.2), panic (V_r>1.5), mean-reversion (R=0 AND V_r<1.0), uncertainty (all other)
- Adjust thresholds per regime: panic→±0.10, mean-reversion→±0.20, trend-following→±0.15, uncertainty→±0.15 with contradiction multiplier 0.6
- Default to uncertainty when data is insufficient (<100 days) or σ values are zero
- _Requirements: 7.1, 7.2, 7.3, 7.4, 7.5, 7.6, 7.7, 7.9_
- [ ] 5. New module: Source Accuracy Tracker (`services/aggregation/source_accuracy.py`)
- [x] 5.1 Implement `SourceAccuracy` dataclass and database functions
- `SourceAccuracy` dataclass with `source_id`, `accuracy_ratio`, `sample_count`, `last_updated`
- `accuracy_factor` property: return 1.0 when sample_count < 10, else `0.5 + accuracy_ratio`
- `fetch_source_accuracy`: batch fetch from `source_accuracy` table via asyncpg
- `update_source_accuracy`: update accuracy metrics from realized price outcomes
- Handle DB unreachable: return neutral factor 1.0 for all sources
- Clamp corrupted accuracy_ratio to [0.0, 1.0]
- _Requirements: 4.1, 4.2, 4.3, 4.4, 4.5_
- [x] 6. Checkpoint — Verify new modules compile and unit tests pass
- Ensure all tests pass, ask the user if questions arise.
- [ ] 7. Signal Scorer upgrades (`services/aggregation/scoring.py`)
- [x] 7.1 Implement sigmoid confidence gate
- Add `sigmoid_gate(x, steepness, midpoint)` function: `σ(k·(x - midpoint))`
- When `probabilistic=True`, replace binary gate with sigmoid gate in `compute_signal_weight`
- When `probabilistic=False`, preserve existing binary gate behavior
- _Requirements: 2.1, 2.2, 2.3, 2.4, 2.5_
- [x] 7.2 Implement information gain surprise weighting
- Add `EVENT_TYPE_BASE_RATES` constant dict and `DEFAULT_BASE_RATE = 0.1`
- Add `compute_info_gain(event_type, lambda_param, max_gain, default_base_rate)` function: `r = 1 + λ·(-log₂ P(event_type))`, clamped to max 3.0
- Integrate as multiplicative factor in combined weight when `probabilistic=True`
- _Requirements: 3.1, 3.2, 3.3, 3.4, 3.5_
- [x] 7.3 Implement adaptive recency decay
- Add `compute_adaptive_half_life(base_half_life, impact_score, info_gain_factor, market_multiplier, config)` function
- Compute `β_impact`, `β_surprise`, `β_market_reaction` scaling factors per design
- `τ_i = τ_base · (1 + β_impact) · (1 + β_surprise) · (1 + β_market_reaction)`
- When `probabilistic=True`, use adaptive half-life in `recency_weight`; otherwise use fixed
- _Requirements: 5.1, 5.2, 5.3, 5.4, 5.5, 5.6, 5.7_
- [x] 7.4 Implement regime multiplier replacing market context multiplier
- Add `compute_regime_multiplier(returns, volumes, config)` function
- Compute z-scores for return and volume, then `M_regime = 1 + 0.15·|z_r| + 0.10·|z_v|`
- Clamp to [1.0, 2.5]; default to 1.0 when data unavailable or σ=0
- When `probabilistic=True`, use `M_regime` instead of `M_context` in combined weight
- _Requirements: 6.1, 6.2, 6.3, 6.4, 6.5_
- [x] 7.5 Integrate source accuracy factor into `compute_signal_weight`
- Accept optional `source_accuracy_factor` parameter
- When `probabilistic=True`, multiply into combined weight formula
- When `probabilistic=False`, ignore (factor = 1.0)
- _Requirements: 4.2, 4.3_
- [x] 7.6 Update `compute_signal_weight` to branch on `probabilistic` flag
- When `probabilistic=True`: use sigmoid gate × recency (adaptive) × credibility × (1 + novelty) × info_gain × source_accuracy × regime_multiplier
- When `probabilistic=False`: preserve exact current formula (binary gate × recency × credibility × (1 + novelty) × market_context)
- Populate all new optional fields on `SignalWeight` and `WeightedSignal`
- _Requirements: 16.4, 16.5_
- [x] 7.7 Write property test for information gain monotonicity
- **Property 6: Information Gain Monotonicity**
- **Validates: Requirements 3.5**
- [x] 7.8 Write property test for adaptive decay lower bound
- **Property 5: Adaptive Decay Lower Bound**
- **Validates: Requirements 5.7, 17.4**
- [ ] 8. Contradiction upgrade (`services/aggregation/contradiction.py`)
- [x] 8.1 Implement weighted disagreement entropy contradiction
- Compute `f_pos = W_positive / (W_positive + W_negative)` and `f_neg = 1 - f_pos`
- Compute `H_contradiction = -f_pos·log₂(f_pos) - f_neg·log₂(f_neg)`
- Weight by evidence mass: `contradiction_score = H_contradiction · min(1.0, (W_pos + W_neg) / W_threshold)`
- Return 0.0 when only one direction exists
- Preserve existing `ContradictionResult` interface
- When `probabilistic=False`, preserve existing minority/majority ratio behavior
- _Requirements: 15.1, 15.2, 15.3, 15.4, 15.5, 15.6, 15.7_
- [x] 8.2 Write property test for contradiction entropy monotonicity
- **Property 9: Contradiction Entropy Monotonicity**
- **Validates: Requirements 15.7**
- [ ] 9. Trend Assembly upgrades (`services/aggregation/worker.py`)
- [x] 9.1 Integrate Bayesian posterior into trend assembly
- When `probabilistic=True`, call `compute_bayesian_posterior` on merged signals
- Use Bayesian confidence formula for trend confidence: `0.5 × C_bayesian + 0.25 × F_count + 0.25 × C_avg_credibility - P_contradiction`
- Use entropy-based direction: H>0.9→mixed, P_bull>0.65→bullish, P_bull<0.35→bearish, else neutral
- Apply regime-adjusted thresholds from `RegimeClassification`
- Populate new `TrendSummary` fields: `p_bull`, `alpha`, `beta_param`, `bayesian_confidence`, `entropy`, `regime`, `pipeline_mode`
- Store probabilistic outputs in `market_context` JSONB under `"probabilistic"` key
- When `probabilistic=False`, preserve exact current heuristic behavior
- _Requirements: 1.1, 1.2, 8.1, 8.2, 8.3, 8.4, 8.5, 9.1, 9.2, 9.3, 9.4, 9.5, 9.6, 7.8, 16.4, 16.5_
- [x] 9.2 Wire regime detection into the aggregation cycle
- Call `classify_regime` with closing prices and returns for each ticker
- Pass `RegimeClassification` to trend assembly for threshold adjustment
- Default to uncertainty regime when market data is unavailable
- Persist regime classification in JSONB for auditability
- _Requirements: 7.1, 7.2, 7.3, 7.8, 7.9_
- [ ] 10. Macro scoring upgrade (`services/aggregation/interpolation.py`)
- [x] 10.1 Implement multiplicative macro exposure formula
- When `probabilistic=True`, compute `S_macro = severity · (1 - Π_k(1 - w_k · O_k))` instead of linear weighted sum
- Preserve overlap weights: w_geo=0.35, w_supply=0.25, w_commodity=0.25, w_sector=0.15
- Preserve severity mapping and resilience modifier
- When `probabilistic=False`, preserve exact current linear formula
- _Requirements: 10.1, 10.2, 10.3, 10.4, 10.5, 10.6_
- [x] 10.2 Implement conditional macro signal integration
- When `probabilistic=True` and both company and macro signals exist, apply macro as multiplicative modifier: `S_adjusted = S_company · clamp(1 + M_macro · sign_alignment, 0.5, 1.5)`
- When only macro signals exist, fall back to additive behavior with weight 0.3
- When only company signals exist, use modifier = 1.0
- Log macro modifier value per ticker
- When `probabilistic=False`, preserve current additive merge behavior
- _Requirements: 11.1, 11.2, 11.3, 11.4, 11.5_
- [x] 10.3 Write property test for multiplicative macro exposure monotonicity
- **Property 7: Multiplicative Macro Exposure Monotonicity**
- **Validates: Requirements 10.7, 17.5**
- [ ] 11. Competitive signal upgrade (`services/aggregation/signal_propagation.py`)
- [x] 11.1 Implement graph-distance attenuation for competitive signals
- When `probabilistic=True`, compute `S_transfer = S_source · ρ_historical · e^(-d_network)` instead of flat transfer
- Compute graph distance as shortest path in competitor relationship graph (cap at 3)
- Use 90-day rolling Pearson correlation for `ρ_historical`; default to 0.3 (same-sector) or 0.1 (cross-sector) when insufficient data (<30 days)
- Preserve existing relationship strength threshold (R ≥ 0.2) as pre-filter
- When `probabilistic=False`, preserve exact current flat transfer behavior
- _Requirements: 12.1, 12.2, 12.3, 12.4, 12.5, 12.6, 12.7_
- [x] 11.2 Write property test for competitive signal distance attenuation
- **Property 11: Competitive Signal Distance Attenuation**
- **Validates: Requirements 12.7**
- [ ] 12. Projection upgrade (`services/aggregation/projection.py`)
- [x] 12.1 Implement exponentially weighted momentum
- When `probabilistic=True`, compute `M_t = Σ_{k=0}^{K-1} λ^k · ΔS_{t-k}` with λ=0.7, K up to 10
- Normalize by geometric series sum to produce value in [-1, 1]
- Fall back to current heuristic when fewer than 2 historical cycles available
- Compute volatility-scaled momentum: `M_adj = M_t / max(σ_20, 0.01)`, clamped to [-2.0, 2.0]
- When `probabilistic=False`, preserve exact current simple momentum behavior
- _Requirements: 13.1, 13.2, 13.3, 13.4, 13.5, 13.6_
- [x] 12.2 Write property test for exponentially weighted momentum direction
- **Property 10: Exponentially Weighted Momentum Direction**
- **Validates: Requirements 13.6, 17.6**
- [ ] 13. Recommendation upgrade (`services/recommendation/eligibility.py`)
- [x] 13.1 Implement expected value recommendation gate
- When `probabilistic=True`, compute `EV = P_bull · R_up - P_bear · R_down`
- Estimate `R_up = strength · σ_20 · √(horizon_days)` and `R_down = (1 - strength) · σ_20 · √(horizon_days)`
- When EV > threshold (default 0.005), allow recommendation through existing gates
- When EV ≤ threshold, force recommendation to informational mode
- Persist EV in `risk_checks` JSONB of `recommendation_evaluations`
- Populate `expected_value`, `p_bull`, `pipeline_mode` on Recommendation model
- Preserve all existing eligibility gates as additional requirements
- When `probabilistic=False`, skip EV gate entirely
- _Requirements: 14.1, 14.2, 14.3, 14.4, 14.5, 14.6_
- [x] 13.2 Write property test for expected value directional consistency
- **Property 12: Expected Value Directional Consistency**
- **Validates: Requirements 17.8**
- [x] 14. Checkpoint — Verify all pipeline stages compile and existing tests still pass
- Ensure all tests pass, ask the user if questions arise.
- [ ] 15. Integration wiring and feature flag plumbing
- [x] 15.1 Wire feature flag through the aggregation worker entry point
- Read `probabilistic_scoring_enabled` from `risk_configs` at cycle start in `services/aggregation/worker.py`
- Pass flag to `ScoringConfig`, trend assembly, contradiction, macro, competitive, and projection stages
- Log pipeline mode at cycle start
- Ensure flag is read once per cycle (mid-cycle changes take effect next cycle)
- _Requirements: 16.3, 16.6, 16.7_
- [x] 15.2 Wire source accuracy fetch into the scoring pipeline
- At cycle start, batch-fetch source accuracy for all source IDs in the current signal set
- Pass `source_accuracy_factor` to `compute_signal_weight` for each signal
- Handle DB errors gracefully (default to 1.0)
- _Requirements: 4.1, 4.2, 4.3_
- [x] 15.3 Wire regime detection into the aggregation cycle
- Fetch closing prices and returns for each ticker from market data
- Call `classify_regime` and pass result to trend assembly and scoring stages
- Handle missing market data (default to uncertainty regime)
- _Requirements: 7.1, 7.8, 7.9_
- [x] 15.4 Store probabilistic outputs in existing JSONB columns
- Store Bayesian fields in `trend_windows.market_context` JSONB under `"probabilistic"` key
- Store EV fields in `recommendation_evaluations.risk_checks` JSONB
- Store regime classification in trend window JSONB
- _Requirements: 16.2_
- [ ] 16. Numerical stability and edge case hardening
- [x] 16.1 Add input validation and edge case guards across all new functions
- Guard `log₂(0)` in entropy and information gain computations
- Floor `max(σ_20, 0.01)` for momentum volatility scaling
- Default to uncertainty regime when σ values are zero
- Return `M_regime = 1.0` when z-score σ = 0
- Skip signals with NaN weight or sentiment
- Clamp all outputs to documented ranges
- _Requirements: 17.9, 6.4_
- [x] 16.2 Write property test for numerical stability across all formulas
- **Property 14: Numerical Stability Across All Formulas**
- **Validates: Requirements 17.9, 6.4**
- [ ] 17. Unit tests for all new and modified modules
- [x] 17.1 Write unit tests for Bayesian accumulator (`tests/test_bayesian.py`)
- Test uninformative prior (empty signals → P_bull=0.5, α=1, β=1, C=0)
- Test specific sigmoid gate values (x=0.5→0.5, x=0.2→<0.05, x=0.8→>0.95)
- Test entropy direction mapping (H>0.9→mixed, P_bull>0.65→bullish, etc.)
- _Requirements: 1.1, 1.2, 1.3, 1.4, 1.5_
- [x] 17.2 Write unit tests for regime detector (`tests/test_regime.py`)
- Test specific (R, V_r) → expected regime classification
- Test threshold adjustments per regime (panic→0.10, mean_reversion→0.20)
- Test insufficient data fallback to uncertainty
- _Requirements: 7.1, 7.2, 7.3, 7.4, 7.5, 7.6, 7.7, 7.9_
- [x] 17.3 Write unit tests for source accuracy tracker (`tests/test_source_accuracy.py`)
- Test accuracy_factor property: sample_count < 10 → 1.0, else 0.5 + ratio
- Test corrupted data clamping
- _Requirements: 4.1, 4.2, 4.3_
- [x] 17.4 Write unit tests for signal scoring upgrades (`tests/test_signal_math_unit.py`)
- Test info gain clamp (very rare event → factor ≤ 3.0)
- Test default base rate (unknown event type → 0.1)
- Test adaptive decay edge cases (all zeros → τ_base, all max → 6×τ_base)
- Test zero overlap → zero macro impact
- Test max overlap → ≈severity×0.724
- Test macro fallback behaviors (only macro → additive, only company → no modifier)
- Test graph distance cutoff (d>3 → no propagation)
- Test momentum fallback (<2 cycles → heuristic)
- Test EV threshold behavior (EV>0.005→proceed, EV≤0.005→informational)
- Test feature flag behaviors (flag=false→heuristic, flag=true→probabilistic)
- _Requirements: 3.1, 3.4, 5.5, 5.6, 10.3, 10.4, 11.3, 13.3, 14.3, 14.4, 16.4, 16.5_
- [x] 18. Final checkpoint — Ensure all tests pass
- Ensure all tests pass, ask the user if questions arise.
## Notes
- Tasks marked with `*` are optional and can be skipped for faster MVP
- Each task references specific requirements for traceability
- Checkpoints ensure incremental validation after each major phase
- Property tests validate the 14 universal correctness properties from the design document
- Unit tests validate specific examples, edge cases, and integration points
- The design uses Python throughout — no language selection needed
- Migration number is 034 (existing migrations go up to 033)
- All new dataclass fields use optional defaults for backward compatibility
- Feature flag `probabilistic_scoring_enabled` gates every behavioral change