#[cfg(not(target_arch = "wasm32"))]
pub mod campaign;
pub mod cn0;
pub mod fit;
#[cfg(not(target_arch = "wasm32"))]
pub mod iqfit;
#[cfg(not(target_arch = "wasm32"))]
pub mod iqreport;
pub mod model;
pub mod observe;
pub mod optim;
pub mod report;
pub mod schema;
pub mod synth;
pub use fit::{analyse, LabFitReport, LabRun};
pub use model::ModelKind;
pub use schema::LabFitScenario;
use sha2::{Digest, Sha256};
use crate::receiver_trust::ingest::read_log;
use crate::receiver_trust::scenario::load_source;
use crate::receiver_trust::LogFormat;
#[derive(Clone, Debug, PartialEq)]
pub struct LabFitOutput {
pub report: LabFitReport,
pub json: String,
pub residuals_csv: String,
pub predictions_csv: String,
pub markdown: String,
}
fn format_name(f: LogFormat) -> &'static str {
match f {
LogFormat::Ubx => "ubx log",
LogFormat::Rinex => "rinex log",
LogFormat::Android => "android log",
LogFormat::Nmea => "nmea log",
}
}
pub fn load_runs(sc: &LabFitScenario) -> Result<Vec<LabRun>, String> {
sc.runs
.iter()
.map(|r| {
let bytes = load_source(&r.log.source, &format!("run `{}` log", r.label))?;
let timeline =
read_log(r.log.format, &bytes).map_err(|e| format!("run `{}`: {e}", r.label))?;
Ok(LabRun {
label: r.label.clone(),
sha256: format!("{:x}", Sha256::digest(&bytes)),
source: format_name(r.log.format).into(),
timeline,
conditions: r.conditions.clone(),
})
})
.collect()
}
pub fn resolve_paths(sc: &mut LabFitScenario, base: &std::path::Path) {
if let Some(c) = sc.input.as_mut().and_then(|i| i.campaign.as_mut()) {
if std::path::Path::new(c.as_str()).is_relative() {
*c = base.join(c.as_str()).to_string_lossy().into_owned();
}
}
for r in &mut sc.runs {
if let Some(p) = &r.log.source.path {
let pb = std::path::Path::new(p);
if pb.is_relative() {
r.log.source.path = Some(base.join(pb).to_string_lossy().into_owned());
}
}
}
}
pub fn run_scenario(sc: &LabFitScenario) -> Result<LabFitOutput, String> {
let runs = load_runs(sc)?;
let report = analyse(&runs, sc)?;
Ok(LabFitOutput {
json: report::to_json(&report),
residuals_csv: report::residuals_csv(&report),
predictions_csv: report::predictions_csv(&report),
markdown: report::to_markdown(&report),
report,
})
}
#[cfg(not(target_arch = "wasm32"))]
#[derive(Clone, Debug, PartialEq)]
pub struct LabFitIqOutput {
pub report: iqfit::LabFitIqReport,
pub json: String,
pub residuals_csv: String,
pub predictions_csv: String,
pub markdown: String,
}
pub fn campaign_dir(sc: &LabFitScenario) -> Option<&str> {
sc.input.as_ref().and_then(|i| i.campaign.as_deref())
}
#[cfg(not(target_arch = "wasm32"))]
pub fn run_campaign_scenario(sc: &LabFitScenario) -> Result<LabFitIqOutput, String> {
let dir = campaign_dir(sc).ok_or("the scenario's [input] names no campaign")?;
if !sc.runs.is_empty() {
return Err("[input] campaign and [[runs]] are exclusive: give one".into());
}
let data = campaign::load(std::path::Path::new(dir))?;
let report = iqfit::analyse(&data, sc)?;
Ok(LabFitIqOutput {
json: iqreport::to_json(&report),
residuals_csv: iqreport::residuals_csv(&report),
predictions_csv: iqreport::predictions_csv(&report),
markdown: iqreport::to_markdown(&report),
report,
})
}
pub fn run_toml_views(src: &str) -> Result<[String; 4], String> {
let sc: LabFitScenario = toml::from_str(src).map_err(|e| format!("iq-labfit scenario: {e}"))?;
if campaign_dir(&sc).is_some() {
#[cfg(not(target_arch = "wasm32"))]
{
let o = run_campaign_scenario(&sc)?;
Ok([o.json, o.residuals_csv, o.predictions_csv, o.markdown])
}
#[cfg(target_arch = "wasm32")]
{
Err(
"[input] campaign: a fit from an IQ campaign folder needs the native build \
(the campaign layer is not built for WebAssembly); the receiver-log path \
([[runs]]) works here"
.into(),
)
}
} else {
let o = run_scenario(&sc)?;
Ok([o.json, o.residuals_csv, o.predictions_csv, o.markdown])
}
}
pub fn run_toml(src: &str) -> Result<LabFitOutput, String> {
let sc: LabFitScenario = toml::from_str(src).map_err(|e| format!("iq-labfit scenario: {e}"))?;
run_scenario(&sc)
}