stygian_plugin/reliability/mod.rs
1//! Extraction reliability scoring
2//!
3//! Computes a 0.0–1.0 reliability score for [`ExtractionResult`] outputs so
4//! fallback chains can optimize for *data quality*, not only fetch success.
5//!
6//! # Score components
7//!
8//! The score is the weighted sum of three sub-scores, clamped to `[0.0, 1.0]`:
9//!
10//! | Sub-score | Default weight | Source |
11//! |------------------------|---------------:|--------------------------------------------------|
12//! | `schema_completeness` | 0.70 | `successful_regions / total_regions` |
13//! | `transformation_success`| 0.30 | `1 − transformation_failure_rate` |
14//! | `retry_penalty` | 0.10 | `retry_count / max_retries` (clamped) |
15//!
16//! `retry_penalty` is **subtracted** from the weighted sum — it represents a
17//! quality discount for results that took many retries to produce.
18//!
19//! # Interpretation bands
20//!
21//! The continuous `overall` score is bucketed into three discrete bands so
22//! callers can branch on a single [`ReliabilityBand`] value:
23//!
24//! | Band | Score range | Interpretation |
25//! |---------|---------------|-------------------------------------------------|
26//! | `High` | `0.85..=1.00` | Production-ready; trust the result. |
27//! | `Medium`| `0.50..0.85` | Partial; treat as best-effort, retry if needed. |
28//! | `Low` | `0.00..0.50` | Unreliable; prefer an alternative fallback. |
29//!
30//! # Selection policy
31//!
32//! The [`ScoreWeightedSelector`] helper ranks a list of `(name, score)`
33//! candidates and picks the highest-scoring one. Callers can apply a custom
34//! [`ScoringWeights`] to tune the importance of each sub-score for their
35//! target class.
36//!
37//! # Example
38//!
39//! ```
40//! use stygian_plugin::domain::{ExtractionResult, IdempotencyKey};
41//! use stygian_plugin::reliability::{ReliabilityScorer, ReliabilityBand};
42//!
43//! let result = ExtractionResult::new(IdempotencyKey::new());
44//! let score = ReliabilityScorer::new().score_extraction(&result, 0);
45//! assert_eq!(score.band, ReliabilityBand::High); // empty template -> vacuously complete
46//! assert!((score.overall - 1.0).abs() < f32::EPSILON);
47//! ```
48
49pub mod score;
50pub mod scorer;
51pub mod selector;
52
53pub use score::{ReliabilityBand, ReliabilityScore};
54pub use scorer::{ReliabilityScorer, ScoringWeights};
55pub use selector::{ScoreWeightedSelector, ScoredCandidate};