mod cli;
mod errors;
mod output;
mod pdf;
mod qr;
mod types;
mod verify;
use clap::Parser;
use cli::{Cli, Commands};
use errors::LabscriptError;
use types::{
GenerateResult, Medication, PatientInfo, Prescription, PrescriptionInput, PrescriberInfo,
};
fn main() {
let cli = Cli::parse();
let use_json = cli.json || !output::is_tty();
match run(&cli, use_json) {
Ok(()) => {}
Err(e) => {
if use_json {
output::json::print_error(&e.to_string());
} else {
eprintln!("error: {e}");
}
e.exit();
}
}
}
fn run(cli: &Cli, use_json: bool) -> Result<(), LabscriptError> {
match &cli.command {
Commands::AgentInfo => {
print_agent_info();
Ok(())
}
Commands::Verify { qr_string } => {
let result = verify::verify_qr(qr_string)?;
if use_json {
output::json::print_success(&result);
} else {
output::table::render_verify_result(&result);
}
Ok(())
}
Commands::Generate {
patient_name,
patient_dob,
patient_address,
prescriber_name,
prescriber_credentials,
prescriber_license,
prescriber_address,
drug,
strength,
form,
quantity,
sig,
refills,
diagnosis,
notes,
date,
signature,
output: output_path,
file,
stdin,
} => {
let input = if *stdin {
let raw = std::io::read_to_string(std::io::stdin())?;
serde_json::from_str::<PrescriptionInput>(&raw)?
} else if let Some(file_path) = file {
let raw = std::fs::read_to_string(file_path)?;
serde_json::from_str::<PrescriptionInput>(&raw)?
} else {
let p_name = patient_name.as_ref().ok_or_else(|| {
LabscriptError::Config("--patient-name is required".into())
})?;
let p_dob = patient_dob.as_ref().ok_or_else(|| {
LabscriptError::Config("--patient-dob is required".into())
})?;
let pr_name = prescriber_name.as_ref().ok_or_else(|| {
LabscriptError::Config("--prescriber-name is required".into())
})?;
let d = drug.as_ref().ok_or_else(|| {
LabscriptError::Config("--drug is required".into())
})?;
let s = strength.as_ref().ok_or_else(|| {
LabscriptError::Config("--strength is required".into())
})?;
let q = quantity.ok_or_else(|| {
LabscriptError::Config("--quantity is required".into())
})?;
let directions = sig.as_ref().ok_or_else(|| {
LabscriptError::Config("--sig is required".into())
})?;
PrescriptionInput {
patient: PatientInfo {
name: p_name.clone(),
dob: p_dob.clone(),
address: patient_address.clone(),
},
prescriber: PrescriberInfo {
name: pr_name.clone(),
credentials: prescriber_credentials.clone(),
license: prescriber_license.clone(),
address: prescriber_address.clone(),
},
medications: vec![Medication {
drug: d.clone(),
strength: s.clone(),
form: form.clone(),
quantity: q,
sig: directions.clone(),
refills: *refills,
}],
diagnosis: diagnosis.clone(),
notes: notes.clone(),
date: date.clone(),
}
};
if input.medications.is_empty() {
return Err(LabscriptError::InvalidInput(
"At least one medication is required".into(),
));
}
let rx_date = input
.date
.unwrap_or_else(|| chrono::Local::now().format("%Y-%m-%d").to_string());
let rx_id = uuid::Uuid::new_v4().to_string();
let generated_at = chrono::Utc::now().to_rfc3339_opts(chrono::SecondsFormat::Secs, true);
let mut rx = Prescription {
id: rx_id.clone(),
patient: input.patient,
prescriber: input.prescriber,
medications: input.medications,
diagnosis: input.diagnosis,
notes: input.notes,
date: rx_date,
verification_hash: String::new(),
qr_payload: String::new(),
generated_at: generated_at.clone(),
};
let hash = qr::compute_hash(&rx);
let qr_payload = qr::build_qr_payload(&rx_id, &hash, &generated_at);
rx.verification_hash = hash.clone();
rx.qr_payload = qr_payload;
pdf::generate(&rx, signature.as_deref(), output_path)?;
let result = GenerateResult {
prescription_id: rx_id,
output_file: output_path.clone(),
verification_hash: hash,
patient_name: rx.patient.name.clone(),
prescriber_name: rx.prescriber.name.clone(),
medication_count: rx.medications.len(),
generated_at,
};
if use_json {
output::json::print_success(&result);
} else {
output::table::render_generate_result(&result);
}
Ok(())
}
}
}
fn print_agent_info() {
let info = serde_json::json!({
"name": "labscript",
"version": env!("CARGO_PKG_VERSION"),
"description": "Prescription PDF generator with e-signature and QR verification",
"capabilities": [
"prescription_pdf_generation",
"qr_code_verification",
"e_signature_embedding",
"multi_medication_support",
"json_input",
"cli_input"
],
"input_formats": ["cli-args", "json-file", "json-stdin"],
"output_formats": ["json", "table"],
"commands": {
"generate": "labscript generate --patient-name 'John Doe' --patient-dob 1990-01-01 --prescriber-name 'Dr. Smith' --drug Metformin --strength 500mg --quantity 60 --sig 'Take 1 tablet twice daily' --output rx.pdf",
"generate_json": "cat prescription.json | labscript generate --stdin --output rx.pdf",
"verify": "labscript verify 'labscript:v1:uuid:hash:timestamp'",
"agent-info": "labscript agent-info"
},
"pipeline": "labparse -> labstore -> labassess -> labscript"
});
println!("{}", serde_json::to_string_pretty(&info).unwrap());
}