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}