Spaces:
Sleeping
Sleeping
| from django.contrib import admin | |
| from django.utils.html import format_html | |
| from .models import Trial, Patient, Visit, PredictionResult, CohortForecast, CoordinatorAction | |
| RISK_COLOURS = { | |
| 'low': '#009639', 'medium': '#FFB81C', | |
| 'high': '#E65C00', 'critical': '#CC0000', | |
| } | |
| class TrialAdmin(admin.ModelAdmin): | |
| list_display = ('name', 'sponsor', 'phase', 'therapeutic_area', 'target_enrollment', | |
| 'start_date', 'end_date', 'patient_count', 'retention') | |
| list_filter = ('phase', 'therapeutic_area') | |
| search_fields = ('name', 'sponsor', 'therapeutic_area') | |
| ordering = ('-created_at',) | |
| readonly_fields = ('created_at',) | |
| def patient_count(self, obj): | |
| return obj.patients.count() | |
| patient_count.short_description = 'Patients' | |
| def retention(self, obj): | |
| rate = obj.retention_rate() | |
| colour = '#009639' if rate >= 80 else ('#FFB81C' if rate >= 60 else '#E65C00') | |
| return format_html('<span style="color: {}; font-weight: bold;">{:.1f}%</span>', colour, rate) | |
| retention.short_description = 'Retention Rate' | |
| class VisitInline(admin.TabularInline): | |
| model = Visit | |
| extra = 0 | |
| fields = ('visit_number', 'visit_date', 'adverse_events_count', | |
| 'missed_visits_to_date', 'medication_adherence_score', 'quality_of_life_score') | |
| readonly_fields = ('visit_number',) | |
| ordering = ('visit_number',) | |
| class PredictionInline(admin.TabularInline): | |
| model = PredictionResult | |
| extra = 0 | |
| fields = ('visit', 'risk_tier_display', 'dropout_probability', 'prediction_timestamp') | |
| readonly_fields = ('visit', 'risk_tier_display', 'dropout_probability', 'prediction_timestamp') | |
| ordering = ('-prediction_timestamp',) | |
| max_num = 5 | |
| def risk_tier_display(self, obj): | |
| colour = RISK_COLOURS.get(obj.risk_tier, '#888') | |
| return format_html( | |
| '<span style="color: {}; font-weight: bold; text-transform: uppercase;">{}</span>', | |
| colour, obj.risk_tier | |
| ) | |
| risk_tier_display.short_description = 'Risk Tier' | |
| class PatientAdmin(admin.ModelAdmin): | |
| list_display = ('id', 'trial', 'age', 'gender', 'condition_severity', | |
| 'dropout_status', 'risk_tier_display', 'enrollment_date') | |
| list_filter = ('trial', 'gender', 'condition_severity', 'dropout_status', | |
| 'ethnicity', 'employment_status', 'prior_dropout_history') | |
| search_fields = ('id', 'trial__name') | |
| ordering = ('-created_at',) | |
| readonly_fields = ('created_at',) | |
| inlines = [VisitInline, PredictionInline] | |
| list_select_related = ('trial',) | |
| def risk_tier_display(self, obj): | |
| pred = obj.predictions.order_by('-prediction_timestamp').first() | |
| if not pred: | |
| return format_html('<span style="color: #888;">—</span>') | |
| colour = RISK_COLOURS.get(pred.risk_tier, '#888') | |
| return format_html( | |
| '<span style="color: {}; font-weight: bold; text-transform: uppercase;">{}</span>', | |
| colour, pred.risk_tier | |
| ) | |
| risk_tier_display.short_description = 'Latest Risk' | |
| class VisitAdmin(admin.ModelAdmin): | |
| list_display = ('id', 'patient', 'visit_number', 'visit_date', 'adverse_events_count', | |
| 'missed_visits_to_date', 'medication_adherence_score', 'quality_of_life_score') | |
| list_filter = ('visit_date',) | |
| search_fields = ('patient__id',) | |
| ordering = ('patient', 'visit_number') | |
| list_select_related = ('patient', 'patient__trial') | |
| class PredictionResultAdmin(admin.ModelAdmin): | |
| list_display = ('id', 'patient', 'visit', 'risk_tier_coloured', 'probability_bar', | |
| 'hazard_ratio', 'survival_time_estimate', 'model_version', 'prediction_timestamp') | |
| list_filter = ('risk_tier', 'model_version', 'prediction_timestamp') | |
| search_fields = ('patient__id',) | |
| ordering = ('-prediction_timestamp',) | |
| readonly_fields = ('prediction_timestamp',) | |
| list_select_related = ('patient', 'visit', 'patient__trial') | |
| def risk_tier_coloured(self, obj): | |
| colour = RISK_COLOURS.get(obj.risk_tier, '#888') | |
| return format_html( | |
| '<span style="color: {}; font-weight: bold; text-transform: uppercase;">{}</span>', | |
| colour, obj.risk_tier | |
| ) | |
| risk_tier_coloured.short_description = 'Risk Tier' | |
| def probability_bar(self, obj): | |
| pct = int(obj.dropout_probability * 100) | |
| colour = RISK_COLOURS.get(obj.risk_tier, '#888') | |
| return format_html( | |
| '<div style="background:#F0F4F5;border-radius:4px;width:120px;">' | |
| '<div style="background:{};width:{}%;padding:2px 4px;border-radius:4px;' | |
| 'color:#ffffff;font-size:11px;text-align:center;">{:.1f}%</div></div>', | |
| colour, pct, obj.dropout_probability * 100 | |
| ) | |
| probability_bar.short_description = 'Probability' | |
| class CohortForecastAdmin(admin.ModelAdmin): | |
| list_display = ('trial', 'forecast_date', 'predicted_dropouts_30d', | |
| 'predicted_dropouts_60d', 'predicted_dropouts_90d', | |
| 'confidence_interval_lower', 'confidence_interval_upper', 'created_at') | |
| list_filter = ('trial', 'forecast_date') | |
| search_fields = ('trial__name',) | |
| ordering = ('-forecast_date',) | |
| list_select_related = ('trial',) | |
| class CoordinatorActionAdmin(admin.ModelAdmin): | |
| list_display = ('patient', 'action_type', 'action_date', 'outcome', 'coordinator', 'created_at') | |
| list_filter = ('action_type', 'outcome', 'action_date') | |
| search_fields = ('patient__id', 'notes') | |
| ordering = ('-action_date',) | |
| readonly_fields = ('created_at',) | |
| list_select_related = ('patient', 'patient__trial', 'coordinator') | |