use crate::prelude::*;
#[cfg(feature = "std")]
use crate::schema::agent::Model;
use crate::schema::namespaces::{codemeta, schema_org};
use crate::util::{LinkedData, StringInterpolation, ToMarkdown};
use bon::Builder;
use core::str::FromStr;
use derive_more::Display;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use serde_repr::{Deserialize_repr, Serialize_repr};
#[derive(Clone, Debug, Default, Deserialize_repr, Display, Serialize_repr, PartialEq, PartialOrd, JsonSchema)]
#[repr(u8)]
#[serde(deny_unknown_fields)]
pub enum Availability {
#[default]
Unavailable = 0,
Restricted = 1,
Unrestricted = 2,
}
#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)]
pub enum Data {
Real(DataDescription),
Synthetic(DataDescription),
#[cfg(feature = "std")]
Model(Box<Model>),
}
#[derive(Clone, Debug, Deserialize, Display, Serialize, JsonSchema)]
pub enum DataReadinessLevel {
#[display("Level E: Hearsay")]
E,
#[display("Level D: Accessible")]
D,
#[display("Level C: Loaded")]
C,
#[display("Level B: Faithful")]
B,
#[display("Level A: Appropriate")]
A,
}
#[derive(Clone, Debug, Deserialize, Display, Serialize, JsonSchema)]
pub enum GeospatialDataProcessingLevel {
#[display("Level 0: Raw")]
L0,
#[display("Level 1A: Annotated")]
L1A,
#[display("Level 1B: Processed Annotated")]
L1B,
#[display("Level 1C: Spectral Variables")]
L1C,
#[display("Level 2: Derived Geophysical")]
L2,
#[display("Level 2A: Derived Surface")]
L2A,
#[display("Level 2B: Processed Derived Surface")]
L2B,
#[display("Level 3: Gridded")]
L3,
#[display("Level 3A: Periodic Summaries")]
L3A,
#[display("Level 4: Model Output")]
L4,
}
#[derive(Clone, Debug, Default, Display, Deserialize, Serialize, JsonSchema)]
#[serde(rename_all = "lowercase")]
pub enum Modality {
#[default]
#[display("text")]
Text,
#[display("audio")]
Audio,
#[display("video")]
Video,
#[display("signal")]
Signal,
#[display("graph")]
Graph,
#[display("image")]
Image,
#[display("pdf")]
Pdf,
}
#[derive(Clone, Debug, Display, Deserialize, Serialize, JsonSchema)]
pub enum Quality {
Raw = 0,
Bronze = 1,
Silver = 2,
Gold = 3,
Platinum = 4,
}
#[derive(Builder, Clone, Debug, Deserialize, Serialize, JsonSchema)]
#[builder(start_fn = init)]
pub struct DataDescription {
#[serde(rename = "@context")]
pub context: Option<DataDescriptionContext>,
#[serde(rename = "@type")]
pub description_type: Option<String>,
pub availability: Option<Availability>,
pub license: Option<String>,
pub modality: Option<Modality>,
pub quality: Option<Quality>,
}
#[derive(Builder, Clone, Debug, Deserialize, Serialize, JsonSchema)]
#[builder(start_fn = init, on(String, into))]
pub struct DataDescriptionContext {
pub(crate) availability: String,
pub(crate) license: String,
pub(crate) modality: String,
pub(crate) quality: String,
}
impl Default for DataDescription {
fn default() -> Self {
DataDescription::init().build()
}
}
impl Default for DataDescriptionContext {
fn default() -> Self {
DataDescriptionContext::init()
.availability(schema_org("DefinedTerm"))
.license(codemeta("softwareVersion"))
.modality(schema_org("DefinedTerm"))
.quality(schema_org("DefinedTerm"))
.build()
}
}
impl ToMarkdown for Data {
fn to_markdown(&self) -> String {
match self {
| Self::Real(description) => format!("- Real{}", description.to_markdown()),
| Self::Synthetic(description) => format!("- Synthetic{}", description.to_markdown()),
#[cfg(feature = "std")]
| Self::Model(model) => match model.as_ref() {
| Model::SLM(details) => format!("- Model\n - Kind: SLM\n{}", details.to_markdown().with_indent(2)),
| Model::LLM(details) => format!("- Model\n - Kind: LLM\n{}", details.to_markdown().with_indent(2)),
},
}
}
}
impl ToMarkdown for DataDescription {
fn to_markdown(&self) -> String {
let lines = [
self.availability.as_ref().map(|value| format!("Availability: {value}")),
self.license.as_ref().map(|value| format!("License: {value}")),
self.modality.as_ref().map(|value| format!("Modality: {value}")),
self.quality.as_ref().map(|value| format!("Quality: {}", value)),
]
.into_iter()
.flatten()
.collect::<Vec<_>>();
if lines.is_empty() {
String::new()
} else {
format!("\n{}", lines.to_markdown().trim_start().with_indent(2))
}
}
}
impl LinkedData for DataDescription {
fn with_context(&self) -> Self {
Self {
context: Some(DataDescriptionContext::default()),
description_type: None,
..self.clone()
}
}
}
impl FromStr for GeospatialDataProcessingLevel {
type Err = String;
fn from_str(value: &str) -> Result<Self, Self::Err> {
match value {
| "LV0" | "L0" | "0" => Ok(Self::L0),
| "LV1A" | "L1A" | "1A" => Ok(Self::L1A),
| "LV1B" | "L1B" | "1B" => Ok(Self::L1B),
| "LV1C" | "L1C" | "1C" => Ok(Self::L1C),
| "LV2" | "L2" | "2" => Ok(Self::L2),
| "LV2A" | "L2A" | "2A" => Ok(Self::L2A),
| "LV2B" | "L2B" | "2B" => Ok(Self::L2B),
| "LV3" | "L3" | "3" => Ok(Self::L3),
| "LV3A" | "L3A" | "3A" => Ok(Self::L3A),
| "LV4" | "L4" | "4" => Ok(Self::L4),
| _ => Err(format!("unknown GeospatialDataProcessingLevel: {value}")),
}
}
}