This commit is contained in:
2026-08-16 20:53:36 +01:00
parent 53ab2c96c4
commit 24d5a4e57a
3 changed files with 793 additions and 0 deletions
+171
View File
@@ -0,0 +1,171 @@
//! Type definitions for hagfish data structures.
//!
//! These types represent the PX-Web API contract and internal data models.
//! All public types derive Serialize/Deserialize for JSON (de)serialization.
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
/// Configuration loaded from config.json on startup.
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct Config {
pub duckdb_path: String,
pub bind_address: String,
pub data_source_url: String,
pub log_file_path: Option<String>,
}
impl Default for Config {
fn default() -> Self {
Self {
duckdb_path: "hagfish.db".to_string(),
bind_address: "127.0.0.1:8090".to_string(),
data_source_url: "https://statbank.hagstova.fo/data/api/table/fisknv_md".to_string(),
log_file_path: Some("hagfish.log".to_string()),
}
}
}
/// Metadata response from PX-Web API GET request.
/// Contains variable definitions and value codes with labels.
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct MetadataResponse {
pub variables: Vec<VariableMeta>,
pub values: HashMap<String, Vec<ValueMeta>>,
}
/// Variable metadata describing a dimension in the PX-Web table.
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct VariableMeta {
pub id: String,
pub text: String,
pub role: String,
/// Position in the key array (0-indexed)
#[serde(rename = "keyPosition", skip_serializing_if = "Option::is_none")]
pub key_position: Option<usize>,
}
/// Value metadata: code → display label mapping for a variable.
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct ValueMeta {
pub code: String,
pub text: String,
}
/// Query body sent to PX-Web API POST endpoint.
#[derive(Debug, Clone, Serialize)]
pub struct Query {
pub query: Vec<QueryItem>,
pub language: String,
}
/// Single query item representing a dimension selection.
#[derive(Debug, Clone, Serialize)]
pub struct QueryItem {
pub id: String,
pub values: Vec<String>,
}
/// Selection helper for building queries programmatically.
#[derive(Debug, Clone)]
pub struct Selection {
/// Dimension name (matches variable.id from metadata)
pub dimension: String,
/// Codes to include, or "*" for all
pub codes: Vec<String>,
}
/// Raw data response from PX-Web API POST request.
/// JSON-stat2 format with metadata and data sections.
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct DataResponse {
pub dataset: Dataset,
}
/// Dataset wrapper containing dimensions and actual values.
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct Dataset {
pub dimension: DimInfo,
pub value: Vec<Option<f64>>,
}
/// Dimension metadata describing key layout.
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct DimInfo {
#[serde(rename = "key")]
pub keys: Vec<String>,
pub category: CategoryInfo,
}
/// Category info containing value lists for each dimension.
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct CategoryInfo {
pub label: HashMap<String, Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub index: Option<HashMap<String, Vec<usize>>>,
}
/// Decoded row of landing data with labeled dimensions.
#[derive(Debug, Clone)]
pub struct DataRow {
pub month: String,
pub species_code: String,
pub species_label: String,
pub gear_code: String,
pub gear_label: String,
pub zone_code: String,
pub zone_label: String,
pub processing_code: String,
pub processing_label: String,
pub preservation_code: String,
pub preservation_label: String,
pub shipsize_code: String,
pub shipsize_label: String,
pub measure_code: String,
pub measure_label: String,
pub value: Option<f64>,
}
/// Structured representation of a single landing record.
#[derive(Debug, Clone)]
pub struct Landing {
pub month: String,
pub species_code: String,
pub species_label: String,
pub gear_code: String,
pub zone_code: String,
pub processing_code: String,
pub preservation_code: String,
pub shipsize_code: String,
pub measure_code: String,
pub value: Option<f64>,
}
/// Error types for ingestion module.
#[derive(Debug, thiserror::Error)]
pub enum IngestError {
#[error("HTTP request failed: {0}")]
HttpError(#[from] reqwest::Error),
#[error("JSON parsing failed: {0}")]
JsonError(#[from] serde_json::Error),
#[error("Missing dimension in response: {0}")]
MissingDimension(String),
#[error("Invalid value code: {0}")]
InvalidValueCode(String),
#[error("API returned empty data set")]
EmptyDataset,
#[error("Unicode decode error: {0}")]
UnicodeError(String),
}
/// Lookup map for decoding key arrays into labeled values.
/// Keyed by dimension name, contains code → label mappings.
pub type LookupMap = HashMap<String, HashMap<String, String>>;
/// Result alias using custom error type.
pub type Result<T> = std::result::Result<T, IngestError>;