Skip to main content

lectito/
diagnostics.rs

1use serde::Serialize;
2
3/// Details about how extraction selected, cleaned, and accepted article roots.
4#[derive(Clone, Debug, Default, Serialize, PartialEq)]
5pub struct ExtractionDiagnostics {
6    /// Result of applying a caller-provided `content_selector`.
7    pub content_selector: Option<ContentSelectorDiagnostic>,
8    /// Result of applying a bundled or caller-provided site profile.
9    pub site_rule: Option<SiteRuleDiagnostic>,
10    /// Generic extraction attempts tried after profile and selector handling.
11    pub attempts: Vec<AttemptDiagnostic>,
12    /// Index into `attempts` for the selected generic extraction attempt.
13    pub selected_attempt: Option<usize>,
14    /// Final extraction outcome.
15    pub outcome: ExtractionOutcome,
16}
17
18/// Final status for an extraction report.
19#[derive(Clone, Debug, Default, Serialize, PartialEq)]
20#[serde(rename_all = "snake_case")]
21pub enum ExtractionOutcome {
22    /// Extraction met the configured acceptance thresholds.
23    Accepted,
24    /// Extraction returned the best available attempt below normal thresholds.
25    BestAttempt,
26    /// Extraction found no usable article content.
27    #[default]
28    NoContent,
29}
30
31/// Article output plus diagnostics for the decisions made during extraction.
32#[derive(Clone, Debug, Serialize, PartialEq)]
33pub struct ExtractionReport {
34    /// Extracted article, if any content was accepted.
35    pub article: Option<crate::Article>,
36    /// Root selection, cleanup, and fallback diagnostics.
37    pub diagnostics: ExtractionDiagnostics,
38}
39
40/// Diagnostics for a caller-provided content selector.
41#[derive(Clone, Debug, Serialize, PartialEq)]
42pub struct ContentSelectorDiagnostic {
43    /// CSS selector supplied by the caller.
44    pub selector: String,
45    /// Whether the selector matched at least one node.
46    pub matched: bool,
47    /// Node selected for extraction when a match was usable.
48    pub selected: Option<NodeDiagnostic>,
49}
50
51/// Diagnostics for a matched site-specific extraction rule.
52#[derive(Clone, Debug, Serialize, PartialEq)]
53pub struct SiteRuleDiagnostic {
54    /// Profile or extractor name.
55    pub name: String,
56    /// Where the rule came from.
57    pub source: SiteRuleSource,
58    /// Host and path match that selected this rule.
59    pub matched_by: SiteRuleMatch,
60    /// CSS-like selectors for roots selected by the rule.
61    pub roots: Vec<String>,
62    /// Number of elements removed by the rule before cleanup.
63    pub removals: usize,
64    /// Text length after rule extraction.
65    pub text_len: usize,
66    /// Whether the rule output met acceptance thresholds.
67    pub accepted: bool,
68    /// Reason generic extraction was used after a weak rule result.
69    pub fallback_reason: Option<String>,
70}
71
72/// Source of a site-specific extraction rule.
73#[derive(Clone, Debug, Serialize, PartialEq)]
74#[serde(rename_all = "snake_case")]
75pub enum SiteRuleSource {
76    /// A TOML profile selected the content.
77    DeclarativeProfile,
78    /// A Rust extractor handled the site.
79    CodeExtractor,
80}
81
82/// Host and path rule that matched a page URL.
83#[derive(Clone, Debug, Serialize, PartialEq)]
84pub struct SiteRuleMatch {
85    /// Hostname matched by the rule.
86    pub host: String,
87    /// Optional path prefix required by the rule.
88    pub path_prefix: Option<String>,
89    /// Whether the matching rule is bundled with Lectito.
90    pub bundled: bool,
91}
92
93/// Diagnostics for one generic extraction attempt.
94#[derive(Clone, Debug, Serialize, PartialEq)]
95pub struct AttemptDiagnostic {
96    /// Attempt index in retry order.
97    pub index: usize,
98    /// Cleanup and scoring flags used for this attempt.
99    pub flags: FlagDiagnostic,
100    /// Number of scored candidate roots.
101    pub candidate_count: usize,
102    /// Highest-scoring generic candidate roots.
103    pub candidates: Vec<CandidateDiagnostic>,
104    /// Candidate roots found through article-like entry points.
105    pub entry_points: Vec<CandidateDiagnostic>,
106    /// Root selected for cleanup and serialization.
107    pub selected_root: Option<NodeDiagnostic>,
108    /// Cleanup result for the selected root.
109    pub cleanup: Option<CleanupDiagnostic>,
110    /// Content recovered from hidden or alternate markup before scoring.
111    pub recovery: RecoveryDiagnostic,
112    /// Extracted text length after cleanup.
113    pub text_len: usize,
114    /// Whether this attempt met acceptance thresholds.
115    pub accepted: bool,
116}
117
118/// Generic extraction flags used for an attempt.
119#[derive(Clone, Copy, Debug, Serialize, PartialEq)]
120pub struct FlagDiagnostic {
121    /// Whether unlikely page chrome was stripped before scoring.
122    pub strip_unlikely: bool,
123    /// Whether positive and negative class names affected scoring.
124    pub weight_classes: bool,
125    /// Whether low-value child nodes were removed conditionally.
126    pub clean_conditionally: bool,
127}
128
129/// Diagnostics for one candidate article root.
130#[derive(Clone, Debug, Serialize, PartialEq)]
131pub struct CandidateDiagnostic {
132    /// Candidate node summary.
133    pub node: NodeDiagnostic,
134    /// Readability score assigned to the candidate.
135    pub score: f64,
136    /// How this candidate entered the selection set.
137    pub selected_by: CandidateSelection,
138}
139
140/// Reason a node was considered as an article root.
141#[derive(Clone, Debug, Serialize, PartialEq)]
142#[serde(rename_all = "snake_case")]
143pub enum CandidateSelection {
144    /// The node was found by generic readability scoring.
145    CandidateScoring,
146    /// The node matched an article-like entry point before scoring.
147    EntryPointPreselection,
148}
149
150/// Stable summary of a DOM node used in diagnostics.
151#[derive(Clone, Debug, Serialize, PartialEq)]
152pub struct NodeDiagnostic {
153    /// CSS-like selector for the node.
154    pub selector: String,
155    /// HTML tag name.
156    pub tag: String,
157    /// Element id, if present.
158    pub id: Option<String>,
159    /// Element classes.
160    pub classes: Vec<String>,
161    /// Visible text length under the node.
162    pub text_len: usize,
163    /// Link text ratio under the node.
164    pub link_density: f64,
165}
166
167/// Cleanup measurements for a selected article root.
168#[derive(Clone, Debug, Serialize, PartialEq)]
169pub struct CleanupDiagnostic {
170    /// Root selectors cleaned for output.
171    pub roots: Vec<String>,
172    /// Text length before cleanup.
173    pub text_len_before: usize,
174    /// Text length after cleanup.
175    pub text_len_after: usize,
176    /// Element count before cleanup.
177    pub element_count_before: usize,
178    /// Element count after cleanup.
179    pub element_count_after: usize,
180    /// Number of elements removed during cleanup.
181    pub removed_elements: usize,
182}
183
184/// Content recovery performed before scoring.
185#[derive(Clone, Debug, Default, Serialize, PartialEq)]
186pub struct RecoveryDiagnostic {
187    /// Number of declarative shadow roots flattened into normal markup.
188    pub shadow_roots_flattened: usize,
189    /// Number of mobile-only style rules applied to recover hidden content.
190    pub mobile_rules_applied: usize,
191}