phase 1 complete

This commit is contained in:
2026-08-16 23:05:29 +01:00
parent 5ec60a71c3
commit 5db5c847b2
3 changed files with 34 additions and 37 deletions
+25 -34
View File
@@ -55,13 +55,9 @@ pub async fn fetch_metadata(client: &Client, url: &str) -> Result<LookupMap> {
.await?; .await?;
if !resp.status().is_success() { if !resp.status().is_success() {
let status = resp.status().as_u16();
let body = resp.text().await.unwrap_or_default(); let body = resp.text().await.unwrap_or_default();
return Err(IngestError::HttpError(reqwest::Error::from( return Err(IngestError::ApiStatus { status, body });
std::io::Error::new(
std::io::ErrorKind::Other,
format!("API returned error: {}", body),
),
)));
} }
let meta: MetadataResponse = resp.json().await?; let meta: MetadataResponse = resp.json().await?;
@@ -194,14 +190,9 @@ pub async fn fetch_data(client: &Client, url: &str, query: &Query) -> Result<Dat
let resp = client.post(url).json(query).send().await?; let resp = client.post(url).json(query).send().await?;
if !resp.status().is_success() { if !resp.status().is_success() {
let status = resp.status().as_u16();
let body = resp.text().await.unwrap_or_default(); let body = resp.text().await.unwrap_or_default();
warn!("API error response: {}", body); return Err(IngestError::ApiStatus { status, body });
return Err(IngestError::HttpError(reqwest::Error::from(
std::io::Error::new(
std::io::ErrorKind::Other,
format!("API returned error: {}", body),
),
)));
} }
let data: DataResponse = resp.json().await?; let data: DataResponse = resp.json().await?;
@@ -323,7 +314,7 @@ pub fn parse_row(
.unwrap_or_else(|| ("".to_string(), "".to_string())) .unwrap_or_else(|| ("".to_string(), "".to_string()))
}; };
let (month_code, month_label) = get(DIM_MONTH); let (month_code, _month_label) = get(DIM_MONTH);
let (species_code, species_label) = get(DIM_SPECIES); let (species_code, species_label) = get(DIM_SPECIES);
let (gear_code, gear_label) = get(DIM_GEAR); let (gear_code, gear_label) = get(DIM_GEAR);
let (zone_code, zone_label) = get(DIM_ZONE); let (zone_code, zone_label) = get(DIM_ZONE);
@@ -451,61 +442,61 @@ mod tests {
}, },
); );
// Dimension order: measure, species, gear, zone, processing, // Dimension order from API: month, species, gear, zone, processing,
// preservation, shipsize, month // preservation, shipsize, measure
// Sizes: 2, 2, 1, 1, 1, 1, 1, 2 = 8 values
DataResponse { DataResponse {
dataset: Dataset { dataset: Dataset {
dimension: DimInfo { dimension: DimInfo {
id: vec![ id: vec![
DIM_MEASURE.to_string(), DIM_MONTH.to_string(),
DIM_SPECIES.to_string(), DIM_SPECIES.to_string(),
DIM_GEAR.to_string(), DIM_GEAR.to_string(),
DIM_ZONE.to_string(), DIM_ZONE.to_string(),
DIM_PROCESSING.to_string(), DIM_PROCESSING.to_string(),
DIM_PRESERVATION.to_string(), DIM_PRESERVATION.to_string(),
DIM_SHIPSIZE.to_string(), DIM_SHIPSIZE.to_string(),
DIM_MONTH.to_string(), DIM_MEASURE.to_string(),
], ],
size: vec![2, 2, 1, 1, 1, 1, 1, 2], size: vec![2, 2, 1, 1, 1, 1, 1, 2],
dimensions, dimensions,
}, },
value: vec![ value: vec![
Some(1234.5), // MASS, Sild, ..., 2015M01 Some(1234.5), // 2015M01, Sild, ..., MASS
Some(2345.6), // MASS, Sild, ..., 2015M02 Some(2345.6), // 2015M01, Sild, ..., VALUE
Some(-1.0), // MASS, Þorskur, ..., 2015M01 (sentinel) Some(-1.0), // 2015M01, Þorskur, ..., MASS (sentinel)
Some(3456.7), // MASS, Þorskur, ..., 2015M02 Some(3456.7), // 2015M01, Þorskur, ..., VALUE
Some(4567.8), // VALUE, Sild, ..., 2015M01 Some(4567.8), // 2015M02, Sild, ..., MASS
Some(5678.9), // VALUE, Sild, ..., 2015M02 Some(5678.9), // 2015M02, Sild, ..., VALUE
None, // VALUE, Þorskur, ..., 2015M01 (null) None, // 2015M02, Þorskur, ..., MASS (null)
Some(6789.0), // VALUE, Þorskur, ..., 2015M02 Some(6789.0), // 2015M02, Þorskur, ..., VALUE
], ],
status: vec![], status: vec![],
}, },
} }
} }
/// Test fixture: mock lookup maps with Faroese labels.
fn mock_lookup_maps() -> LookupMap { fn mock_lookup_maps() -> LookupMap {
let mut maps = LookupMap::new(); let mut maps = LookupMap::new();
maps.insert( maps.insert(
DIM_MONTH.to_string(), "0".to_string(), // Tid / Month
HashMap::from([ HashMap::from([
("2015M01".to_string(), "Januar 2015".to_string()), ("2015M01".to_string(), "Januar 2015".to_string()),
("2015M02".to_string(), "Februar 2015".to_string()), ("2015M02".to_string(), "Februar 2015".to_string()),
]), ]),
); );
maps.insert( maps.insert(
DIM_SPECIES.to_string(), "1".to_string(), // Art / Species
HashMap::from([ HashMap::from([
("148XXXXXXX00000".to_string(), "Sild".to_string()), ("Sild".to_string(), "Sild".to_string()),
("183XXXXXXX00000".to_string(), "Þorskur".to_string()), ("Þorskur".to_string(), "Þorskur".to_string()),
]), ]),
); );
maps.insert( maps.insert(
DIM_MEASURE.to_string(), "7".to_string(), // Måleenhed / Measure
HashMap::from([ HashMap::from([
("MASS".to_string(), "Nøgd".to_string()), ("MASS".to_string(), "Kilo".to_string()),
("VALUE".to_string(), "Virði".to_string()), ("VALUE".to_string(), "Krónur".to_string()),
]), ]),
); );
maps maps
+3
View File
@@ -1,3 +1,6 @@
mod ingest;
mod types;
fn main() { fn main() {
println!("hagfish — not yet wired. Run tests with: cargo test"); println!("hagfish — not yet wired. Run tests with: cargo test");
} }
+6 -3
View File
@@ -251,17 +251,20 @@ pub enum IngestError {
#[error("JSON parsing failed: {0}")] #[error("JSON parsing failed: {0}")]
JsonError(#[from] serde_json::Error), JsonError(#[from] serde_json::Error),
#[error("API returned status {status}: {body}")]
ApiStatus { status: u16, body: String },
#[error("Missing dimension in response: {0}")] #[error("Missing dimension in response: {0}")]
MissingDimension(String), MissingDimension(String),
#[error("Invalid value: {0}")] #[error("Invalid value code: {0}")]
InvalidValueCode(String), InvalidValueCode(String),
#[error("API returned empty data set")] #[error("API returned empty data set")]
EmptyDataset, EmptyDataset,
#[error("Dimension '{0}' not found in metadata")] #[error("Unicode decode error: {0}")]
DimensionNotFound(String), UnicodeError(String),
} }
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------