Skip to content

Plan: REDCap System Structure Gap Analysis & Remediation

Overview

This plan identifies gaps between REDCap's conceptual system structure (as documented in docs/plans/redcap-system-structure.md) and Metricis's current implementation, then proposes targeted remediation.


Gap Analysis Summary

REDCap Concept Metricis Status Gap Severity Remediation
8 User Roles 6 Study Roles Low Mapping is appropriate for EDC context
Data Access Groups (DAGs) Implemented None UserStudy.site_id filtering works
Per-Instrument Permissions Not Implemented Medium Future enhancement
Record Locking Not Implemented Medium Add for GCP compliance
E-Signatures Not Implemented High Required for 21 CFR Part 11
API/Integration Users Partial Medium Add dedicated API key model
Audit Trail Implemented Low AuditLog model exists
Project Lifecycle States Partial Low Study has status but no enforcement

Detailed Gap Analysis

Gap 1: E-Signatures (HIGH PRIORITY)

REDCap Feature:

  • Record-level e-signatures for GCP/21 CFR Part 11 compliance
  • Signature locks record from further editing
  • Captures username, timestamp, reason

Current State:

  • No e-signature support in Metricis
  • Participants can be edited freely

Impact:

  • Cannot use Metricis for FDA-regulated trials requiring 21 CFR Part 11 compliance
  • No audit-ready proof of data integrity

Proposed Solution: Add ParticipantSignature model and signature workflow:

# server/app/db/models.py
class ParticipantSignature(Base):
    __tablename__ = "participant_signatures"

    id: Mapped[uuid.UUID] = mapped_column(primary_key=True, default=uuid.uuid4)
    participant_id: Mapped[uuid.UUID] = mapped_column(ForeignKey("participants.id"))
    signed_by_id: Mapped[uuid.UUID] = mapped_column(ForeignKey("users.id"))
    signature_type: Mapped[str]  # "data_lock", "consent_verification", "query_resolution"
    reason: Mapped[str]
    signed_at: Mapped[datetime] = mapped_column(default=datetime.utcnow)

    # Relationships
    participant = relationship("Participant", back_populates="signatures")
    signed_by = relationship("User")

Files to Modify:

  • server/app/db/models.py - Add ParticipantSignature model
  • server/app/routers/participants.py - Add signature endpoints
  • server/app/schemas/participants.py - Add signature schemas
  • portal/src/pages/ParticipantDetail.tsx - Add signature UI

Gap 2: Record Locking (MEDIUM PRIORITY)

REDCap Feature:

  • Lock individual records to prevent edits
  • Lock specific instruments within a record
  • Locking is separate from e-signatures

Current State:

  • No locking mechanism
  • All records editable by anyone with EDIT_PARTICIPANTS permission

Impact:

  • Data integrity risk in multi-user environments
  • Cannot freeze records for audit

Proposed Solution: Add is_locked and locked_at fields to Participant model:

# In Participant model
is_locked: Mapped[bool] = mapped_column(default=False)
locked_at: Mapped[Optional[datetime]] = mapped_column(nullable=True)
locked_by_id: Mapped[Optional[uuid.UUID]] = mapped_column(ForeignKey("users.id"), nullable=True)

Endpoint Changes:

  • PATCH /participants/{id} - Return 403 if is_locked=True
  • POST /participants/{id}/lock - Lock record (requires EDIT_PARTICIPANTS)
  • POST /participants/{id}/unlock - Unlock record (requires MANAGE_USERS or owner)

Files to Modify:

  • server/app/db/models.py - Add lock fields to Participant
  • server/app/routers/participants.py - Add lock/unlock endpoints, enforce lock check
  • server/alembic/versions/ - Migration for new fields
  • portal/src/pages/ParticipantDetail.tsx - Add lock/unlock UI

Gap 3: API/Integration Users (MEDIUM PRIORITY)

REDCap Feature:

  • Dedicated API tokens per user
  • Scoped permissions (read-only, write, etc.)
  • Separate from UI authentication

Current State:

  • JWT tokens used for both UI and API
  • No dedicated API key model
  • API access inherits user's full permissions

Impact:

  • Cannot create service accounts for integrations
  • No granular API permission scoping
  • Security risk: long-lived JWT tokens

Proposed Solution: Add ApiKey model for service integrations:

# server/app/db/models.py
class ApiKey(Base):
    __tablename__ = "api_keys"

    id: Mapped[uuid.UUID] = mapped_column(primary_key=True, default=uuid.uuid4)
    user_id: Mapped[uuid.UUID] = mapped_column(ForeignKey("users.id"))
    name: Mapped[str]  # "REDCap Sync", "Data Export Script"
    key_hash: Mapped[str]  # bcrypt hash of the key
    key_prefix: Mapped[str]  # First 8 chars for identification
    scopes: Mapped[list[str]] = mapped_column(ARRAY(String))  # ["read:participants", "write:sessions"]
    study_id: Mapped[Optional[uuid.UUID]] = mapped_column(ForeignKey("studies.id"), nullable=True)  # Scope to study
    expires_at: Mapped[Optional[datetime]] = mapped_column(nullable=True)
    last_used_at: Mapped[Optional[datetime]] = mapped_column(nullable=True)
    is_active: Mapped[bool] = mapped_column(default=True)
    created_at: Mapped[datetime] = mapped_column(default=datetime.utcnow)

Files to Modify:

  • server/app/db/models.py - Add ApiKey model
  • server/app/routers/api_keys.py - New router for key management
  • server/app/middleware/auth.py - Add API key authentication path
  • server/app/services/access_control.py - Support scope-based permission checks
  • portal/src/pages/Settings.tsx - API key management UI

Gap 4: Project Lifecycle States (LOW PRIORITY)

REDCap Feature:

  • Development → Production → Archived states
  • Production mode restricts schema changes
  • Archived mode is read-only

Current State:

  • Study has status field (draft, active, completed, archived)
  • No behavioral enforcement of states

Impact:

  • Schema changes possible on active studies
  • No protection against accidental modifications

Proposed Solution: Enforce lifecycle constraints in routers:

# In study modification endpoints
if study.status == "archived":
    raise HTTPException(403, "Cannot modify archived study")

if study.status == "active" and is_schema_change(data):
    raise HTTPException(403, "Cannot modify schema on active study")

Files to Modify:

  • server/app/routers/studies.py - Add lifecycle checks
  • server/app/routers/batteries.py - Prevent battery changes on active studies
  • server/app/routers/schedules.py - Prevent schedule changes on active studies

Gap 5: Per-Instrument Permissions (LOW PRIORITY - FUTURE)

REDCap Feature:

  • Grant access to specific instruments per user
  • Some users see only certain forms
  • Form-level export restrictions

Current State:

  • Permissions are study-level only
  • All users with VIEW_STUDY see all batteries/forms

Impact:

  • Cannot restrict access to sensitive instruments (e.g., psychiatric assessments)
  • All-or-nothing data access within a study

Proposed Solution (Future): Add UserStudyBattery junction table for granular permissions. This is a significant architectural change and should be deferred unless there's a specific regulatory requirement.


Implementation Priority

Phase 1: Critical Compliance (E-Signatures + Record Locking)

These are blockers for GCP/FDA-regulated trials.

  1. Add ParticipantSignature model
  2. Add lock fields to Participant
  3. Create migration
  4. Add signature/lock endpoints
  5. Add portal UI for signatures and locking

Phase 2: Integration Improvements (API Keys)

Enables secure service-to-service integrations.

  1. Add ApiKey model
  2. Add API key authentication middleware
  3. Add key management endpoints
  4. Add portal UI for key management

Phase 3: Governance Hardening (Lifecycle Enforcement)

Prevents accidental changes to production studies.

  1. Add lifecycle state checks to study routers
  2. Add lifecycle state checks to battery/schedule routers
  3. Add warning UI in portal when editing active studies

Files to Modify (Summary)

New Files

  • server/app/routers/api_keys.py - API key management
  • portal/src/pages/ApiKeys.tsx - API key management UI

Modified Files

File Changes
server/app/db/models.py Add ParticipantSignature, ApiKey, lock fields
server/app/routers/participants.py Add signature/lock endpoints, enforce locking
server/app/routers/studies.py Add lifecycle enforcement
server/app/routers/batteries.py Add lifecycle enforcement
server/app/middleware/auth.py Add API key auth path
server/app/services/access_control.py Add scope-based permission checks
portal/src/pages/ParticipantDetail.tsx Add signature/lock UI
portal/src/pages/Settings.tsx Add API key management

Migrations

  • server/alembic/versions/xxx_add_participant_signatures.py
  • server/alembic/versions/xxx_add_participant_locking.py
  • server/alembic/versions/xxx_add_api_keys.py

Verification Plan

E-Signatures

  1. Create participant in test study
  2. Add e-signature via API
  3. Verify signature appears in audit log
  4. Verify participant cannot be edited after signature (if lock-on-sign enabled)

Record Locking

  1. Lock participant via API
  2. Attempt to PATCH participant
  3. Verify 403 response with "Record is locked" message
  4. Unlock participant
  5. Verify PATCH succeeds

API Keys

  1. Create API key via portal
  2. Use key in Authorization header: Authorization: Bearer mk_xxx
  3. Verify scoped access works (can access scoped endpoints, denied others)
  4. Revoke key
  5. Verify key no longer works

Lifecycle Enforcement

  1. Create study in draft status
  2. Modify battery (should succeed)
  3. Change study to active
  4. Attempt to modify battery (should fail with 403)
  5. Archive study
  6. Attempt any modification (should fail with 403)

Not Addressed (Intentionally)

REDCap Feature Reason for Exclusion
Survey-Only Users Metricis handles this via participant session tokens, not user accounts
Repeating Instruments Not applicable - cognitive tasks are session-based, not form-based
Data Quality Rules Out of scope - cognitive data is auto-scored, not manually entered
Randomization Module Out of scope - handled by external randomization systems

Risk Assessment

Change Risk Level Mitigation
ParticipantSignature model Low Additive change, no existing data affected
Record locking Low Additive fields, default to unlocked
API Keys Medium New auth path, requires thorough security review
Lifecycle enforcement Medium Could break existing workflows, needs warning UI