clqms-be/docs/audit-logging-plan.md
mahdahar d173098652 feat: implement audit logging and test management enhancements
Major Features:
- Add comprehensive audit logging system with AuditService
- Create AuditLogs database migration for tracking changes
- Implement TestValidationService for test data validation
- Add FRONTEND_TEST_MANAGEMENT_PROMPT.md documentation

Controllers:
- Update TestsController with improved test management

Models:
- Enhance PatientModel with additional functionality
- Update TestDefSiteModel for better site management

Database:
- Add CreateAuditLogs migration (2026-02-20-000011)
- Update TestSeeder with new test data

Services:
- Add AuditService for comprehensive audit trail logging

Documentation:
- Update AGENTS.md with improved guidelines
- Update audit-logging-plan.md with implementation details
- Add FRONTEND_TEST_MANAGEMENT_PROMPT.md for frontend guidance

API Documentation:
- Update api-docs.bundled.yaml
- Update tests.yaml schema definitions
- Update tests.yaml paths

Testing:
- Enhance TestsControllerTest with new test cases
- Update TestDefModelsTest for model coverage
2026-02-20 13:47:47 +07:00

28 KiB

Audit Logging Architecture Plan for CLQMS

Clinical Laboratory Quality Management System (CLQMS) - Comprehensive audit trail implementation based on section 4.2.1.20 Error Management requirements, implementing 5W1H audit principles across four specialized log types.


Executive Summary

This document defines the audit logging architecture for CLQMS, implementing the 5W1H audit principle (What, When, Who, How, Where, Why) across four specialized log tables. The design supports both manual (user-initiated) and automatic (instrument/service-initiated) operations with complete traceability.


1. Requirements Analysis (Section 4.2.1.20)

5W1H Audit Principles

Dimension Description Captured Fields
What Data changed, operation performed operation, table_name, field_name, previous_value, new_value
When Timestamp of activity created_at
Who User performing operation user_id
How Mechanism, application, session mechanism, application_id, web_page, session_id, event_type
Where Location of operation site_id, workstation_id, pc_name, ip_address
Why Reason for operation reason

Four Log Types

Log Type Description Examples
Data Log Events related to data operations Patient demographics, visits, test orders, samples, results, user data, master data, archiving, transaction errors
Service Log Background service events Host communication, instrument communication, printing, messaging, resource access, system errors
Security Log Security and access events Logins/logouts, file access, permission changes, password failures, system changes
Error Log Error events by entity Instrument errors, integration errors, validation errors

Mechanism Types

  • MANUAL: User-initiated actions via web interface
  • AUTOMATIC: System/instrument-initiated (duplo/repeated operations)

2. Table Architecture

2.1 Overview

Four separate tables optimized for different volumes and retention:

Table Volume Retention Partitioning
data_audit_log Medium 7 years Monthly
service_audit_log Very High 2 years Monthly
security_audit_log Low Permanent No
error_audit_log Variable 5 years Monthly

2.2 Table: data_audit_log

CREATE TABLE data_audit_log (
    id                  BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    
    -- WHAT: Operation and data details
    operation           VARCHAR(50) NOT NULL,            -- 'CREATE', 'UPDATE', 'DELETE', 'ARCHIVE', etc.
    entity_type         VARCHAR(50) NOT NULL,            -- 'patient', 'visit', 'test_order', 'sample', 'user', etc.
    entity_id           VARCHAR(36) NOT NULL,            -- ID of affected entity
    table_name          VARCHAR(100),                    -- Database table name
    field_name          VARCHAR(100),                    -- Specific field changed (NULL if multiple)
    previous_value      JSON,                          -- Value before change
    new_value           JSON,                          -- Value after change
    
    -- HOW: Mechanism details
    mechanism           ENUM('MANUAL', 'AUTOMATIC') NOT NULL DEFAULT 'MANUAL',
    application_id      VARCHAR(50),                     -- Application identifier
    web_page            VARCHAR(500),                    -- URL/endpoint accessed
    session_id          VARCHAR(100),                    -- Session identifier
    event_type          VARCHAR(100),                    -- Event classification
    
    -- WHERE: Location information
    site_id             VARCHAR(36),                     -- Site/location ID
    workstation_id      VARCHAR(36),                     -- Workstation ID
    pc_name             VARCHAR(100),                    -- Computer name
    ip_address          VARCHAR(45),                     -- IP address (IPv6 compatible)
    
    -- WHO: User information
    user_id             VARCHAR(36) NOT NULL,            -- User ID or 'SYSTEM' for automatic
    
    -- WHEN: Timestamp
    created_at          DATETIME DEFAULT CURRENT_TIMESTAMP,
    
    -- WHY: Reason
    reason              TEXT,                            -- User-provided reason
    
    -- Context: Additional flexible data
    context             JSON,                          -- Log-type-specific extra data
    
    -- Indexes
    INDEX idx_operation_created (operation, created_at),
    INDEX idx_entity (entity_type, entity_id, created_at),
    INDEX idx_user_created (user_id, created_at),
    INDEX idx_mechanism (mechanism, created_at),
    INDEX idx_table (table_name, created_at),
    INDEX idx_site (site_id, created_at),
    INDEX idx_created (created_at),
    INDEX idx_session (session_id, created_at)
    
) ENGINE=InnoDB
PARTITION BY RANGE (YEAR(created_at) * 100 + MONTH(created_at)) (
    PARTITION p202601 VALUES LESS THAN (202602),
    PARTITION p202602 VALUES LESS THAN (202603),
    PARTITION p_future VALUES LESS THAN MAXVALUE
);

Data Log Examples:

  • Patient registration: operation='CREATE', entity_type='patient', mechanism='MANUAL'
  • Sample result from instrument: operation='UPDATE', entity_type='result', mechanism='AUTOMATIC'
  • User profile update: operation='UPDATE', entity_type='user', mechanism='MANUAL'

2.3 Table: service_audit_log

CREATE TABLE service_audit_log (
    id                  BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    
    -- WHAT: Service operation details
    operation           VARCHAR(50) NOT NULL,            -- 'COMMUNICATION', 'PRINT', 'BACKUP', 'MESSAGE', etc.
    entity_type         VARCHAR(50) NOT NULL,            -- 'host', 'instrument', 'database', 'network', etc.
    entity_id           VARCHAR(36) NOT NULL,            -- Service identifier
    service_class       VARCHAR(50),                     -- 'communication', 'printing', 'messaging', 'resource'
    resource_type       VARCHAR(100),                    -- 'database_access', 'backup', 'network', 'internet'
    resource_details    JSON,                          -- IP, port, connection details
    previous_value      JSON,                          -- State before
    new_value           JSON,                          -- State after
    
    -- HOW: Mechanism and context
    mechanism           ENUM('MANUAL', 'AUTOMATIC') NOT NULL DEFAULT 'AUTOMATIC',
    application_id      VARCHAR(50),                     -- Service application ID
    service_name        VARCHAR(100),                    -- Background service name
    session_id          VARCHAR(100),                    -- Service session
    event_type          VARCHAR(100),                    -- Event classification
    
    -- WHERE: Location and resources
    site_id             VARCHAR(36),
    workstation_id      VARCHAR(36),
    pc_name             VARCHAR(100),
    ip_address          VARCHAR(45),
    port                INT,                           -- Port number for network
    
    -- WHO: System or user
    user_id             VARCHAR(36) NOT NULL,            -- 'SYSTEM' for automatic services
    
    -- WHEN: Timestamp
    created_at          DATETIME DEFAULT CURRENT_TIMESTAMP,
    
    -- WHY: Reason if manual
    reason              TEXT,
    
    -- Context: Service-specific data
    context             JSON,                          -- Communication details, error codes, etc.
    
    -- Indexes
    INDEX idx_operation_created (operation, created_at),
    INDEX idx_entity (entity_type, entity_id, created_at),
    INDEX idx_service_class (service_class, created_at),
    INDEX idx_user_created (user_id, created_at),
    INDEX idx_mechanism (mechanism, created_at),
    INDEX idx_site (site_id, created_at),
    INDEX idx_created (created_at)
    
) ENGINE=InnoDB
PARTITION BY RANGE (YEAR(created_at) * 100 + MONTH(created_at)) (
    PARTITION p202601 VALUES LESS THAN (202602),
    PARTITION p202602 VALUES LESS THAN (202603),
    PARTITION p_future VALUES LESS THAN MAXVALUE
);

Service Log Examples:

  • Instrument communication: operation='COMMUNICATION', entity_type='instrument', service_class='communication'
  • Database backup: operation='BACKUP', entity_type='database', service_class='resource'
  • Automatic print: operation='PRINT', service_class='printing', mechanism='AUTOMATIC'

2.4 Table: security_audit_log

CREATE TABLE security_audit_log (
    id                  BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    
    -- WHAT: Security event details
    operation           VARCHAR(50) NOT NULL,            -- 'LOGIN', 'LOGOUT', 'ACCESS_DENIED', 'PASSWORD_FAIL', etc.
    entity_type         VARCHAR(50) NOT NULL,            -- 'user', 'file', 'folder', 'setting', 'application'
    entity_id           VARCHAR(36) NOT NULL,            -- Target entity ID
    security_class      VARCHAR(50),                     -- 'authentication', 'authorization', 'system_change'
    resource_path       VARCHAR(500),                    -- File/folder path accessed
    previous_value      JSON,                          -- Previous security state
    new_value           JSON,                          -- New security state
    
    -- HOW: Access details
    mechanism           ENUM('MANUAL', 'AUTOMATIC') NOT NULL DEFAULT 'MANUAL',
    application_id      VARCHAR(50),
    web_page            VARCHAR(500),
    session_id          VARCHAR(100),
    event_type          VARCHAR(100),                    -- 'SUCCESS', 'FAILURE', 'WARNING'
    
    -- WHERE: Access location
    site_id             VARCHAR(36),
    workstation_id      VARCHAR(36),
    pc_name             VARCHAR(100),
    ip_address          VARCHAR(45),
    
    -- WHO: User attempting action
    user_id             VARCHAR(36) NOT NULL,            -- User ID or 'UNKNOWN' for failed attempts
    
    -- WHEN: Timestamp
    created_at          DATETIME DEFAULT CURRENT_TIMESTAMP,
    
    -- WHY: Reason if provided
    reason              TEXT,
    
    -- Context: Security-specific data
    context             JSON,                          -- Permission changes, failure counts, etc.
    
    -- Indexes
    INDEX idx_operation_created (operation, created_at),
    INDEX idx_entity (entity_type, entity_id, created_at),
    INDEX idx_security_class (security_class, created_at),
    INDEX idx_user_created (user_id, created_at),
    INDEX idx_event_type (event_type, created_at),
    INDEX idx_site (site_id, created_at),
    INDEX idx_created (created_at),
    INDEX idx_session (session_id, created_at)
    
) ENGINE=InnoDB;

Security Log Examples:

  • User login: operation='LOGIN', entity_type='user', security_class='authentication', event_type='SUCCESS'
  • Failed password: operation='PASSWORD_FAIL', entity_type='user', security_class='authentication', event_type='FAILURE'
  • Permission change: operation='UPDATE', entity_type='user', security_class='authorization'
  • File access: operation='ACCESS', entity_type='file', security_class='authorization'

2.5 Table: error_audit_log

CREATE TABLE error_audit_log (
    id                  BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    
    -- WHAT: Error details
    operation           VARCHAR(50) NOT NULL,            -- 'ERROR', 'WARNING', 'CRITICAL'
    entity_type         VARCHAR(50) NOT NULL,            -- 'instrument', 'integration', 'database', 'validation'
    entity_id           VARCHAR(36) NOT NULL,            -- Entity where error occurred
    error_code          VARCHAR(50),                     -- Specific error code
    error_message       TEXT,                          -- Error message
    error_details       JSON,                          -- Stack trace, context
    previous_value      JSON,                          -- State before error
    new_value           JSON,                          -- State after error (if recovered)
    
    -- HOW: Error context
    mechanism           ENUM('MANUAL', 'AUTOMATIC') NOT NULL DEFAULT 'MANUAL',
    application_id      VARCHAR(50),
    web_page            VARCHAR(500),
    session_id          VARCHAR(100),
    event_type          VARCHAR(100),                    -- 'TRANSACTION_ERROR', 'SYSTEM_ERROR', 'VALIDATION_ERROR'
    
    -- WHERE: Error location
    site_id             VARCHAR(36),
    workstation_id      VARCHAR(36),
    pc_name             VARCHAR(100),
    ip_address          VARCHAR(45),
    
    -- WHO: User or system
    user_id             VARCHAR(36) NOT NULL,            -- User ID or 'SYSTEM'
    
    -- WHEN: Timestamp
    created_at          DATETIME DEFAULT CURRENT_TIMESTAMP,
    
    -- WHY: Error context
    reason              TEXT,                          -- Why the error occurred
    
    -- Context: Additional error data
    context             JSON,                          -- Related IDs, transaction info, etc.
    
    -- Indexes
    INDEX idx_operation_created (operation, created_at),
    INDEX idx_entity (entity_type, entity_id, created_at),
    INDEX idx_error_code (error_code, created_at),
    INDEX idx_event_type (event_type, created_at),
    INDEX idx_user_created (user_id, created_at),
    INDEX idx_site (site_id, created_at),
    INDEX idx_created (created_at)
    
) ENGINE=InnoDB
PARTITION BY RANGE (YEAR(created_at) * 100 + MONTH(created_at)) (
    PARTITION p202601 VALUES LESS THAN (202602),
    PARTITION p202602 VALUES LESS THAN (202603),
    PARTITION p_future VALUES LESS THAN MAXVALUE
);

Error Log Examples:

  • Transaction error: operation='ERROR', entity_type='database', event_type='TRANSACTION_ERROR'
  • Instrument error: operation='ERROR', entity_type='instrument', event_type='SYSTEM_ERROR'
  • Integration error: operation='ERROR', entity_type='integration', event_type='SYSTEM_ERROR'
  • Validation error: operation='ERROR', entity_type='validation', event_type='VALIDATION_ERROR'

3. Example Audit Entries

3.1 Data Log Entry (Patient Update)

{
  "id": 15243,
  "operation": "UPDATE",
  "entity_type": "patient",
  "entity_id": "PAT-2026-001234",
  "table_name": "patients",
  "field_name": null,
  "previous_value": {
    "NameFirst": "John",
    "NameLast": "Doe",
    "Phone": "+1-555-0100"
  },
  "new_value": {
    "NameFirst": "Johnny",
    "NameLast": "Doe-Smith",
    "Phone": "+1-555-0199"
  },
  "mechanism": "MANUAL",
  "application_id": "CLQMS-WEB",
  "web_page": "/api/patient/PAT-2026-001234",
  "session_id": "sess_abc123",
  "event_type": "PATIENT_UPDATE",
  "site_id": "SITE-001",
  "workstation_id": "WS-001",
  "pc_name": "LAB-PC-01",
  "ip_address": "192.168.1.100",
  "user_id": "USR-001",
  "created_at": "2026-02-19T14:30:00Z",
  "reason": "Patient requested name change after marriage",
  "context": {
    "changed_fields": ["NameFirst", "NameLast", "Phone"],
    "validation_status": "PASSED"
  }
}

3.2 Service Log Entry (Instrument Communication)

{
  "id": 89345,
  "operation": "COMMUNICATION",
  "entity_type": "instrument",
  "entity_id": "INST-001",
  "service_class": "communication",
  "resource_type": "instrument_communication",
  "resource_details": {
    "protocol": "HL7",
    "port": 2575,
    "direction": "INBOUND"
  },
  "previous_value": { "status": "IDLE" },
  "new_value": { "status": "RECEIVING" },
  "mechanism": "AUTOMATIC",
  "application_id": "INSTRUMENT-SERVICE",
  "service_name": "instrument-listener",
  "session_id": "svc_inst_001",
  "event_type": "RESULT_RECEIVED",
  "site_id": "SITE-001",
  "workstation_id": "WS-LAB-01",
  "pc_name": "LAB-SERVER-01",
  "ip_address": "192.168.1.10",
  "port": 2575,
  "user_id": "SYSTEM",
  "created_at": "2026-02-19T14:35:22Z",
  "reason": null,
  "context": {
    "sample_id": "SMP-2026-004567",
    "test_count": 5,
    "bytes_received": 2048
  }
}

3.3 Security Log Entry (Failed Login)

{
  "id": 4521,
  "operation": "PASSWORD_FAIL",
  "entity_type": "user",
  "entity_id": "USR-999",
  "security_class": "authentication",
  "resource_path": "/api/auth/login",
  "previous_value": { "failed_attempts": 2 },
  "new_value": { "failed_attempts": 3 },
  "mechanism": "MANUAL",
  "application_id": "CLQMS-WEB",
  "web_page": "/login",
  "session_id": "sess_fail_789",
  "event_type": "FAILURE",
  "site_id": "SITE-002",
  "workstation_id": "WS-RECEPTION",
  "pc_name": "RECEPTION-PC-02",
  "ip_address": "203.0.113.45",
  "user_id": "USR-999",
  "created_at": "2026-02-19T15:10:05Z",
  "reason": null,
  "context": {
    "lockout_threshold": 5,
    "remaining_attempts": 2,
    "username_attempted": "john.doe"
  }
}

3.4 Error Log Entry (Database Transaction Failure)

{
  "id": 1203,
  "operation": "ERROR",
  "entity_type": "database",
  "entity_id": "DB-PRIMARY",
  "error_code": "DB_TXN_001",
  "error_message": "Transaction rollback due to deadlock",
  "error_details": {
    "sql_state": "40001",
    "error_number": 1213,
    "deadlock_victim": true
  },
  "previous_value": { "transaction_status": "ACTIVE" },
  "new_value": { "transaction_status": "ROLLED_BACK" },
  "mechanism": "AUTOMATIC",
  "application_id": "CLQMS-WEB",
  "web_page": "/api/orders/batch-update",
  "session_id": "sess_xyz789",
  "event_type": "TRANSACTION_ERROR",
  "site_id": "SITE-001",
  "workstation_id": "WS-001",
  "pc_name": "LAB-PC-01",
  "ip_address": "192.168.1.100",
  "user_id": "USR-001",
  "created_at": "2026-02-19T15:15:30Z",
  "reason": "Deadlock detected during batch update",
  "context": {
    "affected_tables": ["orders", "order_tests"],
    "retry_count": 0,
    "transaction_id": "txn_20260219151530"
  }
}

4. Implementation Strategy

4.1 Central Audit Service

<?php

namespace App\Services;

class AuditService
{
    /**
     * Log a DATA audit event
     */
    public static function logData(
        string $operation,
        string $entityType,
        string $entityId,
        ?string $tableName = null,
        ?string $fieldName = null,
        ?array $previousValue = null,
        ?array $newValue = null,
        ?string $reason = null,
        ?array $context = null
    ): void {
        self::log('data_audit_log', [
            'operation'      => $operation,
            'entity_type'    => $entityType,
            'entity_id'      => $entityId,
            'table_name'     => $tableName,
            'field_name'     => $fieldName,
            'previous_value' => $previousValue ? json_encode($previousValue) : null,
            'new_value'      => $newValue ? json_encode($newValue) : null,
            'mechanism'      => 'MANUAL',
            'application_id' => 'CLQMS-WEB',
            'web_page'       => $_SERVER['REQUEST_URI'] ?? null,
            'session_id'     => session_id(),
            'event_type'     => strtoupper($entityType) . '_' . strtoupper($operation),
            'site_id'        => session('site_id'),
            'workstation_id' => session('workstation_id'),
            'pc_name'        => gethostname(),
            'ip_address'     => $_SERVER['REMOTE_ADDR'] ?? null,
            'user_id'        => auth()->id() ?? 'SYSTEM',
            'reason'         => $reason,
            'context'        => $context ? json_encode($context) : null,
            'created_at'     => date('Y-m-d H:i:s')
        ]);
    }
    
    /**
     * Log a SERVICE audit event
     */
    public static function logService(
        string $operation,
        string $entityType,
        string $entityId,
        string $serviceClass,
        ?string $resourceType = null,
        ?array $resourceDetails = null,
        ?array $previousValue = null,
        ?array $newValue = null,
        ?string $serviceName = null,
        ?array $context = null
    ): void {
        self::log('service_audit_log', [
            'operation'        => $operation,
            'entity_type'      => $entityType,
            'entity_id'        => $entityId,
            'service_class'    => $serviceClass,
            'resource_type'    => $resourceType,
            'resource_details' => $resourceDetails ? json_encode($resourceDetails) : null,
            'previous_value'   => $previousValue ? json_encode($previousValue) : null,
            'new_value'        => $newValue ? json_encode($newValue) : null,
            'mechanism'        => 'AUTOMATIC',
            'application_id'   => $serviceName ?? 'SYSTEM-SERVICE',
            'service_name'     => $serviceName,
            'session_id'       => session_id() ?: 'service_session',
            'event_type'       => strtoupper($serviceClass) . '_' . strtoupper($operation),
            'site_id'          => session('site_id'),
            'workstation_id'   => session('workstation_id'),
            'pc_name'          => gethostname(),
            'ip_address'       => $_SERVER['REMOTE_ADDR'] ?? null,
            'port'             => $resourceDetails['port'] ?? null,
            'user_id'          => 'SYSTEM',
            'reason'           => null,
            'context'          => $context ? json_encode($context) : null,
            'created_at'       => date('Y-m-d H:i:s')
        ]);
    }
    
    /**
     * Log a SECURITY audit event
     */
    public static function logSecurity(
        string $operation,
        string $entityType,
        string $entityId,
        string $securityClass,
        ?string $eventType = 'SUCCESS',
        ?string $resourcePath = null,
        ?array $previousValue = null,
        ?array $newValue = null,
        ?string $reason = null,
        ?array $context = null
    ): void {
        self::log('security_audit_log', [
            'operation'      => $operation,
            'entity_type'    => $entityType,
            'entity_id'      => $entityId,
            'security_class' => $securityClass,
            'resource_path'  => $resourcePath,
            'previous_value' => $previousValue ? json_encode($previousValue) : null,
            'new_value'      => $newValue ? json_encode($newValue) : null,
            'mechanism'      => 'MANUAL',
            'application_id' => 'CLQMS-WEB',
            'web_page'       => $_SERVER['REQUEST_URI'] ?? null,
            'session_id'     => session_id(),
            'event_type'     => $eventType,
            'site_id'        => session('site_id'),
            'workstation_id' => session('workstation_id'),
            'pc_name'        => gethostname(),
            'ip_address'     => $_SERVER['REMOTE_ADDR'] ?? null,
            'user_id'        => auth()->id() ?? 'UNKNOWN',
            'reason'         => $reason,
            'context'        => $context ? json_encode($context) : null,
            'created_at'     => date('Y-m-d H:i:s')
        ]);
    }
    
    /**
     * Log an ERROR audit event
     */
    public static function logError(
        string $entityType,
        string $entityId,
        string $errorCode,
        string $errorMessage,
        string $eventType,
        ?array $errorDetails = null,
        ?array $previousValue = null,
        ?array $newValue = null,
        ?string $reason = null,
        ?array $context = null
    ): void {
        self::log('error_audit_log', [
            'operation'      => 'ERROR',
            'entity_type'    => $entityType,
            'entity_id'      => $entityId,
            'error_code'     => $errorCode,
            'error_message'  => $errorMessage,
            'error_details'  => $errorDetails ? json_encode($errorDetails) : null,
            'previous_value' => $previousValue ? json_encode($previousValue) : null,
            'new_value'      => $newValue ? json_encode($newValue) : null,
            'mechanism'      => 'AUTOMATIC',
            'application_id' => 'CLQMS-WEB',
            'web_page'       => $_SERVER['REQUEST_URI'] ?? null,
            'session_id'     => session_id() ?: 'system',
            'event_type'     => $eventType,
            'site_id'        => session('site_id'),
            'workstation_id' => session('workstation_id'),
            'pc_name'        => gethostname(),
            'ip_address'     => $_SERVER['REMOTE_ADDR'] ?? null,
            'user_id'        => auth()->id() ?? 'SYSTEM',
            'reason'         => $reason,
            'context'        => $context ? json_encode($context) : null,
            'created_at'     => date('Y-m-d H:i:s')
        ]);
    }
    
    /**
     * Generic log method with async support
     */
    private static function log(string $table, array $data): void
    {
        // For high-volume operations, dispatch to queue
        if (in_array($table, ['service_audit_log', 'error_audit_log'])) {
            self::dispatchAuditJob($table, $data);
        } else {
            // Direct insert for data and security logs
            \Config\Database::connect()->table($table)->insert($data);
        }
    }
    
    private static function dispatchAuditJob(string $table, array $data): void
    {
        // Implementation: Queue the audit entry for async processing
        // This prevents blocking user operations during high-volume logging
    }
}

5. Query Patterns

5.1 Common Audit Queries

-- View patient history (DATA log)
SELECT * FROM data_audit_log 
WHERE entity_type = 'patient' 
AND entity_id = 'PAT-2026-001234'
ORDER BY created_at DESC;

-- User activity report
SELECT operation, entity_type, COUNT(*) as count
FROM data_audit_log
WHERE user_id = 'USR-001'
AND created_at > DATE_SUB(NOW(), INTERVAL 7 DAY)
GROUP BY operation, entity_type;

-- Instrument communication history (SERVICE log)
SELECT * FROM service_audit_log
WHERE entity_type = 'instrument'
AND entity_id = 'INST-001'
AND operation = 'COMMUNICATION'
ORDER BY created_at DESC;

-- Failed login attempts (SECURITY log)
SELECT * FROM security_audit_log
WHERE operation IN ('PASSWORD_FAIL', 'ACCESS_DENIED')
AND event_type = 'FAILURE'
AND created_at > DATE_SUB(NOW(), INTERVAL 24 HOUR)
ORDER BY created_at DESC;

-- Recent errors (ERROR log)
SELECT * FROM error_audit_log
WHERE created_at > DATE_SUB(NOW(), INTERVAL 1 HOUR)
AND event_type = 'CRITICAL'
ORDER BY created_at DESC;

-- Find changes to specific field
SELECT * FROM data_audit_log
WHERE table_name = 'patients'
AND field_name = 'Phone'
AND entity_id = 'PAT-2026-001234'
ORDER BY created_at DESC;

6. Migration Plan

Phase 1: Foundation (Week 1)

  • Drop existing unused tables (patreglog, patvisitlog, specimenlog)
  • Create 4 new audit tables with partitioning
  • Create AuditService class
  • Add database indexes

Phase 2: Core Implementation (Week 2)

  • Integrate data_audit_log into Patient model
  • Integrate data_audit_log into Order/Test models
  • Integrate data_audit_log into Master data models
  • Integrate security_audit_log into authentication

Phase 3: Service & Error Logging (Week 3)

  • Implement service_audit_log for instrument communication
  • Implement service_audit_log for printing/messaging
  • Implement error_audit_log for database errors
  • Implement error_audit_log for instrument errors
  • Implement error_audit_log for integration errors

Phase 4: API & Optimization (Week 4)

  • Create unified API endpoint for querying all log types
  • Add filters by log_type, date range, user, entity
  • Implement async logging queue
  • Add log export functionality (CSV/PDF)

7. Retention Strategy (TBD)

Table Proposed Retention Notes
data_audit_log 7 years Patient data compliance
service_audit_log 2 years High volume, operational only
security_audit_log Permanent Compliance and forensics
error_audit_log 5 years Debugging and incident analysis

8. Key Design Decisions

Decision Choice Rationale
Table Count 4 tables Separates by log type, different retention needs
5W1H All 6 dimensions captured Complete audit trail per section 4.2.1.20
Mechanism MANUAL vs AUTOMATIC Distinguishes user vs instrument operations
User for AUTO 'SYSTEM' Clear identification of automatic operations
JSON Storage previous_value, new_value, context Flexible schema evolution
Partitioning Monthly for high-volume tables Manage service and error log growth
Async Logging Yes for service/error logs Don't block user operations

Document Version: 2.0
Based on: Section 4.2.1.20 Error Management
Date: February 20, 2026