Spaces:
Configuration error
Configuration error
Add lib/advanced-engines.ts
Browse files- lib/advanced-engines.ts +362 -0
lib/advanced-engines.ts
ADDED
|
@@ -0,0 +1,362 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/**
|
| 2 |
+
* βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 3 |
+
* Cross-Case Intelligence & Advanced TOD Engine
|
| 4 |
+
* βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 5 |
+
*/
|
| 6 |
+
|
| 7 |
+
// βββ CROSS-CASE INTELLIGENCE MATCHING βββ
|
| 8 |
+
|
| 9 |
+
export interface CaseSignature {
|
| 10 |
+
id: string;
|
| 11 |
+
caseNumber: string;
|
| 12 |
+
features: {
|
| 13 |
+
mannerOfDeath: string;
|
| 14 |
+
weaponType: string[];
|
| 15 |
+
injuryPattern: string[];
|
| 16 |
+
toxicology: string[];
|
| 17 |
+
location: string;
|
| 18 |
+
timeOfDay: string;
|
| 19 |
+
victimProfile: string;
|
| 20 |
+
moPattern: string[];
|
| 21 |
+
};
|
| 22 |
+
}
|
| 23 |
+
|
| 24 |
+
export interface CaseMatch {
|
| 25 |
+
caseId: string;
|
| 26 |
+
caseNumber: string;
|
| 27 |
+
similarity: number;
|
| 28 |
+
matchingFeatures: string[];
|
| 29 |
+
potentialLink: 'serial' | 'related' | 'similar_mo' | 'coincidental';
|
| 30 |
+
explanation: string;
|
| 31 |
+
}
|
| 32 |
+
|
| 33 |
+
export class CrossCaseEngine {
|
| 34 |
+
// Simulated historical case database
|
| 35 |
+
private historicalCases: CaseSignature[] = [
|
| 36 |
+
{
|
| 37 |
+
id: 'hist-1', caseNumber: 'CASE-2023-0512',
|
| 38 |
+
features: { mannerOfDeath: 'homicide', weaponType: ['ligature', 'blunt'], injuryPattern: ['strangulation', 'head_trauma'], toxicology: ['benzodiazepine'], location: 'industrial', timeOfDay: 'night', victimProfile: 'adult_male', moPattern: ['sedation_then_violence', 'single_attacker'] }
|
| 39 |
+
},
|
| 40 |
+
{
|
| 41 |
+
id: 'hist-2', caseNumber: 'CASE-2023-0298',
|
| 42 |
+
features: { mannerOfDeath: 'homicide', weaponType: ['ligature'], injuryPattern: ['strangulation'], toxicology: ['rohypnol'], location: 'residential', timeOfDay: 'night', victimProfile: 'adult_female', moPattern: ['sedation_then_violence', 'single_attacker'] }
|
| 43 |
+
},
|
| 44 |
+
{
|
| 45 |
+
id: 'hist-3', caseNumber: 'CASE-2022-0891',
|
| 46 |
+
features: { mannerOfDeath: 'homicide', weaponType: ['sharp'], injuryPattern: ['stab_wounds'], toxicology: [], location: 'outdoor', timeOfDay: 'evening', victimProfile: 'adult_male', moPattern: ['confrontation', 'multiple_attackers'] }
|
| 47 |
+
},
|
| 48 |
+
{
|
| 49 |
+
id: 'hist-4', caseNumber: 'CASE-2024-0103',
|
| 50 |
+
features: { mannerOfDeath: 'homicide', weaponType: ['blunt', 'ligature'], injuryPattern: ['head_trauma', 'strangulation', 'defensive_wounds'], toxicology: ['diazepam'], location: 'commercial', timeOfDay: 'night', victimProfile: 'adult_male', moPattern: ['sedation_then_violence', 'abandoned_location', 'single_attacker'] }
|
| 51 |
+
},
|
| 52 |
+
];
|
| 53 |
+
|
| 54 |
+
matchCase(currentCase: CaseSignature): CaseMatch[] {
|
| 55 |
+
const matches: CaseMatch[] = [];
|
| 56 |
+
|
| 57 |
+
for (const historic of this.historicalCases) {
|
| 58 |
+
const matchingFeatures: string[] = [];
|
| 59 |
+
let score = 0;
|
| 60 |
+
let maxScore = 0;
|
| 61 |
+
|
| 62 |
+
// Manner of death (weight: 2)
|
| 63 |
+
maxScore += 2;
|
| 64 |
+
if (historic.features.mannerOfDeath === currentCase.features.mannerOfDeath) {
|
| 65 |
+
score += 2;
|
| 66 |
+
matchingFeatures.push('manner_of_death');
|
| 67 |
+
}
|
| 68 |
+
|
| 69 |
+
// Weapon overlap (weight: 3)
|
| 70 |
+
maxScore += 3;
|
| 71 |
+
const weaponOverlap = currentCase.features.weaponType.filter(w => historic.features.weaponType.includes(w));
|
| 72 |
+
if (weaponOverlap.length > 0) {
|
| 73 |
+
score += Math.min(3, weaponOverlap.length * 1.5);
|
| 74 |
+
matchingFeatures.push(`weapons: ${weaponOverlap.join(', ')}`);
|
| 75 |
+
}
|
| 76 |
+
|
| 77 |
+
// Injury pattern (weight: 3)
|
| 78 |
+
maxScore += 3;
|
| 79 |
+
const injuryOverlap = currentCase.features.injuryPattern.filter(i => historic.features.injuryPattern.includes(i));
|
| 80 |
+
if (injuryOverlap.length > 0) {
|
| 81 |
+
score += Math.min(3, injuryOverlap.length * 1.5);
|
| 82 |
+
matchingFeatures.push(`injuries: ${injuryOverlap.join(', ')}`);
|
| 83 |
+
}
|
| 84 |
+
|
| 85 |
+
// Toxicology (weight: 2)
|
| 86 |
+
maxScore += 2;
|
| 87 |
+
const toxOverlap = currentCase.features.toxicology.filter(t => historic.features.toxicology.includes(t));
|
| 88 |
+
if (toxOverlap.length > 0) {
|
| 89 |
+
score += 2;
|
| 90 |
+
matchingFeatures.push(`toxicology: ${toxOverlap.join(', ')}`);
|
| 91 |
+
}
|
| 92 |
+
|
| 93 |
+
// MO pattern (weight: 4 β most important)
|
| 94 |
+
maxScore += 4;
|
| 95 |
+
const moOverlap = currentCase.features.moPattern.filter(m => historic.features.moPattern.includes(m));
|
| 96 |
+
if (moOverlap.length > 0) {
|
| 97 |
+
score += Math.min(4, moOverlap.length * 2);
|
| 98 |
+
matchingFeatures.push(`MO: ${moOverlap.join(', ')}`);
|
| 99 |
+
}
|
| 100 |
+
|
| 101 |
+
// Time of day (weight: 1)
|
| 102 |
+
maxScore += 1;
|
| 103 |
+
if (historic.features.timeOfDay === currentCase.features.timeOfDay) {
|
| 104 |
+
score += 1;
|
| 105 |
+
matchingFeatures.push('time_of_day');
|
| 106 |
+
}
|
| 107 |
+
|
| 108 |
+
// Victim profile (weight: 1)
|
| 109 |
+
maxScore += 1;
|
| 110 |
+
if (historic.features.victimProfile === currentCase.features.victimProfile) {
|
| 111 |
+
score += 1;
|
| 112 |
+
matchingFeatures.push('victim_profile');
|
| 113 |
+
}
|
| 114 |
+
|
| 115 |
+
const similarity = score / maxScore;
|
| 116 |
+
|
| 117 |
+
if (similarity > 0.4) {
|
| 118 |
+
let potentialLink: CaseMatch['potentialLink'] = 'coincidental';
|
| 119 |
+
if (similarity > 0.8) potentialLink = 'serial';
|
| 120 |
+
else if (similarity > 0.65) potentialLink = 'related';
|
| 121 |
+
else if (similarity > 0.5) potentialLink = 'similar_mo';
|
| 122 |
+
|
| 123 |
+
matches.push({
|
| 124 |
+
caseId: historic.id,
|
| 125 |
+
caseNumber: historic.caseNumber,
|
| 126 |
+
similarity: Math.round(similarity * 100) / 100,
|
| 127 |
+
matchingFeatures,
|
| 128 |
+
potentialLink,
|
| 129 |
+
explanation: this.generateMatchExplanation(similarity, matchingFeatures, potentialLink)
|
| 130 |
+
});
|
| 131 |
+
}
|
| 132 |
+
}
|
| 133 |
+
|
| 134 |
+
return matches.sort((a, b) => b.similarity - a.similarity);
|
| 135 |
+
}
|
| 136 |
+
|
| 137 |
+
private generateMatchExplanation(similarity: number, features: string[], link: string): string {
|
| 138 |
+
if (link === 'serial') {
|
| 139 |
+
return `β οΈ SERIAL PATTERN DETECTED (${(similarity * 100).toFixed(0)}% match). Matching features: ${features.join('; ')}. Recommend immediate cross-referencing with cold case unit.`;
|
| 140 |
+
}
|
| 141 |
+
if (link === 'related') {
|
| 142 |
+
return `Strong similarity (${(similarity * 100).toFixed(0)}%). Cases share: ${features.join('; ')}. May indicate same perpetrator or organized pattern.`;
|
| 143 |
+
}
|
| 144 |
+
return `Moderate similarity (${(similarity * 100).toFixed(0)}%). Shared characteristics: ${features.join('; ')}.`;
|
| 145 |
+
}
|
| 146 |
+
}
|
| 147 |
+
|
| 148 |
+
// βββ DUAL-MODE TIME-OF-DEATH ENGINE βββ
|
| 149 |
+
|
| 150 |
+
export interface DualTodResult {
|
| 151 |
+
earlyPhase: {
|
| 152 |
+
method: string;
|
| 153 |
+
pmiHours: number;
|
| 154 |
+
lowerBound: number;
|
| 155 |
+
upperBound: number;
|
| 156 |
+
confidence: string;
|
| 157 |
+
indicators: { name: string; value: string; contribution: string }[];
|
| 158 |
+
};
|
| 159 |
+
latePhase: {
|
| 160 |
+
method: string;
|
| 161 |
+
pmiHours: number | null;
|
| 162 |
+
indicators: { name: string; value: string; contribution: string }[];
|
| 163 |
+
applicable: boolean;
|
| 164 |
+
};
|
| 165 |
+
combined: {
|
| 166 |
+
bestEstimate: number;
|
| 167 |
+
range: string;
|
| 168 |
+
confidence: string;
|
| 169 |
+
methodology: string;
|
| 170 |
+
};
|
| 171 |
+
coolingCurve: { time: number; temp: number }[];
|
| 172 |
+
}
|
| 173 |
+
|
| 174 |
+
export class DualModeTodEngine {
|
| 175 |
+
private T_INITIAL = 37.2;
|
| 176 |
+
|
| 177 |
+
estimate(params: {
|
| 178 |
+
rectalTemp: number;
|
| 179 |
+
ambientTemp: number;
|
| 180 |
+
bodyWeight: number;
|
| 181 |
+
correctiveFactor: number;
|
| 182 |
+
rigorMortis: string;
|
| 183 |
+
lividity: string;
|
| 184 |
+
decomposition: string;
|
| 185 |
+
vitreousPotassium?: number; // mEq/L
|
| 186 |
+
bodyCondition?: string;
|
| 187 |
+
}): DualTodResult {
|
| 188 |
+
|
| 189 |
+
// βββ EARLY PHASE (0-72h): Henssge + Physical Signs βββ
|
| 190 |
+
const effectiveWeight = params.correctiveFactor * params.bodyWeight;
|
| 191 |
+
const B = 1.2815 * Math.pow(effectiveWeight, -0.625) + 0.0284;
|
| 192 |
+
const Q = (params.rectalTemp - params.ambientTemp) / (this.T_INITIAL - params.ambientTemp);
|
| 193 |
+
|
| 194 |
+
let henssePMI = 0;
|
| 195 |
+
if (Q > 0 && Q < 1) {
|
| 196 |
+
let t = 10;
|
| 197 |
+
for (let i = 0; i < 100; i++) {
|
| 198 |
+
const f = 1.25 * Math.exp(-B * t) - 0.25 * Math.exp(-5 * B * t) - Q;
|
| 199 |
+
const fp = -1.25 * B * Math.exp(-B * t) + 1.25 * B * Math.exp(-5 * B * t);
|
| 200 |
+
if (Math.abs(fp) < 1e-12) break;
|
| 201 |
+
t = t - f / fp;
|
| 202 |
+
if (Math.abs(f) < 0.0001) break;
|
| 203 |
+
}
|
| 204 |
+
henssePMI = Math.max(0, t);
|
| 205 |
+
}
|
| 206 |
+
|
| 207 |
+
// Vitreous potassium (if available)
|
| 208 |
+
let potassiumPMI: number | null = null;
|
| 209 |
+
if (params.vitreousPotassium) {
|
| 210 |
+
// Sturner formula: PMI (hours) = 7.14 * [K+] - 39.1
|
| 211 |
+
potassiumPMI = 7.14 * params.vitreousPotassium - 39.1;
|
| 212 |
+
}
|
| 213 |
+
|
| 214 |
+
// Rigor/lividity ranges
|
| 215 |
+
const rigorRanges: Record<string, [number, number]> = {
|
| 216 |
+
absent: [0, 3], developing: [2, 8], full: [8, 24], resolving: [24, 72]
|
| 217 |
+
};
|
| 218 |
+
const lividityRanges: Record<string, [number, number]> = {
|
| 219 |
+
absent: [0, 1], developing: [0.5, 4], present_movable: [2, 12], fixed: [8, 200]
|
| 220 |
+
};
|
| 221 |
+
|
| 222 |
+
const rigorRange = rigorRanges[params.rigorMortis] || [0, 72];
|
| 223 |
+
const lividRange = lividityRanges[params.lividity] || [0, 200];
|
| 224 |
+
|
| 225 |
+
const stdError = params.bodyWeight >= 50 && params.bodyWeight <= 100 ? 2.8 : 3.2;
|
| 226 |
+
|
| 227 |
+
const earlyPhase = {
|
| 228 |
+
method: 'Henssge Nomogram (1988) + Physical Indicators',
|
| 229 |
+
pmiHours: Math.round(henssePMI * 10) / 10,
|
| 230 |
+
lowerBound: Math.round(Math.max(0, henssePMI - stdError) * 10) / 10,
|
| 231 |
+
upperBound: Math.round((henssePMI + stdError) * 10) / 10,
|
| 232 |
+
confidence: Q > 0.2 && Q < 0.8 ? 'HIGH' : 'MODERATE',
|
| 233 |
+
indicators: [
|
| 234 |
+
{ name: 'Body Temperature', value: `${params.rectalTemp}Β°C`, contribution: `Primary (Q=${Q.toFixed(3)})` },
|
| 235 |
+
{ name: 'Rigor Mortis', value: params.rigorMortis, contribution: `${rigorRange[0]}-${rigorRange[1]}h range` },
|
| 236 |
+
{ name: 'Lividity', value: params.lividity, contribution: `${lividRange[0]}-${Math.min(lividRange[1], 72)}h range` },
|
| 237 |
+
...(potassiumPMI ? [{ name: 'Vitreous K+', value: `${params.vitreousPotassium} mEq/L`, contribution: `~${potassiumPMI.toFixed(1)}h (Sturner)` }] : []),
|
| 238 |
+
]
|
| 239 |
+
};
|
| 240 |
+
|
| 241 |
+
// βββ LATE PHASE (>72h): Metabolomic/Decomposition ML βββ
|
| 242 |
+
const decompScores: Record<string, number> = {
|
| 243 |
+
absent: 0, early: 48, bloating: 96, advanced: 200
|
| 244 |
+
};
|
| 245 |
+
const decompPMI = decompScores[params.decomposition] || 0;
|
| 246 |
+
|
| 247 |
+
const latePhase = {
|
| 248 |
+
method: 'Metabolomic Regression + Decomposition Staging',
|
| 249 |
+
pmiHours: decompPMI > 0 ? decompPMI : null,
|
| 250 |
+
applicable: params.decomposition !== 'absent',
|
| 251 |
+
indicators: [
|
| 252 |
+
{ name: 'Decomposition Stage', value: params.decomposition, contribution: decompPMI > 0 ? `~${decompPMI}h estimate` : 'Not applicable' },
|
| 253 |
+
{ name: 'Biochemical Markers', value: 'Simulated', contribution: 'ML regression model' },
|
| 254 |
+
]
|
| 255 |
+
};
|
| 256 |
+
|
| 257 |
+
// βββ COMBINED ESTIMATE βββ
|
| 258 |
+
let bestEstimate = henssePMI;
|
| 259 |
+
let methodology = 'Henssge primary';
|
| 260 |
+
|
| 261 |
+
if (potassiumPMI && Math.abs(potassiumPMI - henssePMI) < 6) {
|
| 262 |
+
bestEstimate = (henssePMI * 0.7 + potassiumPMI * 0.3);
|
| 263 |
+
methodology = 'Henssge (70%) + Vitreous K+ (30%)';
|
| 264 |
+
}
|
| 265 |
+
if (latePhase.applicable && decompPMI > 72) {
|
| 266 |
+
bestEstimate = decompPMI;
|
| 267 |
+
methodology = 'Decomposition staging (late-phase dominant)';
|
| 268 |
+
}
|
| 269 |
+
|
| 270 |
+
bestEstimate = Math.round(bestEstimate * 10) / 10;
|
| 271 |
+
|
| 272 |
+
// Cooling curve
|
| 273 |
+
const coolingCurve = Array.from({ length: 48 }, (_, i) => ({
|
| 274 |
+
time: i,
|
| 275 |
+
temp: params.ambientTemp + (this.T_INITIAL - params.ambientTemp) * (1.25 * Math.exp(-B * i) - 0.25 * Math.exp(-5 * B * i))
|
| 276 |
+
}));
|
| 277 |
+
|
| 278 |
+
return {
|
| 279 |
+
earlyPhase,
|
| 280 |
+
latePhase,
|
| 281 |
+
combined: {
|
| 282 |
+
bestEstimate,
|
| 283 |
+
range: `${Math.max(0, bestEstimate - stdError).toFixed(1)} β ${(bestEstimate + stdError).toFixed(1)} hours`,
|
| 284 |
+
confidence: earlyPhase.confidence,
|
| 285 |
+
methodology
|
| 286 |
+
},
|
| 287 |
+
coolingCurve
|
| 288 |
+
};
|
| 289 |
+
}
|
| 290 |
+
}
|
| 291 |
+
|
| 292 |
+
// βββ SMART EVIDENCE PRIORITIZATION βββ
|
| 293 |
+
|
| 294 |
+
export interface PrioritizedEvidence {
|
| 295 |
+
id: string;
|
| 296 |
+
content: string;
|
| 297 |
+
type: string;
|
| 298 |
+
priority: number; // 1-10
|
| 299 |
+
reasoning: string;
|
| 300 |
+
actionRequired: string;
|
| 301 |
+
timeUrgency: 'immediate' | 'high' | 'medium' | 'low';
|
| 302 |
+
}
|
| 303 |
+
|
| 304 |
+
export function prioritizeEvidence(findings: any[]): PrioritizedEvidence[] {
|
| 305 |
+
const prioritized: PrioritizedEvidence[] = [];
|
| 306 |
+
|
| 307 |
+
findings.forEach((f, i) => {
|
| 308 |
+
let priority = 5;
|
| 309 |
+
let reasoning = '';
|
| 310 |
+
let action = '';
|
| 311 |
+
let urgency: PrioritizedEvidence['timeUrgency'] = 'medium';
|
| 312 |
+
|
| 313 |
+
if (f.content?.toLowerCase().includes('dna') || f.content?.toLowerCase().includes('fingernail')) {
|
| 314 |
+
priority = 10;
|
| 315 |
+
reasoning = 'DNA evidence directly identifies suspect';
|
| 316 |
+
action = 'Submit for immediate DNA analysis and CODIS search';
|
| 317 |
+
urgency = 'immediate';
|
| 318 |
+
} else if (f.type === 'PERSON_DISCREPANCY' || f.content?.toLowerCase().includes('defensive')) {
|
| 319 |
+
priority = 9;
|
| 320 |
+
reasoning = 'Directly indicates interpersonal violence and suspect presence';
|
| 321 |
+
action = 'Cross-reference CCTV for facial identification';
|
| 322 |
+
urgency = 'immediate';
|
| 323 |
+
} else if (f.content?.toLowerCase().includes('vehicle') || f.content?.toLowerCase().includes('sedan')) {
|
| 324 |
+
priority = 8;
|
| 325 |
+
reasoning = 'Vehicle identification can lead directly to suspect';
|
| 326 |
+
action = 'Run plate recognition on CCTV footage';
|
| 327 |
+
urgency = 'high';
|
| 328 |
+
} else if (f.content?.toLowerCase().includes('fiber') || f.content?.toLowerCase().includes('foreign')) {
|
| 329 |
+
priority = 7;
|
| 330 |
+
reasoning = 'Trace evidence links suspect to specific items';
|
| 331 |
+
action = 'Lab analysis for material identification';
|
| 332 |
+
urgency = 'high';
|
| 333 |
+
} else if (f.type === 'TOXICOLOGY') {
|
| 334 |
+
priority = 6;
|
| 335 |
+
reasoning = 'Toxicology may indicate premeditation (sedation)';
|
| 336 |
+
action = 'Check victim prescription records and purchase history';
|
| 337 |
+
urgency = 'medium';
|
| 338 |
+
} else if (f.type === 'TIMELINE_GAP') {
|
| 339 |
+
priority = 5;
|
| 340 |
+
reasoning = 'Gaps may contain unrecovered evidence';
|
| 341 |
+
action = 'Search for additional CCTV coverage in gap periods';
|
| 342 |
+
urgency = 'medium';
|
| 343 |
+
} else {
|
| 344 |
+
priority = 3;
|
| 345 |
+
reasoning = 'Supporting evidence for case building';
|
| 346 |
+
action = 'Document and preserve';
|
| 347 |
+
urgency = 'low';
|
| 348 |
+
}
|
| 349 |
+
|
| 350 |
+
prioritized.push({
|
| 351 |
+
id: `pri-${i}`,
|
| 352 |
+
content: f.content?.slice(0, 100) || f.type || 'Unknown',
|
| 353 |
+
type: f.type || 'GENERAL',
|
| 354 |
+
priority,
|
| 355 |
+
reasoning,
|
| 356 |
+
actionRequired: action,
|
| 357 |
+
timeUrgency: urgency
|
| 358 |
+
});
|
| 359 |
+
});
|
| 360 |
+
|
| 361 |
+
return prioritized.sort((a, b) => b.priority - a.priority);
|
| 362 |
+
}
|