import logging
from django import forms
from django.contrib import admin
from django.http import JsonResponse
from access_core.monitoring.models import (
Alignment,
CarePeriod,
ClinicalTrack,
DailyCheckinItem,
DailyCheckinResponse,
DailyCheckinSubmission,
DailyCheckinTemplate,
DailyStatusLog,
EligibilityCheck,
EligibilityRequest,
EligibilityRequestCondition,
EnrollmentDecision,
MissedCheckinEvent,
MonitoringPlan,
MonitoringPlanItem,
MonitoringPlanResult,
MonitoringPlanTemplate,
MonthlyPatientSummary,
PatientAdherenceSummary,
PatientExecutionLog,
PatientNotification,
ProtocolAssignmentVersion,
ProtocolVersion,
RawDailyCheckinSubmission,
TrackSpecificDailyLog,
Unalignment,
)
logger = logging.getLogger(__name__)
@admin.register(ClinicalTrack)
class ClinicalTrackAdmin(admin.ModelAdmin):
list_display = ["track_name", "description", "is_active"]
class EligibilityRequestConditionForm(forms.ModelForm):
"""Marks the code input so icd10_autocomplete.js can attach a typeahead."""
class Meta:
model = EligibilityRequestCondition
fields = ["icd10_code", "icd10_description"]
icd10_code = forms.CharField(
max_length=10,
widget=forms.TextInput(attrs={"class": "icd10-code-input"}),
)
class EligibilityRequestConditionInline(admin.TabularInline):
model = EligibilityRequestCondition
form = EligibilityRequestConditionForm
extra = 1
@admin.register(EligibilityRequest)
class EligibilityRequestAdmin(admin.ModelAdmin):
list_display = [
"intake_record",
"clinical_track",
"icd10_codes",
"created_at",
]
list_filter = ["clinical_track"]
inlines = [EligibilityRequestConditionInline]
@property
def media(self):
# select2 is intentionally NOT used here: its compat shim
# replaces the ajax data adapter with a local-only lookup, so it can
# never search on a plain text input. The typeahead is hand-rolled in
# icd10_autocomplete.js (see that file's header for details).
return super().media + forms.Media(
js=("monitoring/icd10_autocomplete.js",),
css={"screen": ("monitoring/icd10_autocomplete.css",)},
)
def icd10_search(self, request):
"""JSON typeahead endpoint searching the local ICD-10-CM snapshot table."""
from access_core.services.icd10_search import (
Icd10SearchError,
search_icd10,
)
terms = (request.GET.get("terms") or "").strip()
if len(terms) < 2:
return JsonResponse({"results": []})
try:
results = search_icd10(terms)
except Icd10SearchError as exc:
logger.warning(f"ICD-10-CM search failed for {terms!r}: {exc}")
# 5xx signals upstream failure; the JS ignores it and manual entry works.
return JsonResponse({"results": [], "error": str(exc)}, status=502)
return JsonResponse({"results": results})
def get_urls(self):
from django.urls import path
# Prepend: the built-in / history pattern would otherwise
# swallow "icd10-search" as an object id.
return [
path(
"icd10-search/",
self.admin_site.admin_view(self.icd10_search),
name="monitoring_eligibilityrequest_icd10_search",
)
] + super().get_urls()
@admin.display(description="ICD-10 codes")
def icd10_codes(self, obj):
return ", ".join(obj.conditions.values_list("icd10_code", flat=True)) or "—"
@admin.register(EligibilityCheck)
class EligibilityCheckAdmin(admin.ModelAdmin):
list_display = [
"intake_record",
"eligibility_request",
"requested_track",
"check_status",
"checked_at",
]
list_filter = ["check_status"]
@admin.register(Alignment)
class AlignmentAdmin(admin.ModelAdmin):
list_display = ["intake_record", "clinical_track", "aligned_at"]
@admin.register(Unalignment)
class UnalignmentAdmin(admin.ModelAdmin):
list_display = ["beneficiary", "unaligned_at", "reason"]
@admin.register(EnrollmentDecision)
class EnrollmentDecisionAdmin(admin.ModelAdmin):
list_display = ["intake_record", "decision", "decision_by", "decision_at"]
list_filter = ["decision"]
@admin.register(CarePeriod)
class CarePeriodAdmin(admin.ModelAdmin):
list_display = [
"beneficiary",
"clinical_track",
"start_date",
"end_date",
"operational_status",
]
list_filter = ["operational_status", "baseline_status"]
@admin.register(ProtocolVersion)
class ProtocolVersionAdmin(admin.ModelAdmin):
list_display = ["version_number", "clinical_track", "is_active", "effective_from"]
@admin.register(ProtocolAssignmentVersion)
class ProtocolAssignmentVersionAdmin(admin.ModelAdmin):
list_display = ["beneficiary", "protocol_version", "assigned_at"]
@admin.register(PatientExecutionLog)
class PatientExecutionLogAdmin(admin.ModelAdmin):
list_display = ["beneficiary", "execution_date", "completed", "source_system"]
list_filter = ["source_system"]
@admin.register(DailyStatusLog)
class DailyStatusLogAdmin(admin.ModelAdmin):
list_display = ["beneficiary", "log_date", "source_system"]
list_filter = ["source_system"]
@admin.register(TrackSpecificDailyLog)
class TrackSpecificDailyLogAdmin(admin.ModelAdmin):
list_display = ["beneficiary", "log_date", "clinical_track", "source_system"]
list_filter = ["clinical_track", "source_system"]
@admin.register(MonitoringPlanTemplate)
class MonitoringPlanTemplateAdmin(admin.ModelAdmin):
list_display = ["template_name", "clinical_track", "is_active"]
@admin.register(MonitoringPlan)
class MonitoringPlanAdmin(admin.ModelAdmin):
list_display = ["beneficiary", "clinical_trigger", "status", "created_at"]
list_filter = ["status"]
@admin.register(MonitoringPlanItem)
class MonitoringPlanItemAdmin(admin.ModelAdmin):
list_display = [
"monitoring_plan",
"item_description",
"frequency_interval",
"status",
]
list_filter = ["status"]
@admin.register(MonitoringPlanResult)
class MonitoringPlanResultAdmin(admin.ModelAdmin):
list_display = [
"monitoring_plan_item",
"result_date",
"value_numeric",
"review_status",
]
@admin.register(DailyCheckinTemplate)
class DailyCheckinTemplateAdmin(admin.ModelAdmin):
list_display = ["template_name", "clinical_track", "version", "is_active"]
@admin.register(DailyCheckinItem)
class DailyCheckinItemAdmin(admin.ModelAdmin):
list_display = ["template", "item_key", "item_type", "display_order"]
@admin.register(DailyCheckinSubmission)
class DailyCheckinSubmissionAdmin(admin.ModelAdmin):
list_display = ["beneficiary", "submission_date", "source_system", "status"]
@admin.register(DailyCheckinResponse)
class DailyCheckinResponseAdmin(admin.ModelAdmin):
list_display = ["submission", "item"]
@admin.register(RawDailyCheckinSubmission)
class RawDailyCheckinSubmissionAdmin(admin.ModelAdmin):
list_display = ["beneficiary", "source_system", "ingested_at"]
@admin.register(MissedCheckinEvent)
class MissedCheckinEventAdmin(admin.ModelAdmin):
list_display = ["beneficiary", "missed_date", "consecutive_days_missed", "notified"]
@admin.register(PatientAdherenceSummary)
class PatientAdherenceSummaryAdmin(admin.ModelAdmin):
list_display = ["beneficiary", "period_start", "period_end", "adherence_rate"]
@admin.register(MonthlyPatientSummary)
class MonthlyPatientSummaryAdmin(admin.ModelAdmin):
list_display = ["beneficiary", "month", "review_status"]
@admin.register(PatientNotification)
class PatientNotificationAdmin(admin.ModelAdmin):
list_display = ["beneficiary", "notification_type", "channel", "sent_at"]
list_filter = ["notification_type", "channel"]