use crate::store::node::Node;
mod checks;
mod heatmap;
mod llm;
mod parse;
mod pipeline;
mod store;
pub(crate) use checks::run_deterministic_checks;
pub(crate) use llm::run_llm_checks;
pub(crate) use parse::{read_archetypes, read_motifs, read_symbols};
pub(crate) use pipeline::{
collect_explicit_motifs, refresh_inventory, run_density_scan, run_full_scan,
};
pub(crate) use store::MythStore;
pub(crate) const MYTH_TYPES: &[(&str, &str, &str)] = &[
("para:myth-symbol", "⊛", "Symbol"),
("para:myth-motif", "∿", "Motif"),
("para:myth-archetype", "⍟", "Archetype"),
];
pub(crate) fn myth_glyph(node: &Node) -> Option<&'static str> {
node.tags.iter().find_map(|t| {
MYTH_TYPES.iter().find(|(tag, _, _)| *tag == t).map(|(_, glyph, _)| *glyph)
})
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum MythValence {
Positive,
Negative,
Ambiguous,
}
impl MythValence {
pub(crate) fn as_code(&self) -> &'static str {
match self {
MythValence::Positive => "positive",
MythValence::Negative => "negative",
MythValence::Ambiguous => "ambiguous",
}
}
pub(crate) fn from_code(s: &str) -> MythValence {
match s.trim().to_lowercase().as_str() {
"positive" => MythValence::Positive,
"negative" => MythValence::Negative,
_ => MythValence::Ambiguous,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) enum ArchetypeRole {
Hero,
Mentor,
ThresholdGuardian,
Herald,
Shapeshifter,
Shadow,
Ally,
Trickster,
Custom(String),
}
impl ArchetypeRole {
pub(crate) fn as_code(&self) -> &str {
match self {
ArchetypeRole::Hero => "hero",
ArchetypeRole::Mentor => "mentor",
ArchetypeRole::ThresholdGuardian => "threshold_guardian",
ArchetypeRole::Herald => "herald",
ArchetypeRole::Shapeshifter => "shapeshifter",
ArchetypeRole::Shadow => "shadow",
ArchetypeRole::Ally => "ally",
ArchetypeRole::Trickster => "trickster",
ArchetypeRole::Custom(s) => s,
}
}
pub(crate) fn from_code(s: &str) -> ArchetypeRole {
match s.trim().to_lowercase().replace([' ', '-'], "_").as_str() {
"hero" => ArchetypeRole::Hero,
"mentor" => ArchetypeRole::Mentor,
"threshold_guardian" => ArchetypeRole::ThresholdGuardian,
"herald" => ArchetypeRole::Herald,
"shapeshifter" => ArchetypeRole::Shapeshifter,
"shadow" => ArchetypeRole::Shadow,
"ally" => ArchetypeRole::Ally,
"trickster" => ArchetypeRole::Trickster,
other => ArchetypeRole::Custom(other.to_string()),
}
}
pub(crate) fn is_vogler(&self) -> bool {
!matches!(self, ArchetypeRole::Custom(_))
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum FindingType {
SymbolInconsistency,
MotifIncomplete,
MotifAbsentFinalAct,
ArchetypeVacant,
ArchetypeAbsent,
ArchetypeRoleUnfulfilled,
}
impl FindingType {
pub(crate) fn as_code(&self) -> &'static str {
match self {
FindingType::SymbolInconsistency => "symbol_inconsistency",
FindingType::MotifIncomplete => "motif_incomplete",
FindingType::MotifAbsentFinalAct => "motif_absent_final_act",
FindingType::ArchetypeVacant => "archetype_vacant",
FindingType::ArchetypeAbsent => "archetype_absent",
FindingType::ArchetypeRoleUnfulfilled => "archetype_role_unfulfilled",
}
}
pub(crate) fn from_code(s: &str) -> Option<FindingType> {
Some(match s {
"symbol_inconsistency" => FindingType::SymbolInconsistency,
"motif_incomplete" => FindingType::MotifIncomplete,
"motif_absent_final_act" => FindingType::MotifAbsentFinalAct,
"archetype_vacant" => FindingType::ArchetypeVacant,
"archetype_absent" => FindingType::ArchetypeAbsent,
"archetype_role_unfulfilled" => FindingType::ArchetypeRoleUnfulfilled,
_ => return None,
})
}
pub(crate) fn label(&self) -> &'static str {
match self {
FindingType::SymbolInconsistency => "symbol inconsistency",
FindingType::MotifIncomplete => "motif incomplete",
FindingType::MotifAbsentFinalAct => "motif absent from final act",
FindingType::ArchetypeVacant => "archetype role vacant",
FindingType::ArchetypeAbsent => "archetype character absent",
FindingType::ArchetypeRoleUnfulfilled => "archetype role unfulfilled",
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub(crate) struct MythSymbol {
pub para_id: String,
pub vocabulary: Vec<String>,
pub meaning: String,
pub valence: MythValence,
pub traditions: Vec<String>,
}
#[derive(Debug, Clone, PartialEq)]
pub(crate) struct MythMotif {
pub para_id: String,
pub name: String,
pub description: String,
pub valence: MythValence,
}
#[derive(Debug, Clone, PartialEq)]
pub(crate) struct MythArchetype {
pub para_id: String,
pub role: ArchetypeRole,
pub character_name: String,
pub function_desc: String,
}
#[derive(Debug, Clone, PartialEq)]
pub(crate) struct MythFinding {
pub finding_type: FindingType,
pub description: String,
pub evidence: Option<String>,
pub entry_para_id: Option<String>,
pub chapter_ord: Option<u32>,
pub suppressed: bool,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn valence_and_role_round_trip() {
for v in [MythValence::Positive, MythValence::Negative, MythValence::Ambiguous] {
assert_eq!(MythValence::from_code(v.as_code()), v);
}
assert_eq!(MythValence::from_code("nonsense"), MythValence::Ambiguous);
for r in [
ArchetypeRole::Hero,
ArchetypeRole::Mentor,
ArchetypeRole::ThresholdGuardian,
ArchetypeRole::Herald,
ArchetypeRole::Shapeshifter,
ArchetypeRole::Shadow,
ArchetypeRole::Ally,
ArchetypeRole::Trickster,
] {
assert_eq!(ArchetypeRole::from_code(r.as_code()), r);
assert!(r.is_vogler());
}
assert_eq!(ArchetypeRole::from_code("Threshold Guardian"), ArchetypeRole::ThresholdGuardian);
let c = ArchetypeRole::from_code("dark oracle");
assert_eq!(c, ArchetypeRole::Custom("dark_oracle".into()));
assert!(!c.is_vogler());
}
#[test]
fn finding_type_codes_and_registry() {
for f in [
FindingType::SymbolInconsistency,
FindingType::MotifIncomplete,
FindingType::MotifAbsentFinalAct,
FindingType::ArchetypeVacant,
FindingType::ArchetypeAbsent,
FindingType::ArchetypeRoleUnfulfilled,
] {
assert_eq!(FindingType::from_code(f.as_code()), Some(f));
assert!(!f.label().is_empty());
}
assert_eq!(MYTH_TYPES.len(), 3);
let glyphs: Vec<&str> = MYTH_TYPES.iter().map(|(_, g, _)| *g).collect();
assert_eq!(glyphs, vec!["⊛", "∿", "⍟"]);
}
}