Skip to content

Instantly share code, notes, and snippets.

@arockwell
Created January 8, 2026 18:13
Show Gist options
  • Select an option

  • Save arockwell/a4e7cd6381534081abe11eb3fad0a0dd to your computer and use it in GitHub Desktop.

Select an option

Save arockwell/a4e7cd6381534081abe11eb3fad0a0dd to your computer and use it in GitHub Desktop.
KEEP-1996 Implementation Gameplan: Prescriber Validation Blocks

KEEP-1996 Implementation Gameplan: Prescriber Validation Blocks

Overview

Implement prescriber validation blocking system to prevent filling Mifepristone prescriptions without prescriber agreements on file.

Approach: Create order_blocks table with validation service and status machine guards.

Estimated Effort: 2-3 weeks


Phase 1: Foundation & Schema (Days 1-3)

Task 1.1: Create Order Blocks Migration

Owner: Developer
Effort: 2 hours
Dependencies: None

Steps:

  1. Create migration: hive/db/migrate_primary/YYYYMMDDHHMMSS_create_order_blocks.rb
  2. Define schema:
    create_table :order_blocks do |t|
      t.references :order, null: false, foreign_key: { to_table: :orders }
      t.references :order_item, null: true, foreign_key: { to_table: :order_items }
      t.references :prescription, null: true, foreign_key: { to_table: :prescriptions }
      t.references :practitioner, null: true, foreign_key: { to_table: :practitioners }
      t.string :block_type, null: false
      t.text :reason, null: false
      t.boolean :is_resolved, default: false, null: false
      t.references :resolved_by_user, null: true, foreign_key: { to_table: :users }
      t.datetime :resolved_at
      t.timestamps
      
      t.index [:order_id, :is_resolved]
      t.index [:prescription_id, :is_resolved]
      t.index [:practitioner_id, :is_resolved]
      t.index :block_type
    end
  3. Run migration on all environments (hive, colony-rails, keeper-rails, etc.)
  4. Update schema files

Acceptance Criteria:

  • ✅ Migration runs successfully
  • ✅ All schema files updated
  • ✅ Indexes created

Task 1.2: Create OrderBlock Model

Owner: Developer
Effort: 1 hour
Dependencies: Task 1.1

Steps:

  1. Create model: hive/app/models/hive/order_block.rb
  2. Define associations:
    belongs_to :order
    belongs_to :order_item, optional: true
    belongs_to :prescription, optional: true
    belongs_to :practitioner, optional: true
    belongs_to :resolved_by_user, class_name: 'Hive::User', optional: true
  3. Define block types constant:
    BLOCK_TYPES = {
      missing_practitioner: 'missing_practitioner',
      missing_npi: 'missing_npi',
      missing_agreement: 'missing_agreement',
      missing_supervising_practitioner: 'missing_supervising_practitioner',
      missing_supervising_npi: 'missing_supervising_npi',
      missing_supervising_agreement: 'missing_supervising_agreement'
    }.freeze
  4. Add scopes:
    scope :unresolved, -> { where(is_resolved: false) }
    scope :resolved, -> { where(is_resolved: true) }
    scope :by_type, ->(type) { where(block_type: type) }
  5. Add validations:
    validates :block_type, inclusion: { in: BLOCK_TYPES.values }
    validates :reason, presence: true
  6. Add resolve! method:
    def resolve!(user)
      update!(
        is_resolved: true,
        resolved_by_user: user,
        resolved_at: Time.current
      )
    end

Acceptance Criteria:

  • ✅ Model created with all associations
  • ✅ Validations work
  • ✅ Scopes work
  • resolve! method works

Task 1.3: Add Order Association

Owner: Developer
Effort: 15 minutes
Dependencies: Task 1.2

Steps:

  1. Update hive/app/models/hive/order.rb:
    has_many :order_blocks, dependent: :destroy
  2. Add helper method:
    def has_unresolved_prescriber_blocks?
      order_blocks.unresolved.exists?
    end

Acceptance Criteria:

  • ✅ Association works
  • ✅ Helper method works

Task 1.4: Create NPI Credential Type Seed

Owner: Developer
Effort: 1 hour
Dependencies: None

Steps:

  1. Create config script: hive/db/config_scripts/YYYYMMDDHHMMSS_create_npi_credential_type.rb
  2. Create national NPI credential type:
    Hive::PrescribingCredentialType.find_or_create_by!(
      credential_name: 'NPI',
      requires_agreement: false,  # Agreement is drug-specific, not credential-specific
      state_id: nil  # National credential
    )
  3. Run config script

Acceptance Criteria:

  • ✅ NPI credential type exists
  • ✅ Can create practitioner_prescribing_credentials with NPI type

Task 1.5: Add Prescriber NPI to Prescriptions (If Needed)

Owner: Developer
Effort: 2 hours
Dependencies: None

Steps:

  1. Check if prescriptions table has NPI field
  2. If not, create migration:
    add_column :prescriptions, :prescriber_npi, :string
    add_index :prescriptions, :prescriber_npi
  3. Update Hive::Prescription model if needed
  4. Plan for extracting NPI from prescription messages (future work)

Acceptance Criteria:

  • ✅ NPI field exists (or confirmed not needed)
  • ✅ Can store NPI on prescriptions

Phase 2: Validation Service (Days 4-6)

Task 2.1: Create ValidatePrescriber Service

Owner: Developer
Effort: 4 hours
Dependencies: Tasks 1.1, 1.2, 1.4

Steps:

  1. Create service: hive/app/services/hive/services/order/validate_prescriber.rb
  2. Follow existing service pattern:
    module Hive
      module Services
        module Order
          class ValidatePrescriber
            def initialize(order, user: nil)
              @order = order
              @user = user || Hive::User.system_user
            end
    
            def self.call(order, user: nil)
              new(order, user: user).process
            end
    
            def process
              # Implementation from analysis
            end
          end
        end
      end
    end
  3. Implement should_validate? method
  4. Implement validate_practitioner method (handles primary and supervising)
  5. Implement build_block helper
  6. Implement create_blocks method
  7. Implement transition_to_requires_review method
  8. Implement create_starred_note method

Acceptation Criteria:

  • ✅ Service follows existing pattern
  • ✅ Returns [success, message] tuple
  • ✅ Creates blocks when validation fails
  • ✅ Transitions order to requires_review
  • ✅ Creates starred notes

Task 2.2: Implement Practitioner Validation Logic

Owner: Developer
Effort: 3 hours
Dependencies: Task 2.1

Steps:

  1. Implement check: Practitioner exists
  2. Implement check: NPI credential exists
    • Query: practitioner.practitioner_prescribing_credentials.joins(:prescribing_credential_type).where(prescribing_credential_types: { credential_name: 'NPI' })
  3. Implement check: Agreement exists (only for Mife)
    • Check agreement_file_key on NPI credential
  4. Implement recursive check: Supervising practitioner
    • If practitioner.supervising_practitioner.present?, recurse
  5. Handle all block types:
    • missing_practitioner
    • missing_npi
    • missing_agreement
    • missing_supervising_practitioner
    • missing_supervising_npi
    • missing_supervising_agreement

Acceptance Criteria:

  • ✅ All validation checks implemented
  • ✅ Correct block types created
  • ✅ Supervising practitioner validation works
  • ✅ Mife-specific agreement check works

Task 2.3: Add Practitioner Lookup Helper

Owner: Developer
Effort: 2 hours
Dependencies: Task 2.1

Steps:

  1. Create helper method to find practitioner from prescription
  2. Options:
    • By prescription.practitioner_id (if already linked)
    • By prescription.prescriber_npi (if field exists)
    • By extracting from prescription message (future work)
  3. Handle case where practitioner doesn't exist (create block)

Acceptance Criteria:

  • ✅ Can find practitioner from prescription
  • ✅ Handles missing practitioner gracefully

Phase 3: Integration (Days 7-9)

Task 3.1: Integrate into Order Creation

Owner: Developer
Effort: 2 hours
Dependencies: Task 2.1

Steps:

  1. Update ColonyServices::CreateOrder:
    • After order items are saved (line ~237)
    • Call Hive::Services::Order::ValidatePrescriber.call(order)
  2. Update OrderJobConcerns::CreateOrder:
    • After order is created and items are saved
    • Call validation service
  3. Handle validation failures gracefully (don't fail order creation, just set status)

Acceptance Criteria:

  • ✅ Validation runs after order items created
  • ✅ Order creation doesn't fail if validation fails
  • ✅ Order status set to requires_review if blocks exist

Task 3.2: Integrate into Order Updates

Owner: Developer
Effort: 2 hours
Dependencies: Task 2.1

Steps:

  1. Update ColonyServices::UpdateOrder:
    • When prescription is added to order (in add action, line ~85)
    • After order_item is saved with prescription_id
    • Call Hive::Services::Order::ValidatePrescriber.call(order)
  2. Handle case where order already has blocks (don't duplicate)

Acceptance Criteria:

  • ✅ Validation runs when prescription added
  • ✅ Doesn't create duplicate blocks
  • ✅ Updates order status if needed

Task 3.3: Update requires_reviews? Method

Owner: Developer
Effort: 1 hour
Dependencies: Task 1.3

Steps:

  1. Update OrderJobConcerns::CreateOrder#requires_reviews? (line 242):
    def requires_reviews?
      !@order.on_hold? && (
        @order.requires_review? ||
        order_requires_review_flagged? ||
        # NEW: Check for unresolved prescriber blocks
        @order.order_blocks.unresolved.exists? ||
        # ... existing conditions
      )
    end

Acceptance Criteria:

  • ✅ Orders with unresolved blocks go to requires_review
  • ✅ Doesn't conflict with existing logic

Task 3.4: Add Guards to Status Machine

Owner: Developer
Effort: 2 hours
Dependencies: Task 1.3

Steps:

  1. Update hive/app/machines/hive/machines/order_status_machine.rb:
    • Add guard method:
      def has_no_unresolved_blocks?
        !@order.order_blocks.unresolved.exists?
      end
  2. Add guard to mark_as_filling event (line 165):
    event :mark_as_filling, before: [:get_tax, :authorize_transaction] do
      transitions from: [REASON_STATES, PROCESSING_STATES - [:scheduled], :hold_resolved].flatten, 
                  to: :filling,
                  guard: [:has_no_unresolved_blocks?],  # NEW
                  after: [...]
    end
  3. Add guard to dispense event (line 175):
    event :dispense do
      transitions from: :verified, to: :filling,
                  guard: [:valid_for_dispensing?, :has_no_unresolved_blocks?],  # NEW
                  after: [...]
    end

Acceptance Criteria:

  • ✅ Guards prevent filling when blocks exist
  • ✅ Status machine transitions work correctly
  • ✅ Error messages are clear when guard fails

Phase 4: Refill Handling (Days 10-11)

Task 4.1: Implement Refill Block Checking

Owner: Developer
Effort: 3 hours
Dependencies: Task 2.1

Steps:

  1. Add method to ValidatePrescriber service:
    def check_refill_blocks(prescription)
      return [] unless prescription.present?
    
      # Find previous orders with same prescription that had unresolved blocks
      previous_orders = Hive::Order
        .joins(:order_items)
        .where(order_items: { prescription_id: prescription.id })
        .where.not(id: @order.id)
        .order(created_at: :desc)
        .limit(1)
    
      return [] if previous_orders.empty?
    
      previous_order = previous_orders.first
      previous_blocks = previous_order.order_blocks
        .where(prescription_id: prescription.id)
        .unresolved
    
      # Create same blocks for this order
      previous_blocks.map do |block|
        {
          order: @order,
          order_item: nil,  # Will be set when order_item is created
          prescription: prescription,
          practitioner: block.practitioner,
          block_type: block.block_type,
          reason: "Refill blocked: #{block.reason}"
        }
      end
    end
  2. Call this method in process before validating practitioner
  3. Merge refill blocks with validation blocks

Acceptance Criteria:

  • ✅ Refill orders inherit blocks from previous orders
  • ✅ Blocks are created with "Refill blocked:" prefix
  • ✅ Works correctly for multiple refills

Phase 5: Testing (Days 12-14)

Task 5.1: Unit Tests - OrderBlock Model

Owner: Developer
Effort: 2 hours
Dependencies: Task 1.2

Test Cases:

  • ✅ Validations (block_type, reason)
  • ✅ Scopes (unresolved, resolved, by_type)
  • resolve! method
  • ✅ Associations work

File: hive/spec/models/hive/order_block_spec.rb


Task 5.2: Unit Tests - ValidatePrescriber Service

Owner: Developer
Effort: 4 hours
Dependencies: Task 2.1

Test Cases:

  • ✅ Practitioner found/not found
  • ✅ NPI present/missing
  • ✅ Agreement present/missing (Mife only)
  • ✅ Supervising practitioner validation
  • ✅ Multiple blocks per order
  • ✅ Block creation
  • ✅ Status transition
  • ✅ Note creation
  • ✅ Refill block checking

File: hive/spec/services/hive/services/order/validate_prescriber_spec.rb


Task 5.3: Integration Tests - Order Creation

Owner: Developer
Effort: 3 hours
Dependencies: Task 3.1

Test Cases:

  • ✅ Order with blocked prescription goes to requires_review
  • ✅ Order without blocks proceeds normally
  • ✅ Multiple prescriptions with different blocks
  • ✅ Order with Mife and non-Mife products

File: colony-rails/spec/services/colony_services/create_order_spec.rb (add test cases)


Task 5.4: Integration Tests - Status Machine Guards

Owner: Developer
Effort: 2 hours
Dependencies: Task 3.4

Test Cases:

  • ✅ Cannot transition to filling when blocks exist
  • ✅ Can transition to filling when blocks resolved
  • ✅ Guard error messages are clear

File: hive/spec/machines/hive/order_status_machine_spec.rb (add test cases)


Task 5.5: Integration Tests - Refills

Owner: Developer
Effort: 2 hours
Dependencies: Task 4.1

Test Cases:

  • ✅ Refill order inherits blocks from original
  • ✅ Refill with resolved blocks doesn't inherit
  • ✅ Multiple refills work correctly

Task 5.6: Edge Case Tests

Owner: Developer
Effort: 2 hours
Dependencies: All previous tasks

Test Cases:

  • ✅ Legacy prescriptions without practitioner_id
  • ✅ Practitioner with multiple NPI numbers
  • ✅ Supervising practitioner chain (nested)
  • ✅ Order with multiple prescriptions, some blocked
  • ✅ Block resolution workflow

Phase 6: Block Resolution (Days 15-16)

Task 6.1: Create Block Resolution Service

Owner: Developer
Effort: 3 hours
Dependencies: Task 1.2

Steps:

  1. Create service: hive/app/services/hive/services/order/resolve_blocks.rb
  2. Implement resolution logic:
    def resolve_block(block, user, reason: nil)
      block.resolve!(user)
      
      # If all blocks resolved, check if order can proceed
      if order.order_blocks.unresolved.empty?
        # Transition order if appropriate
      end
    end
  3. Handle bulk resolution
  4. Add validation (can't resolve if conditions still not met)

Note: This is part of KEEP-1998, but basic resolution needed for testing

Acceptance Criteria:

  • ✅ Can resolve individual blocks
  • ✅ Can resolve all blocks for order
  • ✅ Order can proceed when all blocks resolved

Phase 7: Documentation & Cleanup (Day 17)

Task 7.1: Update Documentation

Owner: Developer
Effort: 2 hours
Dependencies: All tasks

Steps:

  1. Update docs/order_creation_guide.md with prescriber validation
  2. Add to docs/technical_infrastructure_guide.md if needed
  3. Document block types and their meanings
  4. Document resolution workflow

Task 7.2: Code Review & Refactoring

Owner: Developer
Effort: 2 hours
Dependencies: All tasks

Steps:

  1. Code review with team
  2. Refactor based on feedback
  3. Ensure all code follows existing patterns
  4. Check for performance issues

Dependencies Graph

Task 1.1 (Migration)
  └─> Task 1.2 (Model)
      └─> Task 1.3 (Order Association)
          └─> Task 3.3 (requires_reviews?)
          └─> Task 3.4 (Guards)

Task 1.4 (NPI Seed)
  └─> Task 2.1 (Service)

Task 1.5 (NPI Field)
  └─> Task 2.3 (Lookup Helper)

Task 2.1 (Service)
  └─> Task 2.2 (Validation Logic)
  └─> Task 2.3 (Lookup Helper)
  └─> Task 3.1 (Order Creation Integration)
  └─> Task 3.2 (Order Update Integration)
  └─> Task 4.1 (Refill Handling)
  └─> Task 5.2 (Service Tests)

Task 3.1 (Order Creation)
  └─> Task 5.3 (Integration Tests)

Task 3.4 (Guards)
  └─> Task 5.4 (Guard Tests)

Task 4.1 (Refill Handling)
  └─> Task 5.5 (Refill Tests)

Risk Mitigation

Risk 1: Performance Impact

Mitigation:

  • Add indexes on order_blocks table
  • Use includes to avoid N+1 queries
  • Consider caching if needed

Risk 2: Legacy Prescriptions Without Practitioner

Mitigation:

  • Handle gracefully (create block, don't fail)
  • Log missing practitioners for review
  • Plan migration strategy for linking legacy prescriptions

Risk 3: NPI Extraction Complexity

Mitigation:

  • Start with prescription.practitioner_id if available
  • Add prescriber_npi field as fallback
  • Plan NCPDP parsing as future work

Risk 4: Refill Block Logic Complexity

Mitigation:

  • Start simple (check previous order)
  • Add "change of address/credentials" detection later
  • Test thoroughly with real refill scenarios

Success Criteria

Functional Requirements

  • ✅ Orders with missing practitioner go to requires_review
  • ✅ Orders with missing NPI go to requires_review
  • ✅ Orders with Mife and missing agreement go to requires_review
  • ✅ Supervising practitioner validation works
  • ✅ Multiple blocks per order supported
  • ✅ Refill orders inherit blocks
  • ✅ Blocks prevent order from filling
  • ✅ Starred notes created with block details

Non-Functional Requirements

  • ✅ Performance: Validation doesn't slow down order creation
  • ✅ Maintainability: Code follows existing patterns
  • ✅ Testability: Comprehensive test coverage
  • ✅ Documentation: Clear documentation for future developers

Rollout Plan

Phase 1: Development

  • Complete all tasks in development environment
  • Full test coverage
  • Code review

Phase 2: QA Testing

  • Deploy to QA environment
  • Test with real prescription data
  • Verify all scenarios work

Phase 3: Staged Rollout

  • Deploy to staging
  • Monitor for issues
  • Gradual rollout to production

Phase 4: Production

  • Full deployment
  • Monitor error rates
  • Track block resolution times

Future Enhancements (Out of Scope)

  1. NCPDP NPI Extraction: Parse NPI from prescription messages
  2. Agreement Upload UI: Admin interface for uploading agreements
  3. Block Resolution UI: Better UI for resolving blocks (KEEP-1998)
  4. Change Detection: Detect address/credential changes for refills
  5. Block Analytics: Reporting on block types and resolution times
  6. Auto-resolution: Auto-resolve blocks when conditions are met

Notes

  • Coordinate with KEEP-1998 for block resolution workflow
  • Consider impact on existing orders (legacy prescriptions)
  • Plan for practitioner data migration/backfill
  • Monitor performance in production
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment