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
Owner: Developer
Effort: 2 hours
Dependencies: None
Steps:
- Create migration:
hive/db/migrate_primary/YYYYMMDDHHMMSS_create_order_blocks.rb - 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
- Run migration on all environments (hive, colony-rails, keeper-rails, etc.)
- Update schema files
Acceptance Criteria:
- ✅ Migration runs successfully
- ✅ All schema files updated
- ✅ Indexes created
Owner: Developer
Effort: 1 hour
Dependencies: Task 1.1
Steps:
- Create model:
hive/app/models/hive/order_block.rb - 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
- 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
- Add scopes:
scope :unresolved, -> { where(is_resolved: false) } scope :resolved, -> { where(is_resolved: true) } scope :by_type, ->(type) { where(block_type: type) }
- Add validations:
validates :block_type, inclusion: { in: BLOCK_TYPES.values } validates :reason, presence: true
- 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
Owner: Developer
Effort: 15 minutes
Dependencies: Task 1.2
Steps:
- Update
hive/app/models/hive/order.rb:has_many :order_blocks, dependent: :destroy
- Add helper method:
def has_unresolved_prescriber_blocks? order_blocks.unresolved.exists? end
Acceptance Criteria:
- ✅ Association works
- ✅ Helper method works
Owner: Developer
Effort: 1 hour
Dependencies: None
Steps:
- Create config script:
hive/db/config_scripts/YYYYMMDDHHMMSS_create_npi_credential_type.rb - 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 )
- Run config script
Acceptance Criteria:
- ✅ NPI credential type exists
- ✅ Can create practitioner_prescribing_credentials with NPI type
Owner: Developer
Effort: 2 hours
Dependencies: None
Steps:
- Check if
prescriptionstable has NPI field - If not, create migration:
add_column :prescriptions, :prescriber_npi, :string add_index :prescriptions, :prescriber_npi
- Update
Hive::Prescriptionmodel if needed - Plan for extracting NPI from prescription messages (future work)
Acceptance Criteria:
- ✅ NPI field exists (or confirmed not needed)
- ✅ Can store NPI on prescriptions
Owner: Developer
Effort: 4 hours
Dependencies: Tasks 1.1, 1.2, 1.4
Steps:
- Create service:
hive/app/services/hive/services/order/validate_prescriber.rb - 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
- Implement
should_validate?method - Implement
validate_practitionermethod (handles primary and supervising) - Implement
build_blockhelper - Implement
create_blocksmethod - Implement
transition_to_requires_reviewmethod - Implement
create_starred_notemethod
Acceptation Criteria:
- ✅ Service follows existing pattern
- ✅ Returns
[success, message]tuple - ✅ Creates blocks when validation fails
- ✅ Transitions order to requires_review
- ✅ Creates starred notes
Owner: Developer
Effort: 3 hours
Dependencies: Task 2.1
Steps:
- Implement check: Practitioner exists
- Implement check: NPI credential exists
- Query:
practitioner.practitioner_prescribing_credentials.joins(:prescribing_credential_type).where(prescribing_credential_types: { credential_name: 'NPI' })
- Query:
- Implement check: Agreement exists (only for Mife)
- Check
agreement_file_keyon NPI credential
- Check
- Implement recursive check: Supervising practitioner
- If
practitioner.supervising_practitioner.present?, recurse
- If
- Handle all block types:
missing_practitionermissing_npimissing_agreementmissing_supervising_practitionermissing_supervising_npimissing_supervising_agreement
Acceptance Criteria:
- ✅ All validation checks implemented
- ✅ Correct block types created
- ✅ Supervising practitioner validation works
- ✅ Mife-specific agreement check works
Owner: Developer
Effort: 2 hours
Dependencies: Task 2.1
Steps:
- Create helper method to find practitioner from prescription
- Options:
- By
prescription.practitioner_id(if already linked) - By
prescription.prescriber_npi(if field exists) - By extracting from prescription message (future work)
- By
- Handle case where practitioner doesn't exist (create block)
Acceptance Criteria:
- ✅ Can find practitioner from prescription
- ✅ Handles missing practitioner gracefully
Owner: Developer
Effort: 2 hours
Dependencies: Task 2.1
Steps:
- Update
ColonyServices::CreateOrder:- After order items are saved (line ~237)
- Call
Hive::Services::Order::ValidatePrescriber.call(order)
- Update
OrderJobConcerns::CreateOrder:- After order is created and items are saved
- Call validation service
- 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
Owner: Developer
Effort: 2 hours
Dependencies: Task 2.1
Steps:
- Update
ColonyServices::UpdateOrder:- When prescription is added to order (in
addaction, line ~85) - After order_item is saved with
prescription_id - Call
Hive::Services::Order::ValidatePrescriber.call(order)
- When prescription is added to order (in
- 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
Owner: Developer
Effort: 1 hour
Dependencies: Task 1.3
Steps:
- 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
Owner: Developer
Effort: 2 hours
Dependencies: Task 1.3
Steps:
- Update
hive/app/machines/hive/machines/order_status_machine.rb:- Add guard method:
def has_no_unresolved_blocks? !@order.order_blocks.unresolved.exists? end
- Add guard method:
- Add guard to
mark_as_fillingevent (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
- Add guard to
dispenseevent (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
Owner: Developer
Effort: 3 hours
Dependencies: Task 2.1
Steps:
- Add method to
ValidatePrescriberservice: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
- Call this method in
processbefore validating practitioner - 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
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
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
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)
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)
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
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
Owner: Developer
Effort: 3 hours
Dependencies: Task 1.2
Steps:
- Create service:
hive/app/services/hive/services/order/resolve_blocks.rb - 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
- Handle bulk resolution
- 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
Owner: Developer
Effort: 2 hours
Dependencies: All tasks
Steps:
- Update
docs/order_creation_guide.mdwith prescriber validation - Add to
docs/technical_infrastructure_guide.mdif needed - Document block types and their meanings
- Document resolution workflow
Owner: Developer
Effort: 2 hours
Dependencies: All tasks
Steps:
- Code review with team
- Refactor based on feedback
- Ensure all code follows existing patterns
- Check for performance issues
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)
Mitigation:
- Add indexes on
order_blockstable - Use
includesto avoid N+1 queries - Consider caching if needed
Mitigation:
- Handle gracefully (create block, don't fail)
- Log missing practitioners for review
- Plan migration strategy for linking legacy prescriptions
Mitigation:
- Start with
prescription.practitioner_idif available - Add
prescriber_npifield as fallback - Plan NCPDP parsing as future work
Mitigation:
- Start simple (check previous order)
- Add "change of address/credentials" detection later
- Test thoroughly with real refill scenarios
- ✅ 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
- ✅ Performance: Validation doesn't slow down order creation
- ✅ Maintainability: Code follows existing patterns
- ✅ Testability: Comprehensive test coverage
- ✅ Documentation: Clear documentation for future developers
- Complete all tasks in development environment
- Full test coverage
- Code review
- Deploy to QA environment
- Test with real prescription data
- Verify all scenarios work
- Deploy to staging
- Monitor for issues
- Gradual rollout to production
- Full deployment
- Monitor error rates
- Track block resolution times
- NCPDP NPI Extraction: Parse NPI from prescription messages
- Agreement Upload UI: Admin interface for uploading agreements
- Block Resolution UI: Better UI for resolving blocks (KEEP-1998)
- Change Detection: Detect address/credential changes for refills
- Block Analytics: Reporting on block types and resolution times
- Auto-resolution: Auto-resolve blocks when conditions are met
- 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