diff --git a/.Net Capstone Project/RiskScoringServiceupdatedcode.cs b/.Net Capstone Project/RiskScoringServiceupdatedcode.cs new file mode 100644 index 0000000..1aa2efc --- /dev/null +++ b/.Net Capstone Project/RiskScoringServiceupdatedcode.cs @@ -0,0 +1,147 @@ +using ClinicalInsightsPro.API.Data; +using ClinicalInsightsPro.API.DTOs; +using ClinicalInsightsPro.API.Models; +using ClinicalInsightsPro.API.Services.Interfaces; +using Microsoft.EntityFrameworkCore; + +namespace ClinicalInsightsPro.API.Services; + +// Questionnaire-based rule engine that scores an encounter as High or Normal +// risk, combining chart data (conditions, vitals) with clinician-entered +// questionnaire responses. Rule weights are intentionally simple and modular +// so they can be extended, or eventually replaced by an ML model, per scope. +public class RiskScoringService : IRiskScoringService +{ + private readonly ApplicationDbContext _db; + private const int HighRiskThreshold = 50; + + public RiskScoringService(ApplicationDbContext db) + { + _db = db; + } + + public async Task AssessAsync(string patientId, QuestionnaireDto questionnaire) + { + var patient = await _db.Patients + .Include(p => p.Conditions) + .Include(p => p.Observations) + .Include(p => p.Allergies) + .Include(p => p.Medications) + .Include(p => p.Encounters) + .FirstOrDefaultAsync(p => p.Id == patientId); + + if (patient == null) + throw new KeyNotFoundException($"Patient '{patientId}' was not found."); + + int score = 0; + var factors = new List(); + + foreach (var c in patient.Conditions.Where(c => c.ClinicalStatus == "active")) + { + switch (c.Code) + { + case "E11.9": + score += 20; factors.Add("Active Type 2 diabetes (+20)"); break; + case "I10": + score += 15; factors.Add("Essential hypertension (+15)"); break; + case "J45.909": + score += 10; factors.Add("Asthma (+10)"); break; + default: + score += 5; factors.Add($"Active condition: {c.Display} (+5)"); break; + } + } + + var latestSystolic = patient.Observations + .Where(o => o.Code == "8480-6") + .OrderByDescending(o => o.EffectiveDate) + .FirstOrDefault(); + + if (latestSystolic != null && latestSystolic.Value >= 160) + { + score += 20; factors.Add($"Systolic BP {latestSystolic.Value} mmHg — hypertensive urgency range (+20)"); + } + else if (latestSystolic != null && latestSystolic.Value >= 140) + { + score += 10; factors.Add($"Systolic BP {latestSystolic.Value} mmHg — stage 2 hypertension (+10)"); + } + + var latestGlucose = patient.Observations + .Where(o => o.Code == "2339-0") + .OrderByDescending(o => o.EffectiveDate) + .FirstOrDefault(); + + if (latestGlucose != null && latestGlucose.Value >= 200) + { + score += 15; factors.Add($"Glucose {latestGlucose.Value} mg/dL — poorly controlled (+15)"); + } + + // Allergies — a known high-criticality allergy raises the risk of a + // future adverse drug event, independent of what's happening today. + var highCriticalityAllergies = patient.Allergies.Count(a => a.Criticality == "high"); + if (highCriticalityAllergies > 0) + { + var allergyPoints = Math.Min(highCriticalityAllergies * 10, 20); + score += allergyPoints; + factors.Add($"{highCriticalityAllergies} high-criticality allerg{(highCriticalityAllergies == 1 ? "y" : "ies")} on file (+{allergyPoints})"); + } + + // Medications — polypharmacy (commonly defined as 5+ concurrent + // medications) is itself an established risk factor for interactions + // and adverse events, regardless of what the medications are for. + var activeMedicationCount = patient.Medications.Count(m => m.Status == "active"); + if (activeMedicationCount >= 5) + { + score += 12; factors.Add($"Polypharmacy — {activeMedicationCount} active medications (+12)"); + } + else if (activeMedicationCount >= 3) + { + score += 6; factors.Add($"{activeMedicationCount} active medications (+6)"); + } + + // Encounters — frequent recent encounters (visits, admissions, ED + // visits) in the last 12 months suggest an unstable or worsening + // condition even if no single encounter looks alarming on its own. + var recentEncounterCount = patient.Encounters.Count(e => e.EncounterDate >= DateTime.UtcNow.AddMonths(-12)); + if (recentEncounterCount >= 3) + { + score += 12; factors.Add($"{recentEncounterCount} encounters within the last 12 months (+12)"); + } + else if (recentEncounterCount == 2) + { + score += 6; factors.Add("2 encounters within the last 12 months (+6)"); + } + + if (questionnaire.Smoker) { score += 10; factors.Add("Current smoker (+10)"); } + if (questionnaire.FamilyHistoryHeartDisease) { score += 8; factors.Add("Family history of heart disease (+8)"); } + if (questionnaire.PriorHospitalization) { score += 12; factors.Add("Prior hospitalization within 12 months (+12)"); } + if (questionnaire.Bmi is >= 30) { score += 8; factors.Add($"BMI {questionnaire.Bmi:F1} — obese range (+8)"); } + if (questionnaire.PainScore >= 7) { score += 5; factors.Add($"Reported pain score {questionnaire.PainScore}/10 (+5)"); } + + score = Math.Min(score, 100); + var level = score >= HighRiskThreshold ? "High" : "Normal"; + + if (!factors.Any()) + factors.Add("No significant risk factors identified from chart or questionnaire."); + + var assessment = new RiskAssessment + { + PatientId = patientId, + Score = score, + RiskLevel = level, + Rationale = string.Join("; ", factors), + AssessedDate = DateTime.UtcNow + }; + + _db.RiskAssessments.Add(assessment); + await _db.SaveChangesAsync(); + + return new RiskAssessmentResultDto + { + PatientId = patientId, + Score = score, + RiskLevel = level, + ContributingFactors = factors, + AssessedDate = assessment.AssessedDate + }; + } +}