feat(assessments): layered clinical assessment engine end-to-end
Deploy Ladill Care / deploy (push) Successful in 1m26s

Add a template-driven assessment system with universal intake, clinical
pathways, disease instruments (stroke MVP + extended, diabetes, and ten
specialty packs), scoring, patient outcome trends, REST/API + FHIR
export, and Enterprise org-level assessment analytics. Seed packs and
design/licensing docs ship for deploy and pre-GA review.
This commit is contained in:
isaacclad
2026-07-16 22:58:09 +00:00
parent 8896088425
commit 2ce4bc8993
94 changed files with 13430 additions and 56 deletions
+100
View File
@@ -3,10 +3,13 @@
namespace App\Services\Care;
use App\Models\Appointment;
use App\Models\Assessment;
use App\Models\AssessmentScore;
use App\Models\Bill;
use App\Models\Diagnosis;
use App\Models\InvestigationRequest;
use App\Models\Patient;
use App\Models\PatientPathway;
use App\Models\Payment;
use App\Models\Visit;
use Illuminate\Database\Eloquent\Builder;
@@ -200,4 +203,101 @@ class ReportService
->limit(20)
->get();
}
/**
* Org-level assessment analytics (Enterprise / assessment_analytics).
*
* @return array{
* summary: array<string, int>,
* by_template: Collection<int, object>,
* by_pathway: Collection<int, object>,
* score_averages: Collection<int, object>
* }
*/
public function assessmentsReport(
string $ownerRef,
int $organizationId,
Carbon $from,
Carbon $to,
?int $branchId = null,
): array {
$base = Assessment::query()
->from('care_assessments')
->where('care_assessments.owner_ref', $ownerRef)
->where('care_assessments.organization_id', $organizationId)
->when($branchId, fn ($q) => $q->where('care_assessments.branch_id', $branchId))
->whereBetween('care_assessments.created_at', [$from, $to])
->whereNull('care_assessments.deleted_at');
$summary = [
'total_started' => (clone $base)->count(),
'completed' => (clone $base)->where('care_assessments.status', Assessment::STATUS_COMPLETED)->count(),
'draft' => (clone $base)->where('care_assessments.status', Assessment::STATUS_DRAFT)->count(),
'cancelled' => (clone $base)->where('care_assessments.status', Assessment::STATUS_CANCELLED)->count(),
'patients_with_assessments' => (clone $base)->distinct('care_assessments.patient_id')->count('care_assessments.patient_id'),
'pathways_activated' => PatientPathway::query()
->where('care_patient_pathways.owner_ref', $ownerRef)
->where('care_patient_pathways.organization_id', $organizationId)
->whereBetween('care_patient_pathways.activated_at', [$from, $to])
->whereNull('care_patient_pathways.deleted_at')
->count(),
];
$byTemplate = (clone $base)
->join('care_assessment_templates', 'care_assessments.template_id', '=', 'care_assessment_templates.id')
->select(
'care_assessment_templates.code as template_code',
'care_assessment_templates.name as template_name',
DB::raw('count(*) as total'),
DB::raw("sum(case when care_assessments.status = 'completed' then 1 else 0 end) as completed"),
)
->groupBy('care_assessment_templates.code', 'care_assessment_templates.name')
->orderByDesc('total')
->limit(30)
->get();
$byPathway = PatientPathway::query()
->from('care_patient_pathways')
->where('care_patient_pathways.owner_ref', $ownerRef)
->where('care_patient_pathways.organization_id', $organizationId)
->whereBetween('care_patient_pathways.activated_at', [$from, $to])
->whereNull('care_patient_pathways.deleted_at')
->join('care_clinical_pathways', 'care_patient_pathways.pathway_id', '=', 'care_clinical_pathways.id')
->select(
'care_clinical_pathways.code as pathway_code',
'care_clinical_pathways.name as pathway_name',
DB::raw('count(*) as total'),
DB::raw("sum(case when care_patient_pathways.status = 'active' then 1 else 0 end) as still_active"),
)
->groupBy('care_clinical_pathways.code', 'care_clinical_pathways.name')
->orderByDesc('total')
->limit(20)
->get();
$scoreAverages = AssessmentScore::query()
->from('care_assessment_scores')
->where('care_assessment_scores.owner_ref', $ownerRef)
->whereHas('assessment', function (Builder $q) use ($organizationId, $branchId, $from, $to) {
$q->where('care_assessments.organization_id', $organizationId)
->when($branchId, fn ($qq) => $qq->where('care_assessments.branch_id', $branchId))
->whereBetween('care_assessments.completed_at', [$from, $to]);
})
->select(
'care_assessment_scores.template_code',
DB::raw('count(*) as completions'),
DB::raw('avg(care_assessment_scores.total_score) as avg_total'),
DB::raw('avg(care_assessment_scores.max_score) as avg_max'),
)
->groupBy('care_assessment_scores.template_code')
->orderByDesc('completions')
->limit(20)
->get();
return [
'summary' => $summary,
'by_template' => $byTemplate,
'by_pathway' => $byPathway,
'score_averages' => $scoreAverages,
];
}
}