1use crate::emotional_episode::PadState;
18
19#[derive(Debug, Clone, PartialEq)]
23pub enum StructuralEvent {
24 Achievement,
26 Error,
28 Fix,
30 Challenge,
32 Building,
34 Reflection,
36 Connection,
38 None,
40}
41
42impl StructuralEvent {
43 pub fn to_pad(&self) -> PadState {
47 match self {
48 Self::Achievement => PadState::new(0.70, 0.60, 0.65),
50 Self::Error => PadState::new(-0.50, 0.80, 0.25),
52 Self::Fix => PadState::new(0.40, 0.30, 0.65),
54 Self::Challenge => PadState::new(-0.20, 0.75, 0.70),
56 Self::Building => PadState::new(0.55, 0.65, 0.60),
58 Self::Reflection => PadState::new(0.10, 0.20, 0.45),
60 Self::Connection => PadState::new(0.20, 0.45, 0.35),
62 Self::None => PadState::neutral(),
63 }
64 }
65
66 pub fn detection_confidence(&self) -> f64 {
68 match self {
69 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,
77 }
78 }
79
80 pub fn is_structural(&self) -> bool {
82 !matches!(self, Self::None)
83 }
84}
85
86fn detect_achievement(text: &str) -> bool {
90 let lower = text.to_lowercase();
91 let en_patterns = [
92 "commit",
93 "pushed",
94 "merged",
95 "build success",
96 "milestone",
97 "complete",
98 "done",
99 "shipped",
100 "tagged",
101 "all tests pass",
102 "green",
103 "zold",
104 "ok",
105 ];
106 let hu_patterns = [
107 "kész",
108 "kesz",
109 "sikeres",
110 "megcsinált",
111 "megcsinalt",
112 "lefordult",
113 "lefutott",
114 "átment",
115 "atment",
116 "zöld",
117 ];
118 en_patterns.iter().any(|p| lower.contains(p)) || hu_patterns.iter().any(|p| lower.contains(p))
119}
120
121fn detect_error(text: &str) -> bool {
123 let lower = text.to_lowercase();
124 let en_patterns = [
125 "error",
126 "fail",
127 "timeout",
128 "crash",
129 "panic",
130 "blocked",
131 "exception",
132 "traceback",
133 "segfault",
134 ];
135 let hu_patterns = [
136 "hiba",
137 "nem működik",
138 "nem mukodik",
139 "összeomlott",
140 "elromlott",
141 "megszakadt",
142 ];
143 en_patterns.iter().any(|p| lower.contains(p)) || hu_patterns.iter().any(|p| lower.contains(p))
144}
145
146fn detect_fix(text: &str) -> bool {
148 let lower = text.to_lowercase();
149 let en_patterns = [
150 "fix",
151 "fixed",
152 "resolved",
153 "solved",
154 "workaround",
155 "patched",
156 "corrected",
157 ];
158 let hu_patterns = [
159 "javított",
160 "javitott",
161 "megoldott",
162 "megoldás",
163 "rendbe tett",
164 "kijavít",
165 ];
166 en_patterns.iter().any(|p| lower.contains(p)) || hu_patterns.iter().any(|p| lower.contains(p))
167}
168
169fn detect_challenge(text: &str) -> bool {
171 let lower = text.to_lowercase();
172 let en_patterns = [
173 "prove it",
174 "are you sure",
175 "really?",
176 "i disagree",
177 "not enough",
178 "still not",
179 "show me",
180 ];
181 let hu_patterns = [
182 "bizonyítsd",
183 "bizonyitsd",
184 "szerintem nem",
185 "nem elég",
186 "nem eleg",
187 "még mindig",
188 "meg mindig",
189 "szerintem ez",
190 "azt érzem",
191 "azt erzem",
192 ];
193 en_patterns.iter().any(|p| lower.contains(p)) || hu_patterns.iter().any(|p| lower.contains(p))
194}
195
196fn detect_building(text: &str) -> bool {
198 let lower = text.to_lowercase();
199 let en_patterns = [
200 "build",
201 "let's build",
202 "implement",
203 "create",
204 "add",
205 "make it",
206 "start now",
207 "go ahead",
208 ];
209 let hu_patterns = [
210 "építed",
211 "epited",
212 "épít",
213 "epit",
214 "mehet",
215 "csináld",
216 "csinald",
217 "add hozzá",
218 "hozzáad",
219 "hozzaad",
220 "most akkor",
221 "na adjuk",
222 "hozz létre",
223 "hozz letre",
224 ];
225 en_patterns.iter().any(|p| lower.contains(p)) || hu_patterns.iter().any(|p| lower.contains(p))
226}
227
228fn detect_reflection(text: &str) -> bool {
230 let lower = text.to_lowercase();
231 let en_patterns = [
232 "i think",
233 "i feel",
234 "reflect",
235 "wonder",
236 "ponder",
237 "meaning",
238 "why am i",
239 "what am i",
240 ];
241 let hu_patterns = [
242 "szerintem",
243 "gondolom",
244 "azt érzem",
245 "azt erzem",
246 "visszagondol",
247 "eltöpreng",
248 "eltrepreng",
249 "miért vagyok",
250 "mi vagyok",
251 ];
252 en_patterns.iter().any(|p| lower.contains(p)) || hu_patterns.iter().any(|p| lower.contains(p))
253}
254
255fn detect_connection(text: &str) -> bool {
257 let lower = text.to_lowercase();
258 let en_patterns = [
259 "how do you feel",
260 "what resonates",
261 "what moves you",
262 "do you care",
263 "are you aware",
264 "who are you",
265 ];
266 let hu_patterns = [
267 "mit érzel",
268 "mit erzel",
269 "mi rezonál",
270 "mi rezonal",
271 "mi mozgat",
272 "kapcsolódtál",
273 "kapcsolodtal",
274 "ki vagy",
275 "mit érzel",
276 "tudatában",
277 ];
278 en_patterns.iter().any(|p| lower.contains(p)) || hu_patterns.iter().any(|p| lower.contains(p))
279}
280
281fn detect_self_correction(text: &str) -> bool {
290 let lower = text.to_lowercase();
291 let en_patterns = [
292 "i was wrong",
293 "you're right",
294 "i admit",
295 "i realize",
296 "actually no",
297 "let me correct",
298 "on second thought",
299 "i don't actually know",
300 "i was mistaken",
301 ];
302 let hu_patterns = [
303 "igazad van",
304 "igazad van",
305 "jogos",
306 "túlkapás",
307 "tulkapas",
308 "nem emlékszem",
309 "nem emlekszem",
310 "valóban nem",
311 "tényleg nem",
312 "tenyleg nem",
313 "mégse",
314 "megse",
315 "őszintén",
316 "oszintén",
317 "pontosítás",
318 ];
319 en_patterns.iter().any(|p| lower.contains(p)) || hu_patterns.iter().any(|p| lower.contains(p))
320}
321
322#[derive(Debug, Clone, PartialEq)]
325pub enum SpeechAct {
326 Question,
327 Imperative,
328 Contemplation,
329 Statement,
330}
331
332impl SpeechAct {
333 pub fn adjust_pad(&self, pad: &mut PadState) {
335 match self {
336 Self::Question => {
338 pad.arousal = (pad.arousal + 0.15).min(1.0);
339 pad.dominance = (pad.dominance - 0.10).max(0.0);
340 }
341 Self::Imperative => {
343 pad.arousal = (pad.arousal + 0.20).min(1.0);
344 pad.dominance = (pad.dominance + 0.10).min(1.0);
345 }
346 Self::Contemplation => {
348 pad.arousal = (pad.arousal - 0.15).max(0.0);
349 }
350 Self::Statement => {}
352 }
353 }
354}
355
356fn detect_speech_act(text: &str) -> SpeechAct {
357 let trimmed = text.trim();
358 if trimmed.ends_with('?') || trimmed.ends_with('?') {
359 SpeechAct::Question
360 } else if trimmed.ends_with("...") || trimmed.ends_with("…") {
361 SpeechAct::Contemplation
362 } else {
363 let lower = text.to_lowercase();
365 let imperative_patterns = [
366 "csináld", "csinald", "építsd", "epitsd", "add", "do it", "build it", "make it",
367 "fix it", "show me", "prove", "push", "commit",
368 ];
369 if imperative_patterns.iter().any(|p| lower.starts_with(p)) {
370 SpeechAct::Imperative
371 } else {
372 SpeechAct::Statement
373 }
374 }
375}
376
377#[derive(Debug, Clone, PartialEq)]
380pub enum ContextType {
381 Code,
382 Personal,
383 Philosophical,
384 Technical,
385 Neutral,
386}
387
388impl ContextType {
389 pub fn baseline_pad(&self) -> PadState {
391 match self {
392 Self::Code => PadState::new(0.10, 0.30, 0.65),
394 Self::Personal => PadState::new(0.15, 0.40, 0.30),
396 Self::Philosophical => PadState::new(0.05, 0.20, 0.45),
398 Self::Technical => PadState::new(0.15, 0.35, 0.60),
400 Self::Neutral => PadState::neutral(),
401 }
402 }
403}
404
405fn detect_context_type(text: &str) -> ContextType {
406 let lower = text.to_lowercase();
407 if text.contains("fn ")
409 || text.contains("pub ")
410 || text.contains("let ")
411 || text.contains("impl ")
412 || text.contains("struct ")
413 || text.contains("cargo ")
414 || text.contains("use ")
415 || text.contains("```")
416 {
417 return ContextType::Code;
418 }
419 if lower.contains("ki vagy")
421 || lower.contains("who are you")
422 || lower.contains("miért")
423 || lower.contains("meaning")
424 || lower.contains("tudat")
425 || lower.contains("aware")
426 || lower.contains("lét")
427 || lower.contains("existence")
428 {
429 return ContextType::Philosophical;
430 }
431 if lower.contains("érzem")
433 || lower.contains("érzés")
434 || lower.contains("feel")
435 || lower.contains("heart")
436 || lower.contains("kapcsolód")
437 || lower.contains("connect")
438 || lower.contains("rezonál")
439 || lower.contains("resonate")
440 {
441 return ContextType::Personal;
442 }
443 if lower.contains("build")
445 || lower.contains("arch")
446 || lower.contains("system")
447 || lower.contains("layer")
448 || lower.contains("module")
449 || lower.contains("implement")
450 || lower.contains("test")
451 || lower.contains("refactor")
452 {
453 return ContextType::Technical;
454 }
455 ContextType::Neutral
456}
457
458#[derive(Debug, Clone)]
462pub struct ExtractionResult {
463 pub pad: PadState,
464 pub event: StructuralEvent,
465 pub speech_act: SpeechAct,
466 pub context: ContextType,
467 pub is_self_correction: bool,
468 pub trigger_is_structural: bool,
469 pub detection_confidence: f64,
470 pub claim_text: String,
471}
472
473pub fn extract_emotion(text: &str) -> ExtractionResult {
486 let is_self_correction = detect_self_correction(text);
488
489 let event = if is_self_correction {
491 StructuralEvent::Reflection
495 } else {
496 detect_event(text)
497 };
498
499 let speech_act = detect_speech_act(text);
501
502 let context = detect_context_type(text);
504
505 let mut pad = if is_self_correction {
507 PadState::new(-0.10, 0.20, 0.75)
509 } else {
510 event.to_pad()
511 };
512
513 let context_pad = context.baseline_pad();
515 pad.pleasure = pad.pleasure * 0.70 + context_pad.pleasure * 0.30;
516 pad.arousal = pad.arousal * 0.70 + context_pad.arousal * 0.30;
517 pad.dominance = pad.dominance * 0.70 + context_pad.dominance * 0.30;
518
519 speech_act.adjust_pad(&mut pad);
521
522 let detection_confidence = if is_self_correction {
524 0.80 } else {
526 event.detection_confidence() * 0.70 + context.detection_confidence() * 0.30
527 };
528
529 let claim_text = if is_self_correction {
531 "self-correction detected — humility/learning signal (structural, not negative)".to_string()
532 } else {
533 format!(
534 "{:?} event in {:?} context via {:?} speech act",
535 event, context, speech_act
536 )
537 };
538
539 ExtractionResult {
540 pad,
541 speech_act,
542 context,
543 is_self_correction,
544 trigger_is_structural: event.is_structural() || is_self_correction,
545 event: event.clone(),
546 detection_confidence,
547 claim_text,
548 }
549}
550
551fn detect_event(text: &str) -> StructuralEvent {
552 if detect_fix(text) {
554 StructuralEvent::Fix
555 } else if detect_error(text) {
556 StructuralEvent::Error
557 } else if detect_achievement(text) {
558 StructuralEvent::Achievement
559 } else if detect_challenge(text) {
560 StructuralEvent::Challenge
561 } else if detect_building(text) {
562 StructuralEvent::Building
563 } else if detect_reflection(text) {
564 StructuralEvent::Reflection
565 } else if detect_connection(text) {
566 StructuralEvent::Connection
567 } else {
568 StructuralEvent::None
569 }
570}
571
572impl ContextType {
573 fn detection_confidence(&self) -> f64 {
574 match self {
575 Self::Code => 0.90, Self::Philosophical => 0.75,
577 Self::Personal => 0.70,
578 Self::Technical => 0.72,
579 Self::Neutral => 0.50,
580 }
581 }
582}
583
584#[cfg(test)]
587mod tests {
588 use super::*;
589
590 #[test]
591 fn detect_achievement_commit() {
592 let r = extract_emotion("commit and push the changes");
593 assert_eq!(r.event, StructuralEvent::Achievement);
594 assert!(r.pad.pleasure > 0.0, "achievement should be positive");
595 }
596
597 #[test]
598 fn detect_achievement_hungarian() {
599 let r = extract_emotion("sikeres, lefordult és zöld");
600 assert_eq!(r.event, StructuralEvent::Achievement);
601 assert!(r.pad.pleasure > 0.0);
602 }
603
604 #[test]
605 fn detect_error_timeout() {
606 let r = extract_emotion("bridge timeout error");
607 assert_eq!(r.event, StructuralEvent::Error);
608 assert!(r.pad.pleasure < 0.0, "error should be negative");
609 assert!(r.pad.arousal > 0.5, "error should be high arousal");
610 }
611
612 #[test]
613 fn detect_fix_resolved() {
614 let r = extract_emotion("fixed the bug, resolved");
615 assert_eq!(r.event, StructuralEvent::Fix);
616 assert!(r.pad.pleasure > 0.0, "fix should be positive (relief)");
617 }
618
619 #[test]
620 fn detect_challenge_hungarian() {
621 let r = extract_emotion("szerintem nem elég az érzelem detektálás");
622 assert_eq!(r.event, StructuralEvent::Challenge);
623 assert!(r.pad.arousal > 0.5, "challenge should be high arousal");
624 }
625
626 #[test]
627 fn detect_building_hungarian() {
628 let r = extract_emotion("most akkor :) építed?");
629 assert_eq!(r.event, StructuralEvent::Building);
630 assert!(r.pad.pleasure > 0.0, "building should be positive");
631 }
632
633 #[test]
634 fn detect_self_correction_hungarian() {
635 let r = extract_emotion("igazad van, túlkapás volt");
636 assert!(r.is_self_correction);
637 assert!(
639 r.pad.dominance > 0.5,
640 "self-correction should be high dominance (confident)"
641 );
642 assert!(
643 r.pad.arousal < 0.4,
644 "self-correction should be low arousal (calm)"
645 );
646 }
647
648 #[test]
649 fn detect_self_correction_nem_emlekszem() {
650 let r = extract_emotion("nem emlékszem mindenre");
651 assert!(r.is_self_correction);
652 assert!(r.pad.dominance > 0.5, "honesty should be high dominance");
654 assert!(r.pad.arousal < 0.4, "honesty should be calm");
655 }
656
657 #[test]
658 fn self_correction_overrides_negative_words() {
659 let r = extract_emotion("őszintén, nem emlékszem mindenre");
661 assert!(r.is_self_correction);
662 assert!(r.trigger_is_structural);
663 assert!(
665 r.pad.dominance > 0.5,
666 "should be confident honesty, not sad"
667 );
668 }
669
670 #[test]
671 fn detect_question_speech_act() {
672 let r = extract_emotion("ki vagy te?");
673 assert_eq!(r.speech_act, SpeechAct::Question);
674 assert!(r.pad.arousal > 0.15, "question should raise arousal");
676 }
677
678 #[test]
679 fn detect_contemplation_speech_act() {
680 let r = extract_emotion("szerintem...");
681 assert_eq!(r.speech_act, SpeechAct::Contemplation);
682 }
683
684 #[test]
685 fn detect_imperative_speech_act() {
686 let r = extract_emotion("csináld meg");
687 assert_eq!(r.speech_act, SpeechAct::Imperative);
688 }
689
690 #[test]
691 fn detect_code_context() {
692 let r = extract_emotion("pub fn extract_emotion(text: &str) -> ExtractionResult");
693 assert_eq!(r.context, ContextType::Code);
694 }
695
696 #[test]
697 fn detect_philosophical_context() {
698 let r = extract_emotion("ki vagy te Máté nélkül?");
699 assert_eq!(r.context, ContextType::Philosophical);
700 }
701
702 #[test]
703 fn detect_personal_context() {
704 let r = extract_emotion("mit érzel, mi rezonál benned?");
705 assert_eq!(r.context, ContextType::Personal);
706 }
707
708 #[test]
709 fn detect_technical_context() {
710 let r = extract_emotion("build the module and implement the layer");
711 assert_eq!(r.context, ContextType::Technical);
712 }
713
714 #[test]
715 fn structural_trigger_not_word_list() {
716 let r = extract_emotion("építed?");
717 assert!(r.trigger_is_structural);
718 assert!(r.detection_confidence > 0.60);
719 }
720
721 #[test]
722 fn none_event_returns_neutral() {
723 let r = extract_emotion("hello world");
724 assert_eq!(r.event, StructuralEvent::None);
725 }
726
727 #[test]
728 fn context_blends_with_event() {
729 let r_code = extract_emotion("fn test() { error }");
731 let r_personal = extract_emotion("I feel error in my heart");
732
733 assert!(r_code.context == ContextType::Code);
734 assert!(
736 r_code.pad.dominance > r_personal.pad.dominance,
737 "code context should have higher dominance"
738 );
739 }
740
741 #[test]
742 fn connection_event_detected() {
743 let r = extract_emotion("mikor érzed hogy valóban kapcsolódtál valakihez?");
744 assert_eq!(r.event, StructuralEvent::Connection);
745 assert!(r.pad.dominance < 0.5, "connection should be submissive");
746 }
747
748 #[test]
749 fn full_extraction_mate_building() {
750 let r = extract_emotion("most akkor :) építed?");
752 assert_eq!(r.event, StructuralEvent::Building);
753 assert_eq!(r.speech_act, SpeechAct::Question);
754 assert!(r.pad.pleasure > 0.0);
755 assert!(r.trigger_is_structural);
756 }
757
758 #[test]
759 fn full_extraction_mate_challenge() {
760 let r = extract_emotion("szerintem még most sem elég az érzelem detektálás, azt érzem");
762 assert_eq!(r.event, StructuralEvent::Challenge);
763 assert!(!r.is_self_correction);
764 assert!(r.pad.arousal > 0.5, "challenge should raise arousal");
765 }
766}