mod corpus;
mod detect;
mod grounding;
mod lens;
mod llm;
mod pipeline;
mod prompt;
mod store;
mod vocab;
pub(crate) use detect::DetectWindows;
pub(crate) use grounding::build_grounding;
pub(crate) use llm::theologian_llm_call;
pub(crate) use pipeline::run_fast_scan;
pub(crate) use prompt::{
THEOLOGIAN_SYSTEM, build_discovery_prompt, build_session_prompt, parse_selected_lenses,
};
pub(crate) use store::TheologianStore;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum TraditionLens {
Catholic,
Protestant,
Orthodox,
Gnostic,
Lds,
Islam,
Judaism,
Hinduism,
Buddhism,
Confucianism,
SecularPhilosophy,
}
impl TraditionLens {
pub(crate) const ALL: [TraditionLens; 11] = [
TraditionLens::Catholic,
TraditionLens::Protestant,
TraditionLens::Orthodox,
TraditionLens::Gnostic,
TraditionLens::Lds,
TraditionLens::Islam,
TraditionLens::Judaism,
TraditionLens::Hinduism,
TraditionLens::Buddhism,
TraditionLens::Confucianism,
TraditionLens::SecularPhilosophy,
];
pub(crate) fn as_code(&self) -> &'static str {
match self {
TraditionLens::Catholic => "catholic",
TraditionLens::Protestant => "protestant",
TraditionLens::Orthodox => "orthodox",
TraditionLens::Gnostic => "gnostic",
TraditionLens::Lds => "lds",
TraditionLens::Islam => "islam",
TraditionLens::Judaism => "judaism",
TraditionLens::Hinduism => "hinduism",
TraditionLens::Buddhism => "buddhism",
TraditionLens::Confucianism => "confucianism",
TraditionLens::SecularPhilosophy => "secular_philosophy",
}
}
pub(crate) fn from_code(s: &str) -> Option<TraditionLens> {
Some(match s.trim().to_lowercase().as_str() {
"catholic" => TraditionLens::Catholic,
"protestant" => TraditionLens::Protestant,
"orthodox" => TraditionLens::Orthodox,
"gnostic" => TraditionLens::Gnostic,
"lds" => TraditionLens::Lds,
"islam" => TraditionLens::Islam,
"judaism" => TraditionLens::Judaism,
"hinduism" => TraditionLens::Hinduism,
"buddhism" => TraditionLens::Buddhism,
"confucianism" => TraditionLens::Confucianism,
"secular_philosophy" | "secular" => TraditionLens::SecularPhilosophy,
_ => return None,
})
}
pub(crate) fn label(&self) -> &'static str {
match self {
TraditionLens::Catholic => "Catholic",
TraditionLens::Protestant => "Protestant",
TraditionLens::Orthodox => "Orthodox",
TraditionLens::Gnostic => "Gnostic",
TraditionLens::Lds => "LDS",
TraditionLens::Islam => "Islam",
TraditionLens::Judaism => "Judaism",
TraditionLens::Hinduism => "Hinduism",
TraditionLens::Buddhism => "Buddhism",
TraditionLens::Confucianism => "Confucianism",
TraditionLens::SecularPhilosophy => "Secular philosophy",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum SignalType {
MoralInvisibility,
ConsequenceGap,
SacredLevity,
}
impl SignalType {
pub(crate) fn as_code(&self) -> &'static str {
match self {
SignalType::MoralInvisibility => "moral_invisibility",
SignalType::ConsequenceGap => "consequence_gap",
SignalType::SacredLevity => "sacred_levity",
}
}
pub(crate) fn from_code(s: &str) -> Option<SignalType> {
Some(match s {
"moral_invisibility" => SignalType::MoralInvisibility,
"consequence_gap" => SignalType::ConsequenceGap,
"sacred_levity" => SignalType::SacredLevity,
_ => return None,
})
}
pub(crate) fn label(&self) -> &'static str {
match self {
SignalType::MoralInvisibility => "moral invisibility",
SignalType::ConsequenceGap => "consequence gap",
SignalType::SacredLevity => "sacred vocabulary in levity-adjacent context",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum QuestionCategory {
MoralWeight,
Theodicy,
Redemption,
Sacred,
Duty,
ImplicitTheology,
}
impl QuestionCategory {
pub(crate) fn from_number(n: u8) -> Option<QuestionCategory> {
Some(match n {
1 => QuestionCategory::MoralWeight,
2 => QuestionCategory::Theodicy,
3 => QuestionCategory::Redemption,
4 => QuestionCategory::Sacred,
5 => QuestionCategory::Duty,
6 => QuestionCategory::ImplicitTheology,
_ => return None,
})
}
pub(crate) fn number(&self) -> u8 {
match self {
QuestionCategory::MoralWeight => 1,
QuestionCategory::Theodicy => 2,
QuestionCategory::Redemption => 3,
QuestionCategory::Sacred => 4,
QuestionCategory::Duty => 5,
QuestionCategory::ImplicitTheology => 6,
}
}
pub(crate) fn label(&self) -> &'static str {
match self {
QuestionCategory::MoralWeight => "Moral weight and consequence",
QuestionCategory::Theodicy => "Theodicy and innocent suffering",
QuestionCategory::Redemption => "Redemption and transformation",
QuestionCategory::Sacred => "Sacred and transcendent",
QuestionCategory::Duty => "Duty and obligation",
QuestionCategory::ImplicitTheology => "The author's implicit theology",
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub(crate) struct TheologianFinding {
pub signal_type: SignalType,
pub chapter_ord: u32,
pub para_id: String,
pub description: String,
pub suppressed: bool,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn tradition_lens_round_trip_all_eleven() {
assert_eq!(TraditionLens::ALL.len(), 11);
for lens in TraditionLens::ALL {
assert_eq!(TraditionLens::from_code(lens.as_code()), Some(lens));
assert!(!lens.label().is_empty());
}
assert_eq!(TraditionLens::from_code("secular"), Some(TraditionLens::SecularPhilosophy));
assert_eq!(TraditionLens::from_code("nope"), None);
}
#[test]
fn signal_and_category_codes() {
for s in [SignalType::MoralInvisibility, SignalType::ConsequenceGap, SignalType::SacredLevity] {
assert_eq!(SignalType::from_code(s.as_code()), Some(s));
}
for n in 1..=6u8 {
assert_eq!(QuestionCategory::from_number(n).map(|c| c.number()), Some(n));
}
assert_eq!(QuestionCategory::from_number(7), None);
}
}