1use pest::Parser;
2use pest_derive::Parser;
3use thiserror::Error;
4
5#[derive(Parser)]
6#[grammar = "grammar.pest"]
7pub struct Grammar;
8
9#[derive(Debug)]
11pub struct Patient {
12 pub name: String,
13 pub age: u8,
14 pub visits: Vec<Visit>,
15}
16
17#[derive(Debug)]
19pub struct Visit {
20 pub diagnosis: String,
21 pub temperature: f32,
22 pub notes: String,
23}
24
25#[derive(Error, Debug)]
26pub enum ParseError {
27 #[error(transparent)]
28 PestError(#[from] Box<pest::error::Error<Rule>>),
29
30 #[error("Invalid data format: {0}")]
31 DataError(String),
32}
33
34pub fn parse_medical_document(input: &str) -> Result<Vec<Patient>, ParseError> {
36 let pairs =
37 Grammar::parse(Rule::file, input).map_err(|e| ParseError::PestError(Box::new(e)))?;
38 let mut patients = Vec::new();
39
40 for p in pairs {
41 for patient in p.into_inner() {
42 if patient.as_rule() == Rule::patient {
43 patients.push(parse_patient(patient)?);
44 }
45 }
46 }
47
48 Ok(patients)
49}
50
51fn parse_patient(pair: pest::iterators::Pair<Rule>) -> Result<Patient, ParseError> {
53 let mut name = String::new();
54 let mut age = 0;
55 let mut visits = Vec::new();
56
57 for part in pair.into_inner() {
58 match part.as_rule() {
59 Rule::name => name = part.into_inner().as_str().trim().to_string(),
60 Rule::age => {
61 age = part
62 .into_inner()
63 .as_str()
64 .parse()
65 .map_err(|_| ParseError::DataError("Invalid age value".into()))?;
66 }
67 Rule::visit => visits.push(parse_visit(part)?),
68 _ => {}
69 }
70 }
71
72 Ok(Patient { name, age, visits })
73}
74
75fn parse_visit(pair: pest::iterators::Pair<Rule>) -> Result<Visit, ParseError> {
77 let mut diagnosis = String::new();
78 let mut temperature = 0.0;
79 let mut notes = String::new();
80
81 for part in pair.into_inner() {
82 match part.as_rule() {
83 Rule::diagnosis => diagnosis = part.into_inner().as_str().trim().to_string(),
84 Rule::temperature => {
85 temperature = part
86 .into_inner()
87 .as_str()
88 .parse()
89 .map_err(|_| ParseError::DataError("Invalid temperature".into()))?;
90 }
91 Rule::notes => notes = part.into_inner().as_str().trim().to_string(),
92 _ => {}
93 }
94 }
95
96 Ok(Visit {
97 diagnosis,
98 temperature,
99 notes,
100 })
101}
102
103pub fn print_patients(patients: &[Patient]) {
105 for p in patients {
106 println!("{} (age {})", p.name, p.age);
107 for v in &p.visits {
108 println!(" {}, {} °C — {}", v.diagnosis, v.temperature, v.notes);
109 }
110 println!();
111 }
112}
113
114pub fn count_patients(patients: &[Patient]) -> usize {
116 patients.len()
117}
118
119pub fn count_total_visits(patients: &[Patient]) -> usize {
121 patients.iter().map(|p| p.visits.len()).sum()
122}