mshaiel2004 commited on
Commit
b5fa15a
ยท
verified ยท
1 Parent(s): 7e6d950

Create main.py

Browse files
Files changed (1) hide show
  1. main.py +202 -0
main.py ADDED
@@ -0,0 +1,202 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ import numpy as np
3
+ from fastapi import FastAPI, HTTPException
4
+ from pydantic import BaseModel
5
+ from typing import List, Optional, Dict, Any
6
+
7
+ from core.tensor_bridge import TensorBridge
8
+ from core.amlm import load_amlm_model
9
+ from core.aeg import load_aeg_model
10
+ from core.triage import TriageEngineV2
11
+
12
+ # ==========================================
13
+ # 1. SETUP & CONFIG
14
+ # ==========================================
15
+ app = FastAPI(title="Clario Training Module", version="1.0.0")
16
+
17
+ # Global Model Containers
18
+ MODELS = {}
19
+ DEVICE = "cpu" # Force CPU for Hugging Face Spaces (unless GPU upgraded)
20
+
21
+ # Paths to weights (Uploaded in Phase 5)
22
+ AMLM_PATH = "models/amlm_v5.pth"
23
+ AEG_PATH = "models/aeg_v5.pth"
24
+
25
+ # Activity Format Map (Must match AEG Training Data)
26
+ FORMAT_MAP = [
27
+ "Auditory_Bombardment", "Phoneme_Isolation", "Minimal_Pairs",
28
+ "Syllable_Chaining", "Carrier_Phrases", "Pacing",
29
+ "Shadowing", "Speed_Drills"
30
+ ]
31
+ STRUCTURE_MAP = ["CV", "VC", "CVC", "VCV", "CVCV", "CCV", "VCC", "Complex"]
32
+
33
+ # ==========================================
34
+ # 2. DATA MODELS (Pydantic)
35
+ # ==========================================
36
+ class TensorUpdatePayload(BaseModel):
37
+ current_tensor: Optional[List[List[List[float]]]] = None # 3x64x9 List
38
+ new_results: List[Dict[str, Any]] # From Backend or Practice
39
+
40
+ class UserStats(BaseModel):
41
+ severity: float # 0.0 - 1.0
42
+ fatigue: float # 0.0 - 1.0
43
+ age: float # Normalized 0.0 - 1.0
44
+ delta: float # Velocity (-0.5 to +0.5)
45
+
46
+ class StrategyGeneratePayload(BaseModel):
47
+ current_tensor: List[List[List[float]]] # The raw observed tensor
48
+ user_stats: UserStats
49
+ force_easier: bool = False # The "Frustration Valve"
50
+
51
+ # ==========================================
52
+ # 3. LIFECYCLE EVENTS
53
+ # ==========================================
54
+ @app.on_event("startup")
55
+ async def startup_event():
56
+ print("๐Ÿš€ Initializing Clario Training Module...")
57
+ try:
58
+ # Load Cognitive Core
59
+ MODELS['amlm'] = load_amlm_model(AMLM_PATH, DEVICE)
60
+ MODELS['aeg'] = load_aeg_model(AEG_PATH, DEVICE)
61
+ MODELS['bridge'] = TensorBridge()
62
+ MODELS['triage'] = TriageEngineV2(DEVICE)
63
+ print("โœ… All Systems Operational.")
64
+ except Exception as e:
65
+ print(f"โŒ Startup Failed: {e}")
66
+
67
+ # ==========================================
68
+ # 4. HELPER FUNCTIONS
69
+ # ==========================================
70
+ def _prepare_amlm_input(raw_tensor_list):
71
+ """
72
+ Converts raw 3x64x9 list into Batchx6x64x9 Tensor for AMLM.
73
+ Generates visibility mask based on non-zero entries.
74
+ """
75
+ # 1. Convert to Numpy (3, 64, 9)
76
+ val_map = np.array(raw_tensor_list, dtype=np.float32)
77
+
78
+ # 2. Generate Mask (Assumption: Val > 0 means "Observed Error")
79
+ # Zeros are treated as "Ambiguous/Untested" for the Inference Engine
80
+ vis_map = (val_map > 0.0).astype(np.float32)
81
+
82
+ # 3. Concatenate (6, 64, 9)
83
+ input_numpy = np.concatenate([val_map, vis_map], axis=0)
84
+
85
+ # 4. Add Batch Dim & Convert to Torch
86
+ return torch.FloatTensor(input_numpy).unsqueeze(0).to(DEVICE)
87
+
88
+ # ==========================================
89
+ # 5. ENDPOINTS
90
+ # ==========================================
91
+
92
+ @app.post("/tensor/update")
93
+ async def update_tensor(payload: TensorUpdatePayload):
94
+ """
95
+ THE FAST LOOP: Updates the patient's digital twin with new error data.
96
+ """
97
+ bridge = MODELS['bridge']
98
+
99
+ # Run Bridge Logic
100
+ updated_tensor = bridge.update_tensor(
101
+ payload.current_tensor,
102
+ payload.new_results
103
+ )
104
+
105
+ return {
106
+ "status": "success",
107
+ "message": "Tensor updated successfully.",
108
+ "updated_tensor": updated_tensor
109
+ }
110
+
111
+ @app.post("/strategy/generate")
112
+ async def generate_strategy(payload: StrategyGeneratePayload):
113
+ """
114
+ THE SLOW LOOP: Generates a full therapy session blueprint.
115
+ """
116
+ amlm = MODELS['amlm']
117
+ aeg = MODELS['aeg']
118
+ triage = MODELS['triage']
119
+
120
+ # 1. AMLM Inference (Fill Gaps)
121
+ # We use the raw tensor to infer the full clinical image
122
+ amlm_input = _prepare_amlm_input(payload.current_tensor)
123
+ with torch.no_grad():
124
+ diagnosis_map, _ = amlm(amlm_input)
125
+ # Squeeze batch dim -> (3, 64, 9) Numpy
126
+ diag_np = diagnosis_map[0].cpu().numpy()
127
+
128
+ # 2. Triage (Find Focus)
129
+ all_diagnoses = triage.scan_tensor(diag_np)
130
+
131
+ if not all_diagnoses:
132
+ return {"status": "healthy", "message": "No significant errors detected."}
133
+
134
+ focus_diag = all_diagnoses[0] # Highest priority issue
135
+
136
+ # 3. Prepare Stats (Frustration Valve Logic)
137
+ stats = payload.user_stats
138
+ effective_fatigue = 1.0 if payload.force_easier else stats.fatigue
139
+
140
+ # 4. AEG Input Preparation
141
+ aeg_inputs = triage.prepare_aeg_input(
142
+ focus_diag,
143
+ all_diagnoses,
144
+ [stats.severity, effective_fatigue, stats.age, stats.delta]
145
+ )
146
+
147
+ # 5. AEG Inference (Generate Blueprint)
148
+ with torch.no_grad():
149
+ p_vol, p_diff, p_fmt, p_targ, p_struct, p_forbid, p_dyn = aeg(
150
+ aeg_inputs['error_idx'], aeg_inputs['major_idx'], aeg_inputs['pos_idx'],
151
+ aeg_inputs['cluster'], aeg_inputs['trap'], aeg_inputs['feat'],
152
+ aeg_inputs['flags'], aeg_inputs['stats']
153
+ )
154
+
155
+ # 6. Decode Outputs
156
+ vol = int(torch.clamp(p_vol, 3, 10).item())
157
+
158
+ # Extract Sequences (Slice by volume)
159
+ diff_seq = torch.argmax(p_diff, dim=2)[0].tolist()[:vol]
160
+ # Difficulty is 0-4 internally, map to 1-5 for UI
161
+ diff_seq_ui = [d + 1 for d in diff_seq]
162
+
163
+ fmt_indices = torch.argmax(p_fmt, dim=2)[0].tolist()[:vol]
164
+ fmt_names = [FORMAT_MAP[i] for i in fmt_indices]
165
+
166
+ struct_indices = torch.argmax(p_struct, dim=2)[0].tolist()[:vol]
167
+ struct_names = [STRUCTURE_MAP[i] for i in struct_indices]
168
+
169
+ # Target Phonemes (Multi-hot)
170
+ target_logits = p_targ[0][:vol] # (Vol, 64)
171
+ target_phonemes = []
172
+
173
+ from core.triage import PHONEMES # Helper to map back
174
+
175
+ for step_logits in target_logits:
176
+ # Sigmoid > 0.5 implies active target
177
+ active_indices = (torch.sigmoid(step_logits) > 0.5).nonzero(as_tuple=True)[0].tolist()
178
+ targets = [PHONEMES[i] for i in active_indices]
179
+ if not targets: targets = focus_diag.active_phonemes # Fallback to main targets
180
+ target_phonemes.append(targets)
181
+
182
+ return {
183
+ "status": "generated",
184
+ "focus_diagnosis": {
185
+ "name": focus_diag.error_name,
186
+ "type": focus_diag.major_type,
187
+ "severity": focus_diag.severity
188
+ },
189
+ "blueprint": {
190
+ "volume": vol,
191
+ "plan": [
192
+ {
193
+ "step": i+1,
194
+ "difficulty": diff_seq_ui[i],
195
+ "format": fmt_names[i],
196
+ "structure": struct_names[i],
197
+ "targets": target_phonemes[i]
198
+ }
199
+ for i in range(vol)
200
+ ]
201
+ }
202
+ }