Kevinshh commited on
Commit
80365e6
·
verified ·
1 Parent(s): 2800f93

Upload 2 files

Browse files
Files changed (2) hide show
  1. __init__.py +58 -0
  2. decision_result.py +225 -0
__init__.py ADDED
@@ -0,0 +1,58 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Schemas Package - Data Structures for Three-Layer Architecture
3
+
4
+ This package contains pure data structures (dataclasses) that define
5
+ the contracts between layers:
6
+
7
+ - analysis_intent.py: Layer 1 → Layer 2 contract
8
+ - decision_result.py: Layer 2 → Layer 3 contract
9
+
10
+ IMPORTANT: No business logic in this package. Only data definitions.
11
+ """
12
+
13
+ from schemas.analysis_intent import (
14
+ AnalysisIntent,
15
+ AnalysisType,
16
+ AnalysisPurpose,
17
+ UserPreferences,
18
+ HardConstraints,
19
+ ExtractedDataSummary,
20
+ )
21
+
22
+ from schemas.decision_result import (
23
+ RegulatoryDecisionResult,
24
+ RefusalResult,
25
+ RefusalSeverity,
26
+ DataQuality,
27
+ DataQualityReport,
28
+ KineticFitSummary,
29
+ PredictionSummary,
30
+ ArrheniusResult,
31
+ TrendTransferResult,
32
+ BatchRankingItem,
33
+ RegulatoryNotes,
34
+ CalculationTrace,
35
+ )
36
+
37
+ __all__ = [
38
+ # Intent structures
39
+ "AnalysisIntent",
40
+ "AnalysisType",
41
+ "AnalysisPurpose",
42
+ "UserPreferences",
43
+ "HardConstraints",
44
+ "ExtractedDataSummary",
45
+ # Decision structures
46
+ "RegulatoryDecisionResult",
47
+ "RefusalResult",
48
+ "RefusalSeverity",
49
+ "DataQuality",
50
+ "DataQualityReport",
51
+ "KineticFitSummary",
52
+ "PredictionSummary",
53
+ "ArrheniusResult",
54
+ "TrendTransferResult",
55
+ "BatchRankingItem",
56
+ "RegulatoryNotes",
57
+ "CalculationTrace",
58
+ ]
decision_result.py ADDED
@@ -0,0 +1,225 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Decision Result Schema - Scientific & Regulatory Decision Layer Output
3
+
4
+ This module defines the structured output of Layer 2 (RegulatoryDecisionEngine).
5
+ Used as input to Layer 3 (Presentation Layer).
6
+
7
+ IMPORTANT: This is a DATA STRUCTURE ONLY. No business logic here.
8
+ """
9
+
10
+ from dataclasses import dataclass, field
11
+ from typing import List, Dict, Optional, Literal, Any
12
+ from enum import Enum
13
+
14
+
15
+ class DataQuality(str, Enum):
16
+ """Data quality assessment levels."""
17
+ SUFFICIENT = "sufficient" # All requirements met
18
+ MARGINAL = "marginal" # Minimum requirements met with caveats
19
+ INSUFFICIENT = "insufficient" # Cannot proceed
20
+
21
+
22
+ class RefusalSeverity(str, Enum):
23
+ """Severity of refusal - affects how Layer 3 presents results."""
24
+ HARD_REFUSAL = "hard_refusal" # No numbers at all, only explanation
25
+ SOFT_WARNING = "soft_warning" # Numbers shown with "reference only" caveat
26
+
27
+
28
+ @dataclass
29
+ class RefusalResult:
30
+ """
31
+ Detailed refusal information when analysis cannot proceed.
32
+
33
+ This ensures "refusal" is a first-class output, not just "return None".
34
+ """
35
+ severity: RefusalSeverity
36
+ reason: str
37
+ regulatory_reference: str # e.g., "ICH Q1E Section 4.2"
38
+ suggestions: List[str] = field(default_factory=list) # How to fix the issue
39
+ missing_data: List[str] = field(default_factory=list) # Specific missing items
40
+
41
+
42
+ @dataclass
43
+ class KineticFitSummary:
44
+ """Summary of a single kinetic fit result."""
45
+ condition_id: str
46
+ model_type: Literal["zero_order", "first_order"]
47
+ k: float
48
+ k_unit: str
49
+ y0: float
50
+ R2: float
51
+ SE_k: float
52
+ n_points: int
53
+ equation: str
54
+ confidence_level: Literal["high", "medium", "low"]
55
+
56
+ # Audit trail
57
+ scipy_method: str = "linregress"
58
+ calculation_timestamp: str = ""
59
+
60
+
61
+ @dataclass
62
+ class PredictionSummary:
63
+ """Summary of a single prediction result."""
64
+ timepoint_months: int
65
+ point_estimate: float
66
+ CI_lower: float
67
+ CI_upper: float
68
+ risk_level: Literal["compliant", "marginal", "non_compliant"]
69
+ specification_limit: float
70
+ margin_to_limit: float # spec_limit - CI_upper
71
+
72
+ # Validity - Added for Refusal Logic
73
+ is_valid: bool = True
74
+ validity_reason: str = "" # Reason if invalid (e.g. "Extrapolation > 2x")
75
+
76
+ def is_compliant(self) -> bool:
77
+ return self.is_valid and self.CI_upper < self.specification_limit
78
+
79
+
80
+ @dataclass
81
+ class ArrheniusResult:
82
+ """Arrhenius acceleration factor calculation result."""
83
+ is_calculated: bool
84
+ reference_batch: Optional[str] = None
85
+ empirical_AF: Optional[float] = None
86
+ theoretical_AF: float = 13.4 # Default for 40°C/25°C
87
+ deviation_percent: Optional[float] = None
88
+ calculation_method: str = ""
89
+
90
+ # Validation
91
+ is_valid: bool = True
92
+ validity_reason: str = ""
93
+
94
+
95
+ @dataclass
96
+ class TrendTransferResult:
97
+ """Cross-formulation trend transfer result."""
98
+ is_applicable: bool
99
+ source_batch: Optional[str] = None
100
+ target_batch: Optional[str] = None
101
+ transferred_k: Optional[float] = None
102
+ confidence_level: Literal["high", "medium", "low", "not_applicable"] = "not_applicable"
103
+ method: str = ""
104
+
105
+
106
+ @dataclass
107
+ class BatchRankingItem:
108
+ """Single item in batch ranking."""
109
+ rank: int
110
+ batch_id: str
111
+ batch_name: str
112
+ score: float
113
+ reason: str
114
+ k_best: Optional[float]
115
+ r2_best: Optional[float]
116
+
117
+ # Evaluation Logic
118
+ data_completeness: Literal["full_trend", "partial", "single_point"] = "partial"
119
+ confidence: Literal["high", "medium", "low"] = "low"
120
+
121
+
122
+
123
+ @dataclass
124
+ class DataQualityReport:
125
+ """
126
+ Detailed data quality assessment.
127
+ Produced early in Layer 2 before any calculations.
128
+ """
129
+ overall_quality: DataQuality
130
+ n_batches: int
131
+ n_conditions: int
132
+ n_total_datapoints: int
133
+
134
+ # Per-condition assessment
135
+ conditions_with_sufficient_data: List[str] = field(default_factory=list)
136
+ conditions_with_insufficient_data: List[str] = field(default_factory=list)
137
+
138
+ # Specific issues
139
+ issues: List[str] = field(default_factory=list)
140
+ warnings: List[str] = field(default_factory=list)
141
+
142
+
143
+ @dataclass
144
+ class RegulatoryNotes:
145
+ """Regulatory compliance notes for the report."""
146
+ applicable_guidelines: List[str] = field(default_factory=lambda: [
147
+ "ICH Q1A(R2)",
148
+ "ICH Q1E",
149
+ "WHO TRS 953"
150
+ ])
151
+ extrapolation_statement: str = ""
152
+ statistical_method_statement: str = ""
153
+ limitations: List[str] = field(default_factory=list)
154
+ disclaimers: List[str] = field(default_factory=list)
155
+
156
+
157
+ @dataclass
158
+ class CalculationTrace:
159
+ """
160
+ Audit trail for all calculations.
161
+ Used for FDA Data Integrity compliance.
162
+ """
163
+ entries: List[Dict[str, Any]] = field(default_factory=list)
164
+
165
+ def add(self, step: str, inputs: Dict, outputs: Dict, method: str):
166
+ self.entries.append({
167
+ "step": step,
168
+ "inputs": inputs,
169
+ "outputs": outputs,
170
+ "method": method
171
+ })
172
+
173
+
174
+ @dataclass
175
+ class RegulatoryDecisionResult:
176
+ """
177
+ Complete output of Layer 2 (Scientific & Regulatory Decision Engine).
178
+
179
+ This is the CONTRACT between Layer 2 and Layer 3.
180
+ Layer 2 produces this; Layer 3 consumes this for presentation.
181
+
182
+ IMPORTANT: Layer 3 must NOT access raw data - only this structure.
183
+ """
184
+ # Decision status
185
+ can_proceed: bool
186
+ refusal: Optional[RefusalResult] = None # Only if can_proceed=False
187
+
188
+ # Data assessment (always provided)
189
+ data_quality: DataQualityReport = field(default_factory=DataQualityReport)
190
+
191
+ # Analysis results (only if can_proceed=True)
192
+ analysis_type_executed: str = ""
193
+ kinetic_fits: Dict[str, KineticFitSummary] = field(default_factory=dict)
194
+ arrhenius: Optional[ArrheniusResult] = None
195
+ trend_transfer: Optional[TrendTransferResult] = None
196
+ predictions: Dict[str, PredictionSummary] = field(default_factory=dict)
197
+
198
+ # Batch ranking (only for batch_comparison type)
199
+ batch_ranking: List[BatchRankingItem] = field(default_factory=list)
200
+
201
+ # Regulatory compliance
202
+ regulatory_notes: RegulatoryNotes = field(default_factory=RegulatoryNotes)
203
+
204
+ # Audit trail (for FDA/EMA inspection)
205
+ calculation_trace: CalculationTrace = field(default_factory=CalculationTrace)
206
+
207
+ # Metadata
208
+ engine_version: str = "2.0.0"
209
+ timestamp: str = ""
210
+
211
+ def get_executive_summary(self) -> str:
212
+ """Generate one-line executive summary for Layer 3."""
213
+ if not self.can_proceed:
214
+ return f"分析无法完成: {self.refusal.reason if self.refusal else '未知原因'}"
215
+
216
+ if self.predictions:
217
+ compliant = sum(1 for p in self.predictions.values() if p.is_compliant())
218
+ total = len(self.predictions)
219
+ return f"预测完成: {compliant}/{total} 时间点符合规格"
220
+
221
+ if self.batch_ranking:
222
+ top = self.batch_ranking[0] if self.batch_ranking else {}
223
+ return f"批次筛选完成: 推荐批次 {top.get('batch_id', 'N/A')}"
224
+
225
+ return "分析完成"