automapper_validation/eval/
answer_lists.rs1use serde::Deserialize;
16use std::collections::{HashMap, HashSet};
17use std::sync::OnceLock;
18
19const EMBEDDED_JSON: &str = include_str!("../../data/answer_code_lists.json");
20
21#[derive(Debug, Deserialize)]
22struct RawFile {
23 lists: HashMap<String, HashMap<String, Vec<String>>>,
24}
25
26#[derive(Debug)]
28pub struct AnswerCodeLists {
29 by_fv: HashMap<String, HashMap<String, HashSet<String>>>,
30}
31
32impl AnswerCodeLists {
33 pub fn from_json(json: &str) -> Result<Self, serde_json::Error> {
34 let raw: RawFile = serde_json::from_str(json)?;
35 let by_fv = raw
36 .lists
37 .into_iter()
38 .map(|(fv, lists)| {
39 let lists = lists
40 .into_iter()
41 .map(|(id, codes)| (id, codes.into_iter().collect()))
42 .collect();
43 (fv, lists)
44 })
45 .collect();
46 Ok(Self { by_fv })
47 }
48
49 pub fn embedded() -> &'static Self {
50 static CELL: OnceLock<AnswerCodeLists> = OnceLock::new();
51 CELL.get_or_init(|| {
52 AnswerCodeLists::from_json(EMBEDDED_JSON)
53 .expect("embedded answer_code_lists.json is malformed")
54 })
55 }
56
57 pub fn codes(&self, fv: &str, list: &str) -> Option<&HashSet<String>> {
60 let fv = if fv == "FV2504" { "FV2510" } else { fv };
61 self.by_fv.get(fv)?.get(list)
62 }
63
64 pub fn holds(&self, fv: &str, list: &str, answer: &str) -> Option<bool> {
66 self.codes(fv, list).map(|codes| codes.contains(answer))
67 }
68}
69
70pub fn is_answer_list_id(value: &str) -> bool {
73 let bytes = value.as_bytes();
74 bytes.len() == 6
75 && matches!(bytes[0], b'E' | b'S' | b'G')
76 && bytes[1] == b'_'
77 && bytes[2..].iter().all(u8::is_ascii_digit)
78}
79
80pub fn answer_path(list_path: &str) -> Option<String> {
85 if let Some(prefix) = list_path.strip_suffix("AJT/1082") {
86 return Some(format!("{prefix}AJT/4465"));
87 }
88 list_path
89 .strip_suffix("C556/1131")
90 .map(|prefix| format!("{prefix}C556/9013"))
91}
92
93#[cfg(test)]
94mod tests {
95 use super::*;
96
97 #[test]
98 fn the_embedded_lists_name_what_the_ebd_document_says() {
99 let lists = AnswerCodeLists::embedded();
100 assert_eq!(lists.holds("FV2610", "S_0066", "ZB5"), Some(true));
101 assert_eq!(lists.holds("FV2610", "S_0066", "ZB4"), Some(false));
102 assert_eq!(lists.holds("FV2610", "S_0065", "ZB4"), Some(true));
103 assert_eq!(lists.holds("FV2504", "S_0066", "ZB5"), Some(true));
105 assert_eq!(lists.holds("FV2610", "S_9999", "ZB5"), None);
106 }
107
108 #[test]
109 fn only_ajt_and_sts_name_a_list() {
110 assert_eq!(answer_path("SG2/AJT/1082").as_deref(), Some("SG2/AJT/4465"));
111 assert_eq!(
112 answer_path("SG4/STS/C556/1131").as_deref(),
113 Some("SG4/STS/C556/9013")
114 );
115 assert_eq!(answer_path("SG26/LIN/1082"), None);
116 assert!(is_answer_list_id("E_0204") && is_answer_list_id("G_0059"));
117 assert!(!is_answer_list_id("Z07") && !is_answer_list_id("E_02041"));
118 }
119}