use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ProvenanceKind {
ReimplementedFromPaper,
ReimplementedFromPaperAndCode,
UpstreamWeights,
PortedFromCode,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Paper {
pub title: String,
pub authors: Vec<String>,
pub venue: String,
pub year: u16,
pub doi: String,
#[serde(default)]
pub license: Option<String>,
}
impl Paper {
pub fn doi_url(&self) -> String {
format!("https://doi.org/{}", self.doi)
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct UpstreamCode {
pub url: String,
pub license: Option<String>,
pub version: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ArtifactSource {
pub name: String,
pub url: String,
pub sha256: String,
pub size_bytes: u64,
#[serde(default)]
pub license: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Provenance {
pub name: String,
pub kind: ProvenanceKind,
pub paper: Option<Paper>,
pub code: UpstreamCode,
#[serde(default)]
pub artifacts: Vec<ArtifactSource>,
#[serde(default)]
pub notes: String,
}
impl Provenance {
pub fn citation(&self) -> String {
let code = match &self.code.license {
Some(l) => format!("Code: {} ({}, {l})", self.code.url, self.code.version),
None => format!("Code: {} ({})", self.code.url, self.code.version),
};
match &self.paper {
Some(p) => {
let authors = match p.authors.as_slice() {
[] => String::new(),
[one] => one.clone(),
[first, .., last] if p.authors.len() > 2 => format!("{first} … {last}"),
[first, second] => format!("{first} & {second}"),
_ => unreachable!(),
};
format!("{authors} ({}). {}. {}. {}. {code}", p.year, p.title, p.venue, p.doi_url())
}
None => format!("{} (no verified paper). {code}", self.name),
}
}
}
pub trait Attributed {
fn provenance(&self) -> Provenance;
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn citation_names_paper_doi_and_code() {
let p = Provenance {
name: "Kilosort4".into(),
kind: ProvenanceKind::ReimplementedFromPaper,
paper: Some(Paper {
title: "Spike sorting with Kilosort4".into(),
authors: vec!["Pachitariu".into(), "Sridhar".into(), "Pennington".into(), "Stringer".into()],
venue: "Nature Methods".into(),
year: 2024,
doi: "10.1038/s41592-024-02232-7".into(),
license: None,
}),
code: UpstreamCode { url: "https://github.com/MouseLand/Kilosort".into(), license: Some("GPL-3.0".into()), version: "v4.1.3".into() },
artifacts: Vec::new(),
notes: String::new(),
};
let c = p.citation();
assert!(c.starts_with("Pachitariu … Stringer (2024). Spike sorting with Kilosort4. Nature Methods. https://doi.org/10.1038/s41592-024-02232-7."), "{c}");
assert!(c.ends_with("Code: https://github.com/MouseLand/Kilosort (v4.1.3, GPL-3.0)"), "{c}");
}
}