, assessed_at: ?\Carbon\Carbon}> */ public function outcomeSeries(Patient $patient, string $ownerRef, ?int $branchScope = null, int $limit = 50): Collection { $template = AssessmentTemplate::currentSystemByCode(self::OUTCOME_TEMPLATE_CODE); $query = Assessment::query() ->owned($ownerRef) ->where('patient_id', $patient->id) ->where('status', Assessment::STATUS_COMPLETED) ->with(['answers.question', 'template']) ->orderByDesc('assessed_at') ->orderByDesc('completed_at') ->limit($limit); if ($template) { $query->where('template_id', $template->id); } else { $query->whereHas('template', fn ($q) => $q->where('code', self::OUTCOME_TEMPLATE_CODE)); } if ($branchScope !== null) { $query->where('branch_id', $branchScope); } return $query->get()->map(function (Assessment $assessment) { $answers = []; foreach ($assessment->answers as $answer) { if ($answer->question) { $answers[$answer->question->code] = $answer->authoritativeValue($answer->question->answer_type); } } return [ 'assessment' => $assessment, 'answers' => $answers, 'assessed_at' => $assessment->assessed_at ?? $assessment->completed_at, ]; }); } /** * Recent instrument scores for chartable disease templates. * * @return Collection */ public function instrumentScores( Patient $patient, string $ownerRef, ?int $branchScope = null, ?string $templateCode = null, int $limit = 50, ): Collection { $query = AssessmentScore::query() ->owned($ownerRef) ->whereHas('assessment', function ($q) use ($patient, $branchScope) { $q->where('patient_id', $patient->id) ->where('status', Assessment::STATUS_COMPLETED) ->when($branchScope !== null, fn ($qq) => $qq->where('branch_id', $branchScope)); }) ->with(['assessment.template']) ->orderByDesc('computed_at') ->limit($limit); if ($templateCode) { $query->where('template_code', $templateCode); } return $query->get(); } /** * Latest vitals from consultations (typed path — not outcome form). * * @return Collection */ public function recentVitals(Patient $patient, string $ownerRef, int $limit = 10): Collection { return VitalSign::query() ->where('owner_ref', $ownerRef) ->whereHas('consultation', fn ($q) => $q->where('patient_id', $patient->id)) ->orderByDesc('recorded_at') ->limit($limit) ->get(); } /** * Sparkline-friendly series for a single outcome numeric code (oldest → newest). * * @return list */ public function numericOutcomeSeries(Patient $patient, string $ownerRef, string $questionCode, ?int $branchScope = null): array { $rows = $this->outcomeSeries($patient, $ownerRef, $branchScope)->reverse()->values(); $out = []; foreach ($rows as $row) { $val = $row['answers'][$questionCode] ?? null; if ($val === null || $val === '') { continue; } $out[] = [ 'date' => $row['assessed_at']?->format('Y-m-d') ?? '', 'value' => is_numeric($val) ? $val + 0 : null, ]; } return $out; } }