ServicesCrelio AppArchitectureApp Modules

report

Lab reports, signing workflow, smart reports, and reflex testing

Report App Architecture

Domain Responsibility

What This App Owns

  • Lab reports (LabReportRelation) - Report lifecycle and signing
  • Report values - Test results and parameters
  • Smart reports - AI-generated interpretive reports
  • Reflex testing - Automatic test triggering
  • Report formats - Display templates
  • Amendments - Report corrections and history

What It Depends On

  • core/ - BaseModel, utilities
  • admin/ - Tests, doctors, departments
  • patient/ - Patient details
  • finance/ - Bill for report context

What Should NOT Be Added Here

  • Billing logic (belongs in finance/)
  • Patient demographics (belongs in patient/)
  • Device interfacing (belongs in interfacing/)

Model-Centric Design (Fat Models)

Key Models

ModelResponsibilityKey FieldsCross-App Relations
LabReportRelationCore report entityisSigned, isApproved, syncStatuspatient.UserDetails, finance.Billing
SmartReportInterpretive reportsname, template, test_idsadmin.AllTests
ReflexTestConfigurationAuto-trigger rulestest, parameter_rules, is_activeadmin.AllTests
ReportFormatDisplay configurationformat_type, ranges, templateadmin.AllTests
ReportAmendmentHistoryAudit trailprevious_values, amended_byLabReportRelation

Business Logic in Models

LabReportRelation (report/models/lab_report_relation.py) - 808 lines

Key Methods:

MethodPurpose
after_save()Post-save processing
validate_hooks()Validate report generation hooks
prepare_report_filters()Build query filters
fetch_org_records()Organization-scoped queries
fetch_staff_records()Staff-scoped queries
prepare_department_filters()Department access control
update_lab_report_and_report_value_with_path()Update file paths

Status Fields:

FieldValuesMeaning
isSigned0/1Report signed by doctor
isPartialSigned0/1Partially signed
isApproved0/1Approved for release
syncStatusVariousES sync status

SmartReport (report/models/smart_report.py) - 1787 lines

Key Methods:

MethodPurpose
before_save()Set timestamps
after_save()Activity logging
get_critical_parameters()Find out-of-range values
compute_department_stats()Statistics by department
prepare_context()Build report context
generate_report()Render HTML/PDF
get_graph()Generate matplotlib charts
get_components()Fetch template components
render_component()Render individual component

ReflexTestConfiguration (report/models/reflex_test_config.py) - 1279 lines

Key Methods:

MethodPurpose
validate_payload()Comprehensive validation
save_reflex_test_config()Create configuration
update_reflex_test_config()Update with rules
get_reflex_test_configs()Fetch configurations
create_parameter_rules()Create rule relations
check_and_trigger_reflex_tests()Auto-trigger logic

Why Fat Model Style Here

Report logic is complex with:

  1. Multiple signing states and workflows
  2. Rule-based auto-triggering
  3. Complex rendering with graphs
  4. Department-based access control

All this requires consistent logic regardless of entry point.


Invariants & Data Rules

Report State Machine

Reflex Test Rules

Rule TypeTrigger Condition
RANGEValue in/out of normal range
LISTValue matches list item
DESCRIPTIVEText contains pattern
CUSTOM_RANGEValue in custom bounds

Validation Rules

# report/models/reflex_test_config.py
@classmethod
def validate_payload(cls, payload, lab_id):
    # Required fields
    if not payload.get("test_id"):
        raise ValidationError("test_id is required")
    
    # Parameter rules validation
    cls._validate_range_parameter_rules(rules)
    cls._validate_reflex_tests_uniqueness(rules)
    cls._validate_test_ids_exist(test_id, reflex_ids, lab_id)

Data Access Patterns

QuerySet Optimization

# report/models/reflex_test_config.py
@classmethod
def get_reflex_test_configs(cls, lab_id, reflex_test_id=None):
    qs = cls.objects.filter(
        lab_id=lab_id, is_active=True
    ).select_related(
        "test"
    ).prefetch_related(
        "reflextestparameterrules_set",
        "reflextestparameterrules_set__reflex_tests"
    )
    return qs

Transaction Usage

# report/models/reflex_test_config.py
@classmethod
@transaction.atomic
def update_reflex_test_config(cls, payload, reflex_test_id, session):
    instance = cls.objects.get(id=reflex_test_id)
    # Delete old rules
    instance.reflextestparameterrules_set.all().delete()
    # Create new rules
    cls.create_parameter_rules(instance, rules)
    instance.add_activity_log("updated", session=session)

API Layer (Wiring)

Endpoint Map

EndpointViewModel Method
GET /report/listReportListViewLabReportRelation.fetch_*_records()
POST /report/signReportSignViewVia device results
POST /report/amendAmendReportViewAtomic update
GET /report/smart/{id}SmartReportViewSmartReport.generate_report()
POST /report/reflexReflexConfigViewReflexTestConfiguration.save_*()

View Pattern

# report/views/smart_report.py
class SmartReportView(GenericView):
    
    @transaction.atomic
    def get(self, request, smart_report_id, bill_id, *args, **kwargs):
        smart_report = SmartReport.objects.get(id=smart_report_id)
        bill = Billing.objects.get(pk=bill_id)
        
        html = smart_report.generate_report(
            request, bill, is_pdf=False
        )
        
        return HttpResponse(html)

Integrations

External Systems

IntegrationDirectionPurpose
ElasticsearchOutboundReport search/sync
Fusion WorkerOutboundReport notifications
PDF RendererInternalReport generation
MatplotlibInternalChart generation

Notification Flow

Reports trigger notifications via CommunicationBase:

  • SMS on sign completion
  • WhatsApp with report link
  • Email with PDF attachment

Side Effects & Hidden Coupling

after_save() Side Effects

# report/models/lab_report_relation.py
def after_save(self, *args, **kwargs):
    # Activity logging
    # ES sync (if enabled)
    # Webhook triggers

Cross-App Imports

ImportUsed For
patient.models.user_details.UserDetailsPatient info
finance.models.billing.BillingBill context
admin.masters.models.all_tests.AllTestsTest catalog
admin.account.models.departments.DepartmentsAccess control

Performance & Scaling Notes

Hot Tables

TableVolumeConcern
labReportRelationVery highMany per bill
reportFormatHighPer test/parameter
smartReportMediumPer lab

SmartReport Rendering

[!WARNING] SmartReport.generate_report() does synchronous matplotlib rendering. For large reports, consider:

  • Caching generated images
  • Async generation via Fusion
  • Pre-computing common graphs

Query Optimization

# Use department filters
LabReportRelation.objects.filter(
    labId_id=lab_id,
    department_id__in=user_departments
).select_related(
    "userDetailsId",
    "billId"
)

Safe Extension Guide

Adding New Report Type

  1. Add model or extend LabReportRelation
  2. Add format handling in ReportFormat
  3. Create view for rendering
  4. Register in URL patterns

Adding New Reflex Rule Type

  1. Add to parameter type choices
  2. Add validation in _validate_*_parameter_rules()
  3. Add trigger logic in check_and_trigger_reflex_tests()

Adding Smart Report Component

  1. Create component template
  2. Link via SmartReportComponentDetails
  3. Implement render method

Patterns to Follow

  • Use @transaction.atomic for rule updates
  • Validate rules before saving
  • Log activities for audit

Patterns to Avoid

  • Synchronous PDF generation in hot paths
  • N+1 queries in report lists
  • Skipping department access checks

File Map

FilePurpose
report/models/init.pyModel exports (52 files)
report/models/lab_report_relation.pyCore report model (808 lines)
report/models/smart_report.pySmart reports (1787 lines)
report/models/reflex_test_config.pyReflex testing (1279 lines)
report/models/report_format.pyDisplay formats
report/models/report_amendment_history.pyAmendment audit
report/models/sample_rerun.pySample rerun logic
report/proxies/Proxy models (8 files)
report/views/API views (27 files)
report/mappers/Data mappers
report/decorators.pyView decorators
report/constants.pyConstants

On this page