""" Data Curation — Tasks, drafts, review events, documents, reporting. """ import uuid from django.db import models from access_core.constants import TRACK_CHOICES class SourceDocumentRecord(models.Model): id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) beneficiary = models.ForeignKey( "identity.Beneficiary", on_delete=models.SET_NULL, null=True, blank=True, related_name="source_documents", ) intake_record = models.ForeignKey( "intake.IntakeRecord", on_delete=models.SET_NULL, null=True, blank=True, related_name="source_documents", ) document_type = models.TextField() source_system = models.TextField() source_record_id = models.TextField(null=True, blank=True) document_date = models.DateField(null=True, blank=True) linked_ingestion = models.ForeignKey( "intake.RawIngestionRecord", on_delete=models.SET_NULL, null=True, blank=True ) created_at = models.DateTimeField(auto_now_add=True) def __str__(self): return f"Source doc: {self.source_system} ({self.document_type})" class Meta: app_label = "curation" class ClinicalDocument(models.Model): id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) beneficiary = models.ForeignKey( "identity.Beneficiary", on_delete=models.SET_NULL, null=True, blank=True, related_name="clinical_documents", ) intake_record = models.ForeignKey( "intake.IntakeRecord", on_delete=models.SET_NULL, null=True, blank=True, related_name="clinical_documents", ) document_type = models.TextField() file_name = models.TextField() storage_path = models.TextField() mime_type = models.TextField() file_size_bytes = models.IntegerField() upload_date = models.DateField() uploaded_by = models.ForeignKey( "identity.User", on_delete=models.SET_NULL, null=True, blank=True ) is_encrypted = models.BooleanField(default=True) created_at = models.DateTimeField(auto_now_add=True) def __str__(self): return f"Document: {self.file_name} ({self.document_type})" class Meta: app_label = "curation" class DocumentMetadata(models.Model): id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) document = models.ForeignKey( ClinicalDocument, on_delete=models.CASCADE, related_name="metadata" ) metadata_key = models.TextField() metadata_value = models.TextField() created_at = models.DateTimeField(auto_now_add=True) def __str__(self): return f"Metadata: {self.document} - {self.metadata_key}" class Meta: app_label = "curation" class DocumentVersion(models.Model): id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) document = models.ForeignKey( ClinicalDocument, on_delete=models.CASCADE, related_name="versions" ) version_number = models.IntegerField() storage_path = models.TextField() change_reason = models.TextField(null=True, blank=True) uploaded_by = models.ForeignKey("identity.User", on_delete=models.PROTECT) created_at = models.DateTimeField(auto_now_add=True) def __str__(self): return f"v{self.version_number}: {self.document}" class Meta: app_label = "curation" constraints = [ models.UniqueConstraint( fields=["document", "version_number"], name="unique_document_version_number", ) ] class DocumentAccessEvent(models.Model): id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) document = models.ForeignKey( ClinicalDocument, on_delete=models.CASCADE, related_name="access_events" ) user = models.ForeignKey("identity.User", on_delete=models.PROTECT) action = models.TextField() accessed_at = models.DateTimeField(auto_now_add=True) def __str__(self): return f"Access: {self.document} - {self.action} by {self.user}" class Meta: app_label = "curation" class DataCurationTask(models.Model): TASK_TYPE_CHOICES = [ ("lab_extraction", "Lab Extraction"), ("prom_scoring", "PROM Scoring"), ("intake_normalization", "Intake Normalization"), ("document_summarization", "Document Summarization"), ("daily_log_review", "Daily Log Review"), ] id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) source_record = models.ForeignKey( "intake.RawIngestionRecord", on_delete=models.PROTECT ) beneficiary = models.ForeignKey( "identity.Beneficiary", on_delete=models.SET_NULL, null=True, blank=True ) assigned_to = models.ForeignKey("identity.User", on_delete=models.PROTECT) task_type = models.TextField(choices=TASK_TYPE_CHOICES) status = models.TextField(default="pending") assigned_at = models.DateTimeField(auto_now_add=True) completed_at = models.DateTimeField(null=True, blank=True) notes = models.TextField(null=True, blank=True) created_at = models.DateTimeField(auto_now_add=True) def __str__(self): return f"Task: {self.task_type} -> {self.assigned_to}" class Meta: app_label = "curation" class CuratedObservationDraft(models.Model): id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) curation_task = models.ForeignKey(DataCurationTask, on_delete=models.PROTECT) beneficiary = models.ForeignKey("identity.Beneficiary", on_delete=models.PROTECT) care_period = models.ForeignKey( "monitoring.CarePeriod", on_delete=models.SET_NULL, null=True, blank=True ) observation_type = models.TextField() measure_name = models.TextField() loinc_code = models.ForeignKey( "clinical.LoincCode", on_delete=models.SET_NULL, null=True, blank=True ) value_text = models.TextField(null=True, blank=True) value_numeric = models.FloatField(null=True, blank=True) unit = models.TextField(null=True, blank=True) collection_date = models.DateField(null=True, blank=True) data_source = models.TextField() collection_method = models.TextField(null=True, blank=True) notes = models.TextField(null=True, blank=True) status = models.TextField(default="drafted") migrated_to_observation = models.ForeignKey( "clinical.ClinicalObservation", on_delete=models.SET_NULL, null=True, blank=True ) created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) def __str__(self): return f"Draft: {self.beneficiary} - {self.measure_name} ({self.status})" class Meta: app_label = "curation" class CurationReviewEvent(models.Model): id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) draft = models.ForeignKey( CuratedObservationDraft, on_delete=models.PROTECT, related_name="review_events" ) reviewer = models.ForeignKey("identity.User", on_delete=models.PROTECT) review_type = models.TextField() decision = models.TextField() comments = models.TextField(null=True, blank=True) corrected_value_text = models.TextField(null=True, blank=True) corrected_value_numeric = models.FloatField(null=True, blank=True) reviewed_at = models.DateTimeField(auto_now_add=True) def __str__(self): return f"Review: {self.draft} - {self.reviewer} ({self.decision})" class Meta: app_label = "curation" class ReportingView(models.Model): id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) view_name = models.TextField() view_sql = models.TextField() aggregation_level = models.TextField() is_public_reportable = models.BooleanField(default=False) suppression_required = models.BooleanField(default=False) data_origin = models.TextField() is_active = models.BooleanField(default=True) created_at = models.DateTimeField(auto_now_add=True) def __str__(self): return f"View: {self.view_name} ({self.aggregation_level})" class Meta: app_label = "curation" class AggregateOutcome(models.Model): id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) clinical_track = models.TextField(choices=TRACK_CHOICES) measure_name = models.TextField() period_start = models.DateField() period_end = models.DateField() denominator = models.IntegerField(default=0) numerator = models.IntegerField(default=0) rate_or_mean = models.FloatField(default=0) cell_size = models.IntegerField(default=0) suppression_required = models.BooleanField(default=False) data_origin = models.TextField() created_at = models.DateTimeField(auto_now_add=True) def __str__(self): return f"Outcome: {self.measure_name} ({self.clinical_track}) - {self.rate_or_mean}" class Meta: app_label = "curation" class SuppressionRule(models.Model): id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) rule_name = models.TextField() min_cell_size = models.IntegerField(default=10) applies_to = models.TextField(default="all") is_active = models.BooleanField(default=True) created_at = models.DateTimeField(auto_now_add=True) def __str__(self): return f"Suppression: {self.rule_name} (min={self.min_cell_size})" class Meta: app_label = "curation" class PublicReportExport(models.Model): id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) report_name = models.TextField() export_date = models.DateField() storage_path = models.TextField() approved_by = models.ForeignKey("identity.User", on_delete=models.PROTECT) approved_at = models.DateTimeField(auto_now_add=True) is_published = models.BooleanField(default=False) created_at = models.DateTimeField(auto_now_add=True) def __str__(self): return f"Export: {self.report_name} ({self.export_date})" class Meta: app_label = "curation"