DotNet_Capstone_Project/.Net Capstone Project/RiskScoringServiceupdatedcode.cs

148 lines
6.1 KiB
C#

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<RiskAssessmentResultDto> 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<string>();
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
};
}
}