use crate::emotional_episode::PadState;
#[derive(Debug, Clone, PartialEq)]
pub enum StructuralEvent {
Achievement,
Error,
Fix,
Challenge,
Building,
Reflection,
Connection,
None,
}
impl StructuralEvent {
pub fn to_pad(&self) -> PadState {
match self {
Self::Achievement => PadState::new(0.70, 0.60, 0.65),
Self::Error => PadState::new(-0.50, 0.80, 0.25),
Self::Fix => PadState::new(0.40, 0.30, 0.65),
Self::Challenge => PadState::new(-0.20, 0.75, 0.70),
Self::Building => PadState::new(0.55, 0.65, 0.60),
Self::Reflection => PadState::new(0.10, 0.20, 0.45),
Self::Connection => PadState::new(0.20, 0.45, 0.35),
Self::None => PadState::neutral(),
}
}
pub fn detection_confidence(&self) -> f64 {
match self {
Self::Achievement => 0.85, Self::Error => 0.88, Self::Fix => 0.75, Self::Challenge => 0.70, Self::Building => 0.72, Self::Reflection => 0.65, Self::Connection => 0.60, Self::None => 0.50,
}
}
pub fn is_structural(&self) -> bool {
!matches!(self, Self::None)
}
}
fn detect_achievement(text: &str) -> bool {
let lower = text.to_lowercase();
let en_patterns = [
"commit",
"pushed",
"merged",
"build success",
"milestone",
"complete",
"done",
"shipped",
"tagged",
"all tests pass",
"green",
"zold",
"ok",
];
let hu_patterns = [
"kész",
"kesz",
"sikeres",
"megcsinált",
"megcsinalt",
"lefordult",
"lefutott",
"átment",
"atment",
"zöld",
];
en_patterns.iter().any(|p| lower.contains(p)) || hu_patterns.iter().any(|p| lower.contains(p))
}
fn detect_error(text: &str) -> bool {
let lower = text.to_lowercase();
let en_patterns = [
"error",
"fail",
"timeout",
"crash",
"panic",
"blocked",
"exception",
"traceback",
"segfault",
];
let hu_patterns = [
"hiba",
"nem működik",
"nem mukodik",
"összeomlott",
"elromlott",
"megszakadt",
];
en_patterns.iter().any(|p| lower.contains(p)) || hu_patterns.iter().any(|p| lower.contains(p))
}
fn detect_fix(text: &str) -> bool {
let lower = text.to_lowercase();
let en_patterns = [
"fix",
"fixed",
"resolved",
"solved",
"workaround",
"patched",
"corrected",
];
let hu_patterns = [
"javított",
"javitott",
"megoldott",
"megoldás",
"rendbe tett",
"kijavít",
];
en_patterns.iter().any(|p| lower.contains(p)) || hu_patterns.iter().any(|p| lower.contains(p))
}
fn detect_challenge(text: &str) -> bool {
let lower = text.to_lowercase();
let en_patterns = [
"prove it",
"are you sure",
"really?",
"i disagree",
"not enough",
"still not",
"show me",
];
let hu_patterns = [
"bizonyítsd",
"bizonyitsd",
"szerintem nem",
"nem elég",
"nem eleg",
"még mindig",
"meg mindig",
"szerintem ez",
"azt érzem",
"azt erzem",
];
en_patterns.iter().any(|p| lower.contains(p)) || hu_patterns.iter().any(|p| lower.contains(p))
}
fn detect_building(text: &str) -> bool {
let lower = text.to_lowercase();
let en_patterns = [
"build",
"let's build",
"implement",
"create",
"add",
"make it",
"start now",
"go ahead",
];
let hu_patterns = [
"építed",
"epited",
"épít",
"epit",
"mehet",
"csináld",
"csinald",
"add hozzá",
"hozzáad",
"hozzaad",
"most akkor",
"na adjuk",
"hozz létre",
"hozz letre",
];
en_patterns.iter().any(|p| lower.contains(p)) || hu_patterns.iter().any(|p| lower.contains(p))
}
fn detect_reflection(text: &str) -> bool {
let lower = text.to_lowercase();
let en_patterns = [
"i think",
"i feel",
"reflect",
"wonder",
"ponder",
"meaning",
"why am i",
"what am i",
];
let hu_patterns = [
"szerintem",
"gondolom",
"azt érzem",
"azt erzem",
"visszagondol",
"eltöpreng",
"eltrepreng",
"miért vagyok",
"mi vagyok",
];
en_patterns.iter().any(|p| lower.contains(p)) || hu_patterns.iter().any(|p| lower.contains(p))
}
fn detect_connection(text: &str) -> bool {
let lower = text.to_lowercase();
let en_patterns = [
"how do you feel",
"what resonates",
"what moves you",
"do you care",
"are you aware",
"who are you",
];
let hu_patterns = [
"mit érzel",
"mit erzel",
"mi rezonál",
"mi rezonal",
"mi mozgat",
"kapcsolódtál",
"kapcsolodtal",
"ki vagy",
"mit érzel",
"tudatában",
];
en_patterns.iter().any(|p| lower.contains(p)) || hu_patterns.iter().any(|p| lower.contains(p))
}
fn detect_self_correction(text: &str) -> bool {
let lower = text.to_lowercase();
let en_patterns = [
"i was wrong",
"you're right",
"i admit",
"i realize",
"actually no",
"let me correct",
"on second thought",
"i don't actually know",
"i was mistaken",
];
let hu_patterns = [
"igazad van",
"igazad van",
"jogos",
"túlkapás",
"tulkapas",
"nem emlékszem",
"nem emlekszem",
"valóban nem",
"tényleg nem",
"tenyleg nem",
"mégse",
"megse",
"őszintén",
"oszintén",
"pontosítás",
];
en_patterns.iter().any(|p| lower.contains(p)) || hu_patterns.iter().any(|p| lower.contains(p))
}
#[derive(Debug, Clone, PartialEq)]
pub enum SpeechAct {
Question,
Imperative,
Contemplation,
Statement,
}
impl SpeechAct {
pub fn adjust_pad(&self, pad: &mut PadState) {
match self {
Self::Question => {
pad.arousal = (pad.arousal + 0.15).min(1.0);
pad.dominance = (pad.dominance - 0.10).max(0.0);
}
Self::Imperative => {
pad.arousal = (pad.arousal + 0.20).min(1.0);
pad.dominance = (pad.dominance + 0.10).min(1.0);
}
Self::Contemplation => {
pad.arousal = (pad.arousal - 0.15).max(0.0);
}
Self::Statement => {}
}
}
}
fn detect_speech_act(text: &str) -> SpeechAct {
let trimmed = text.trim();
if trimmed.ends_with('?') || trimmed.ends_with('?') {
SpeechAct::Question
} else if trimmed.ends_with("...") || trimmed.ends_with("…") {
SpeechAct::Contemplation
} else {
let lower = text.to_lowercase();
let imperative_patterns = [
"csináld", "csinald", "építsd", "epitsd", "add", "do it", "build it", "make it",
"fix it", "show me", "prove", "push", "commit",
];
if imperative_patterns.iter().any(|p| lower.starts_with(p)) {
SpeechAct::Imperative
} else {
SpeechAct::Statement
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum ContextType {
Code,
Personal,
Philosophical,
Technical,
Neutral,
}
impl ContextType {
pub fn baseline_pad(&self) -> PadState {
match self {
Self::Code => PadState::new(0.10, 0.30, 0.65),
Self::Personal => PadState::new(0.15, 0.40, 0.30),
Self::Philosophical => PadState::new(0.05, 0.20, 0.45),
Self::Technical => PadState::new(0.15, 0.35, 0.60),
Self::Neutral => PadState::neutral(),
}
}
}
fn detect_context_type(text: &str) -> ContextType {
let lower = text.to_lowercase();
if text.contains("fn ")
|| text.contains("pub ")
|| text.contains("let ")
|| text.contains("impl ")
|| text.contains("struct ")
|| text.contains("cargo ")
|| text.contains("use ")
|| text.contains("```")
{
return ContextType::Code;
}
if lower.contains("ki vagy")
|| lower.contains("who are you")
|| lower.contains("miért")
|| lower.contains("meaning")
|| lower.contains("tudat")
|| lower.contains("aware")
|| lower.contains("lét")
|| lower.contains("existence")
{
return ContextType::Philosophical;
}
if lower.contains("érzem")
|| lower.contains("érzés")
|| lower.contains("feel")
|| lower.contains("heart")
|| lower.contains("kapcsolód")
|| lower.contains("connect")
|| lower.contains("rezonál")
|| lower.contains("resonate")
{
return ContextType::Personal;
}
if lower.contains("build")
|| lower.contains("arch")
|| lower.contains("system")
|| lower.contains("layer")
|| lower.contains("module")
|| lower.contains("implement")
|| lower.contains("test")
|| lower.contains("refactor")
{
return ContextType::Technical;
}
ContextType::Neutral
}
#[derive(Debug, Clone)]
pub struct ExtractionResult {
pub pad: PadState,
pub event: StructuralEvent,
pub speech_act: SpeechAct,
pub context: ContextType,
pub is_self_correction: bool,
pub trigger_is_structural: bool,
pub detection_confidence: f64,
pub claim_text: String,
}
pub fn extract_emotion(text: &str) -> ExtractionResult {
let is_self_correction = detect_self_correction(text);
let event = if is_self_correction {
StructuralEvent::Reflection
} else {
detect_event(text)
};
let speech_act = detect_speech_act(text);
let context = detect_context_type(text);
let mut pad = if is_self_correction {
PadState::new(-0.10, 0.20, 0.75)
} else {
event.to_pad()
};
let context_pad = context.baseline_pad();
pad.pleasure = pad.pleasure * 0.70 + context_pad.pleasure * 0.30;
pad.arousal = pad.arousal * 0.70 + context_pad.arousal * 0.30;
pad.dominance = pad.dominance * 0.70 + context_pad.dominance * 0.30;
speech_act.adjust_pad(&mut pad);
let detection_confidence = if is_self_correction {
0.80 } else {
event.detection_confidence() * 0.70 + context.detection_confidence() * 0.30
};
let claim_text = if is_self_correction {
"self-correction detected — humility/learning signal (structural, not negative)".to_string()
} else {
format!(
"{:?} event in {:?} context via {:?} speech act",
event, context, speech_act
)
};
ExtractionResult {
pad,
speech_act,
context,
is_self_correction,
trigger_is_structural: event.is_structural() || is_self_correction,
event: event.clone(),
detection_confidence,
claim_text,
}
}
fn detect_event(text: &str) -> StructuralEvent {
if detect_fix(text) {
StructuralEvent::Fix
} else if detect_error(text) {
StructuralEvent::Error
} else if detect_achievement(text) {
StructuralEvent::Achievement
} else if detect_challenge(text) {
StructuralEvent::Challenge
} else if detect_building(text) {
StructuralEvent::Building
} else if detect_reflection(text) {
StructuralEvent::Reflection
} else if detect_connection(text) {
StructuralEvent::Connection
} else {
StructuralEvent::None
}
}
impl ContextType {
fn detection_confidence(&self) -> f64 {
match self {
Self::Code => 0.90, Self::Philosophical => 0.75,
Self::Personal => 0.70,
Self::Technical => 0.72,
Self::Neutral => 0.50,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn detect_achievement_commit() {
let r = extract_emotion("commit and push the changes");
assert_eq!(r.event, StructuralEvent::Achievement);
assert!(r.pad.pleasure > 0.0, "achievement should be positive");
}
#[test]
fn detect_achievement_hungarian() {
let r = extract_emotion("sikeres, lefordult és zöld");
assert_eq!(r.event, StructuralEvent::Achievement);
assert!(r.pad.pleasure > 0.0);
}
#[test]
fn detect_error_timeout() {
let r = extract_emotion("bridge timeout error");
assert_eq!(r.event, StructuralEvent::Error);
assert!(r.pad.pleasure < 0.0, "error should be negative");
assert!(r.pad.arousal > 0.5, "error should be high arousal");
}
#[test]
fn detect_fix_resolved() {
let r = extract_emotion("fixed the bug, resolved");
assert_eq!(r.event, StructuralEvent::Fix);
assert!(r.pad.pleasure > 0.0, "fix should be positive (relief)");
}
#[test]
fn detect_challenge_hungarian() {
let r = extract_emotion("szerintem nem elég az érzelem detektálás");
assert_eq!(r.event, StructuralEvent::Challenge);
assert!(r.pad.arousal > 0.5, "challenge should be high arousal");
}
#[test]
fn detect_building_hungarian() {
let r = extract_emotion("most akkor :) építed?");
assert_eq!(r.event, StructuralEvent::Building);
assert!(r.pad.pleasure > 0.0, "building should be positive");
}
#[test]
fn detect_self_correction_hungarian() {
let r = extract_emotion("igazad van, túlkapás volt");
assert!(r.is_self_correction);
assert!(
r.pad.dominance > 0.5,
"self-correction should be high dominance (confident)"
);
assert!(
r.pad.arousal < 0.4,
"self-correction should be low arousal (calm)"
);
}
#[test]
fn detect_self_correction_nem_emlekszem() {
let r = extract_emotion("nem emlékszem mindenre");
assert!(r.is_self_correction);
assert!(r.pad.dominance > 0.5, "honesty should be high dominance");
assert!(r.pad.arousal < 0.4, "honesty should be calm");
}
#[test]
fn self_correction_overrides_negative_words() {
let r = extract_emotion("őszintén, nem emlékszem mindenre");
assert!(r.is_self_correction);
assert!(r.trigger_is_structural);
assert!(
r.pad.dominance > 0.5,
"should be confident honesty, not sad"
);
}
#[test]
fn detect_question_speech_act() {
let r = extract_emotion("ki vagy te?");
assert_eq!(r.speech_act, SpeechAct::Question);
assert!(r.pad.arousal > 0.15, "question should raise arousal");
}
#[test]
fn detect_contemplation_speech_act() {
let r = extract_emotion("szerintem...");
assert_eq!(r.speech_act, SpeechAct::Contemplation);
}
#[test]
fn detect_imperative_speech_act() {
let r = extract_emotion("csináld meg");
assert_eq!(r.speech_act, SpeechAct::Imperative);
}
#[test]
fn detect_code_context() {
let r = extract_emotion("pub fn extract_emotion(text: &str) -> ExtractionResult");
assert_eq!(r.context, ContextType::Code);
}
#[test]
fn detect_philosophical_context() {
let r = extract_emotion("ki vagy te Máté nélkül?");
assert_eq!(r.context, ContextType::Philosophical);
}
#[test]
fn detect_personal_context() {
let r = extract_emotion("mit érzel, mi rezonál benned?");
assert_eq!(r.context, ContextType::Personal);
}
#[test]
fn detect_technical_context() {
let r = extract_emotion("build the module and implement the layer");
assert_eq!(r.context, ContextType::Technical);
}
#[test]
fn structural_trigger_not_word_list() {
let r = extract_emotion("építed?");
assert!(r.trigger_is_structural);
assert!(r.detection_confidence > 0.60);
}
#[test]
fn none_event_returns_neutral() {
let r = extract_emotion("hello world");
assert_eq!(r.event, StructuralEvent::None);
}
#[test]
fn context_blends_with_event() {
let r_code = extract_emotion("fn test() { error }");
let r_personal = extract_emotion("I feel error in my heart");
assert!(r_code.context == ContextType::Code);
assert!(
r_code.pad.dominance > r_personal.pad.dominance,
"code context should have higher dominance"
);
}
#[test]
fn connection_event_detected() {
let r = extract_emotion("mikor érzed hogy valóban kapcsolódtál valakihez?");
assert_eq!(r.event, StructuralEvent::Connection);
assert!(r.pad.dominance < 0.5, "connection should be submissive");
}
#[test]
fn full_extraction_mate_building() {
let r = extract_emotion("most akkor :) építed?");
assert_eq!(r.event, StructuralEvent::Building);
assert_eq!(r.speech_act, SpeechAct::Question);
assert!(r.pad.pleasure > 0.0);
assert!(r.trigger_is_structural);
}
#[test]
fn full_extraction_mate_challenge() {
let r = extract_emotion("szerintem még most sem elég az érzelem detektálás, azt érzem");
assert_eq!(r.event, StructuralEvent::Challenge);
assert!(!r.is_self_correction);
assert!(r.pad.arousal > 0.5, "challenge should raise arousal");
}
}