feat: core domain types and Filter predicate

This commit is contained in:
2026-05-18 16:16:08 +02:00
parent 70be6449db
commit 30799ddb0b
2 changed files with 163 additions and 1 deletions
+143 -1
View File
@@ -1 +1,143 @@
// implemented in a later task
use serde::{Deserialize, Serialize};
use std::str::FromStr;
#[derive(Debug, Clone)]
pub struct AlphaIdEntry {
pub alpha_id: String,
pub valid: bool,
pub icd_primary: String, // Field 3, normalized below
pub icd_star: String, // Field 4
pub icd_addon: String, // Field 5 ("!") — NOT ICD exclusion
pub icd_primary2: String, // Field 6
pub orpha: String, // Field 7
pub text: String, // Field 8
}
#[derive(Debug, Clone, Default)]
pub struct IcdMeta {
pub code: String, // normalized, no + * !
pub description: String,
pub chapter: String,
pub group: String,
pub para295: String, // P/O/Z/V
pub para301: String,
pub exotic: bool,
pub ifsg: bool,
pub content: bool,
}
#[derive(Debug, Clone, Serialize)]
pub struct Tags {
pub billable: bool, // para295 == "P"
pub valid: bool, // Alpha-ID field 1
pub chapter: String,
pub exotic: bool,
}
#[derive(Debug, Clone)]
pub struct Filter {
pub billable_only: bool,
pub valid_only: bool,
pub chapters: Option<Vec<String>>,
pub exclude_exotic: bool,
}
impl Default for Filter {
fn default() -> Self {
Filter { billable_only: false, valid_only: false, chapters: None, exclude_exotic: false }
}
}
impl Filter {
pub fn accepts(&self, t: &Tags) -> bool {
if self.billable_only && !t.billable { return false; }
if self.valid_only && !t.valid { return false; }
if self.exclude_exotic && t.exotic { return false; }
if let Some(ch) = &self.chapters {
if !ch.iter().any(|c| c == &t.chapter) { return false; }
}
true
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Mode { C, A }
impl FromStr for Mode {
type Err = String;
fn from_str(s: &str) -> Result<Self, String> {
match s.to_ascii_lowercase().as_str() {
"c" => Ok(Mode::C),
"a" => Ok(Mode::A),
other => Err(format!("unknown mode: {other}")),
}
}
}
#[derive(Debug, Clone)]
pub struct Candidate {
pub icd_code: String,
pub alpha_text: String,
pub segment_idx: usize,
pub lexical: Option<f32>,
pub semantic: Option<f32>,
pub rerank: Option<f32>,
}
#[derive(Debug, Clone, Serialize)]
pub struct Suggestion {
pub icd_code: String,
pub description: String,
pub score: f32,
pub matched_phrase: String,
pub source_segments: Vec<usize>,
pub tags: Tags,
}
#[derive(Debug, Clone, Serialize, Default)]
pub struct Diagnostics {
pub mode: String,
pub degraded: bool,
pub segment_count: usize,
pub ionos_calls: usize,
pub millis: u128,
}
#[derive(Debug, Serialize)]
pub struct SuggestResult {
pub suggestions: Vec<Suggestion>,
pub diagnostics: Diagnostics,
}
#[derive(Debug, Clone, Deserialize)]
pub struct Config {
pub ionos_base_url: String,
pub token_path: String,
pub embed_model: String,
pub rerank_model: String,
pub alpha_id_path: String,
pub claml_path: String,
pub index_dir: String,
pub pool_size: usize,
pub top_k: usize,
}
impl Config {
pub fn load(path: &str) -> Result<Config, AppError> {
let s = std::fs::read_to_string(path)
.map_err(|e| AppError::Io(format!("config {path}: {e}")))?;
toml::from_str(&s).map_err(|e| AppError::Parse(format!("config: {e}")))
}
}
#[derive(Debug, thiserror::Error)]
pub enum AppError {
#[error("io error: {0}")]
Io(String),
#[error("parse error: {0}")]
Parse(String),
#[error("ionos error: {0}")]
Ionos(String),
#[error("config error: {0}")]
Config(String),
}
+20
View File
@@ -0,0 +1,20 @@
use alpha_id::model::{Filter, Tags, Mode};
#[test]
fn filter_billable_only_rejects_non_primary() {
let tags = Tags { billable: false, valid: true, chapter: "I".into(), exotic: false };
let f = Filter { billable_only: true, valid_only: false, chapters: None, exclude_exotic: false };
assert!(!f.accepts(&tags));
}
#[test]
fn filter_default_accepts_everything() {
let tags = Tags { billable: false, valid: false, chapter: "II".into(), exotic: true };
assert!(Filter::default().accepts(&tags));
}
#[test]
fn mode_parses_from_str() {
assert_eq!("a".parse::<Mode>().unwrap(), Mode::A);
assert_eq!("c".parse::<Mode>().unwrap(), Mode::C);
}