Project: doctolib-doctolib · Branch: master · Mode: ` · **Model:**claude-haiku-4-5-20251001`
Stats: 1 user messages, 26 tool calls, tools used: Glob, Grep, Read, Bash.
Conversation
Turn 1 — User
Search the codebase for:
1. The feature switch nhr_document_list in config/feature_switches.yml or any YAML config files
2. How NHR_DOCUMENT_LIST_ENABLED is defined as a dl_constants / frontend constant
3. How cohort-based feature switches are exposed to the frontend as dl_constants
Key paths to check:
- config/feature_switches.yml
- Any file defining NHR_DOCUMENT_LIST_ENABLED
- How cohort feature switches get translated into dl_constants JS constants
Return: the full definition of the nhr_document_list feature switch, the NHR_DOCUMENT_LIST_ENABLED constant definition, and any middleware or helper that maps feature switches to dl_constants.
Turn 1 — Assistant
I'll help you find the feature switch nhr_document_list and how it's exposed to the frontend as a dl_constants. Let me search the codebase systematically.
Tools used:
Glob:config/feature_switches.yml
Tools used:
Grep:nhr_document_list
Tool results:
→ result (ok): No files found
Tools used:
Grep:NHR_DOCUMENT_LIST_ENABLED
Tools used:
Grep:dl_constants
Tool results:
→ result (ok): No files found
Tool results:
→ result (ok): No files found
Tool results:
→ result (ok): /Users/USER/workspace/doctolib/doctolib/config/feature_switches.yml
Let me read the feature_switches.yml file and search more broadly for how constants are defined:
Tools used:
Read:/Users/USER/workspace/doctolib/doctolib/config/feature_switches.yml
Tool results:
→ result (ERROR): File content (440.5KB) exceeds maximum allowed size (256KB). Use offset and limit parameters to read specific portions of the file, or search for specific content instead of reading the whole file.
Tools used:
Grep:document_list
Tool results:
→ result (ok): No files found
Tools used:
Glob:**/*dl_constants*
Tool results:
→ result (ok): No files found
Let me search for how feature switches are mapped to frontend constants:
Tools used:
Grep:feature.*switch|cohort.*feature
Tool results:
→ result (ok): No files found
Tools used:
Glob:app/**/*constant*
Tools used:
Glob:config/**/*constant*
Tool results:
→ result (ok): /Users/USER/workspace/doctolib/doctolib/config/initializers/01_constants.rb /Users/USER/workspace/doctolib/doctolib/config/initializers/constants_data.rb /Users/USER/workspace/doctolib/doctolib/config/bundler/shared/constants.ts
Tool results:
→ result (ok): /Users/USER/workspace/doctolib/doctolib/app/controllers/doctor_app/constants_controller.rb /Users/USER/workspace/doctolib/doctolib/app/assets/javascripts/queries/doctor_app/constants.ts /Users/USER/workspace/doctolib/doctolib/app/assets/javascripts/components/doctor/desktop/configuration/modules/components/exports/constants.ts /Users/USER/workspace/doctolib/doctolib/app/assets/javascripts/components/doctor/desktop/configuration/modules/components/agendas/agenda-creation-form/steps/constants.ts / …(truncated, 4935 more chars)
Great! Let me examine the key files:
Tools used:
Read:/Users/USER/workspace/doctolib/doctolib/app/controllers/doctor_app/constants_controller.rb
Tool results:
→ result (ok): 1→# frozen_string_literal: true
2→
3→class DoctorApp::ConstantsController < DoctorApp::ApplicationController
4→ include FrontendConstantsHelper
5→ include ConfigurationHelper
6→ include AppointmentsHelper
7→
8→ authenticate_with Authentication::Api::Filter::ACCOUNT
9→ after_action :verify_policy_scoped
10→ authorized_user_types :doctor, :telesecretary, :impersonated_doctor, :staff
11→
12→ def index
13→ skip_policy_scope
14→
…(truncated, 400 more chars)
Tools used:
Read:/Users/USER/workspace/doctolib/doctolib/app/helpers/frontend_constants_helper.rb
Tool results:
→ result (ok): 1→# frozen_string_literal: true 2→ 3→module FrontendConstantsHelper 4→ # rubocop:todo DoctolibInternal/NoCountrySpecificLogic 5→ CONSTANTS = { 6→ AGENDA_DEFAULT_MAX_TIME: Preferences::AccountPreferences::Api::DEFAULT_MAX_TIME, 7→ AGENDA_DEFAULT_MIN_TIME: Preferences::AccountPreferences::Api::DEFAULT_MIN_TIME, 8→ AMELI_WEB_SERVICE_URL: ENV.fetch('AMELI_WEB_SERVICE_URL', nil), 9→ APPOINTMENT_PAGINATION_SIZE: Appointment::PAGINATION_SIZE, 10 …(truncated, 18601 more chars)
Tools used:
Read:/Users/USER/workspace/doctolib/doctolib/config/initializers/constants_data.rb
Tool results:
→ result (ok): 1→# frozen_string_literal: true 2→ 3→begin 4→ # rubocop:todo Style/HashConversion 5→ DISPOSABLE_EMAIL_DOMAINS = Hash[CSV.read(Rails.root.join('lib/data/disposable-email-domains.csv'))] 6→ # rubocop:enable Style/HashConversion 7→rescue StandardError 8→ DISPOSABLE_EMAIL_DOMAINS = {}.freeze 9→end 10→
<system-reminder> Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, …(truncated, 185 more chars)
Now let me search for where feature switches are defined in the YAML and also look for ApplicationPreference:
Tools used:
Bash:grep -n "nhr_document_list" /Users/USER/workspace/doctolib/doctolib/config/feature_switches.yml 2>/dev/null | head -20
Tools used:
Glob:**/application_preference* **/*ApplicationPreference*
Tools used:
Bash:find /Users/USER/workspace/doctolib/doctolib -name "*application_preference*" -o -name "*ApplicationPreference*" 2>/dev/null | head -20
Tool results:
→ result (ok): 5707: nhr_document_list:
Tool results:
→ result (ok): No files found
Tool results:
→ result (ok): /Users/USER/workspace/doctolib/doctolib/app/models/application_preference /Users/USER/workspace/doctolib/doctolib/app/models/application_preference.rb /Users/USER/workspace/doctolib/doctolib/app/policies/v2/admin/application_preference_policy.rb /Users/USER/workspace/doctolib/doctolib/app/jobs/application_preference /Users/USER/workspace/doctolib/doctolib/test/test_utils/test_classes/setup_teardown/reset_application_preferences.rb /Users/USER/workspace/doctolib/doctolib/test/models/application_p …(truncated, 1412 more chars)
Good! Now let me look at the feature_switches.yml around line 5707 and read the ApplicationPreference model:
Tools used:
Read:/Users/USER/workspace/doctolib/doctolib/config/feature_switches.yml(5700–5750)
Tool results:
→ result (ok): 5700→ details: This feature allows to import lab results as LDT files from a user's 5701→ computer 5702→ expire_at: 2026-06-01 5703→ message: Allow Lab data imports using LDT files 5704→ send_to_front: true 5705→ team: FIRE 5706→ value_in_test: false 5707→ nhr_document_list: 5708→ cohort_types: 5709→ - account 5710→ default: false 5711→ details: Enables the NHR (National Health Records / DMP) document list modal for 5712→ healthc …(truncated, 1689 more chars)
Tools used:
Read:/Users/USER/workspace/doctolib/doctolib/app/models/application_preference.rb(1–101)
Tool results:
→ result (ok): 1→# frozen_string_literal: true 2→ 3→class ApplicationPreference < Db1Record 4→ data_taxonomy do 5→ description('Application feature switches state') 6→ 7→ default( 8→ secret: false, 9→ system_id: false, 10→ health_information_likelihood: :none, 11→ data_subject_identity_likelihood: :none, 12→ data_subject_info_likelihood: :none, 13→ data_subjects: [:not_applicable], 14→ sensitive_likelihood: :n …(truncated, 4045 more chars)
Perfect! Now let me check how the feature switch gets converted to a frontend constant. Let me read more of the FrontendConstantsHelper to see the full picture:
Tools used:
Bash:grep -rn "NHR_DOCUMENT_LIST" /Users/USER/workspace/doctolib/doctolib --include="*.ts" --include="*.tsx" --include="*.js" --include="*.rb" 2>/dev/null | head -20
Tool results:
→ result (ok)
Let me search for how feature switches are transformed into constants in the frontend:
Tools used:
Bash:grep -n "feature_switches_sent_to_front\|evaluate_feature_switch_pair" /Users/USER/workspace/doctolib/doctolib/app/helpers/frontend_constants_helper.rb
Tool results:
→ result (ok): 124: feature_switches_sent_to_front, 133: def feature_switches_sent_to_front 138: evaluate_feature_switch_pair(feature_name, feature_attributes, cohort_context) 219: def evaluate_feature_switch_pair(feature_name, feature_attributes, cohort_context)
Tools used:
Bash:grep -rn "send_to_front.*true" /Users/USER/workspace/doctolib/doctolib/config/feature_switches.yml | grep -A 5 "nhr_document_list"
Tool results:
→ result (ok)
Let me look at how cohort_types work by examining the ApplicationPreference more closely:
Tools used:
Bash:grep -rn "cohort_types\|INCLUDE_ALL" /Users/USER/workspace/doctolib/doctolib/app/models/application_preference.rb | head -15
Tool results:
→ result (ok): /Users/USER/workspace/doctolib/doctolib/app/models/application_preference.rb:46: COHORT_FEATURE_KEYS = ApplicationPreference::FEATURES.select { |_, metadata| metadata['cohort_types'].present? }.keys /Users/USER/workspace/doctolib/doctolib/app/models/application_preference.rb:50: INCLUDE_ALL_COHORT_ID = 'INCLUDE_ALL' /Users/USER/workspace/doctolib/doctolib/app/models/application_preference.rb:84: :cohort_types, /Users/USER/workspace/doctolib/doctolib/app/models/application_preferenc …(truncated, 575 more chars)
Tools used:
Bash:grep -rn "resolved_cohorts\|ContextResolution" /Users/USER/workspace/doctolib/doctolib/lib/application_preferences/ --include="*.rb" | head -10
Tool results:
→ result (ok): /Users/USER/workspace/doctolib/doctolib/lib/application_preferences/context_resolution.rb:4: module ContextResolution /Users/USER/workspace/doctolib/doctolib/lib/application_preferences/context_resolution.rb:18: { cohorts: group_by_resource_type(@resolved_cohorts), **additional_context } /Users/USER/workspace/doctolib/doctolib/lib/application_preferences/context_resolution.rb:22: identities = ContextResolution.identities_from_resources!(resources) /Users/USER/workspace/doctolib/do …(truncated, 1006 more chars)
Let me read the ContextResolution file and a broader section of ApplicationPreference:
Tools used:
Read:/Users/USER/workspace/doctolib/doctolib/lib/application_preferences/context_resolution.rb
Tool results:
→ result (ok): 1→# frozen_string_literal: true 2→ 3→module ApplicationPreferences 4→ module ContextResolution 5→ RESOURCE_TYPE_MAP = { 6→ 'Account' => Cohorts::Resolution::RESOURCE_TYPES[:ACCOUNT], 7→ 'Authentication::AccountContext' => Cohorts::Resolution::RESOURCE_TYPES[:ACCOUNT], 8→ 'Legal::Account' => Cohorts::Resolution::RESOURCE_TYPES[:ACCOUNT], 9→ 'StaffAccounts::Account' => Cohorts::Resolution::RESOURCE_TYPES[:ACCOUNT], 10→ 'Teles …(truncated, 2963 more chars)
Tools used:
Read:/Users/USER/workspace/doctolib/doctolib/app/models/application_preference.rb(460–560)
Tool results:
→ result (ok): 460→ end 461→ 462→ def cohort_types(feature) 463→ FEATURES.dig(feature.to_s, 'cohort_types')&.map(&:to_sym) 464→ end 465→ 466→ def active_cohorts(feature) 467→ ApplicationPreference.default.features_enabled["#{feature}_cohorts"] || [] 468→ end 469→ 470→ def cohort_feature?(feature) 471→ cohort_types(feature).present? 472→ end 473→ 474→ def update_cohorts!(feature, cohorts) 475→ raise "Expected #{feature} to be a cohort feature but …(truncated, 3563 more chars)
Perfect! Now let me create a comprehensive summary of my findings. Let me also check how the constant naming is done:
Tools used:
Read:/Users/USER/workspace/doctolib/doctolib/app/helpers/frontend_constants_helper.rb(219–279)
Tool results:
→ result (ok): 219→ def evaluate_feature_switch_pair(feature_name, feature_attributes, cohort_context) 220→ per_country = feature_attributes['per_country'] == true 221→ per_speciality = feature_attributes['per_speciality'] == true 222→ per_ssid = feature_attributes['per_ssid'] == true 223→ 224→ return handle_value_feature(feature_name, per_country) if feature_name.ends_with?('_value') 225→ 226→ bare_feature_name = feature_name.to_s.sub(/_factor\Z/, '') 227→ feature_ke …(truncated, 3196 more chars)
Excellent! Now I have all the information I need. Let me compile the findings:
Summary of Findings
1. Feature Switch Definition: nhr_document_list
File: /Users/USER/workspace/doctolib/doctolib/config/feature_switches.yml
Full Definition (lines 5707-5717):
nhr_document_list:
cohort_types:
- account
default: false
details: Enables the NHR (National Health Records / DMP) document list modal for
healthcare professionals. Part of DMP 3.1a certification.
expire_at: 2027-06-01
message: NHR document list — DMP 3.1a feature rollout
send_to_front: true
team: FIRE
value_in_test: false
2. Frontend Constant: NHR_DOCUMENT_LIST_ENABLED
There is no explicit constant definition named NHR_DOCUMENT_LIST_ENABLED in the codebase. Instead, it is dynamically generated at runtime by the frontend constants helper.
The constant is created on-the-fly by the evaluate_feature_switch_pair method in the FrontendConstantsHelper, which transforms the YAML feature name nhr_document_list into the constant NHR_DOCUMENT_LIST_ENABLED.
3. How Cohort-Based Feature Switches Are Exposed to Frontend as dl_constants
File: /Users/USER/workspace/doctolib/doctolib/app/helpers/frontend_constants_helper.rb
Key Flow:
-
Controller (
/Users/USER/workspace/doctolib/doctolib/app/controllers/doctor_app/constants_controller.rb): - Callsbuild_constantsmethod fromFrontendConstantsHelper- Returns JSON that becomeswindow.dl_constantson the frontend -
Helper Method:
build_constants(lines 120-129): ```ruby def build_constants CONSTANTS.merge( { built_with_account: assumed_or_current_account&.id }, engines_frontend_constants, feature_switches_sent_to_front, # <-- Feature switches injected here dynamic_constants, country_features, deprecated_associated_account_fs, ) end
3. **Feature Switch Extraction**: `feature_switches_sent_to_front` (lines 133-140):
```ruby
def feature_switches_sent_to_front
cohort_context = build_cohort_context
ApplicationPreference::FEATURES
.filter { |_feature_name, feature_attributes| feature_attributes['send_to_front'] == true }
.to_h do |feature_name, feature_attributes|
evaluate_feature_switch_pair(feature_name, feature_attributes, cohort_context)
end
end
- Filters features where
send_to_front: true(which includesnhr_document_list) - Calls
evaluate_feature_switch_pairfor each feature
-
Feature-to-Constant Transformation:
evaluate_feature_switch_pair(lines 219-250): ```ruby def evaluate_feature_switch_pair(feature_name, feature_attributes, cohort_context) per_country = feature_attributes['per_country'] == true per_speciality = feature_attributes['per_speciality'] == true per_ssid = feature_attributes['per_ssid'] == truereturn handle_value_feature(feature_name, per_country) if feature_name.ends_with?('_value')
bare_feature_name = feature_name.to_s.sub(/_factor\Z/, '') feature_key = "#{bare_feature_name.upcase}_ENABLED" # <-- Creates NHR_DOCUMENT_LIST_ENABLED
if feature_name.ends_with?('_factor') # Factor handling else [ feature_key, if per_country ApplicationPreference.send("#{bare_feature_name}_enabled_for_country?", country) else ApplicationPreference.send("#{bare_feature_name}_enabled?", context: cohort_context) # <-- For cohort features end, ] end end
5. **Cohort Context Evaluation**: `build_cohort_context` (lines 282-296):
```ruby
def build_cohort_context
resources = []
if assumed_or_current_account.present? && desktop_pro_or_mobile_pro_domain?
resources = [assumed_or_current_account, *assumed_or_current_account.all_current_organizations.select(:id)]
end
resolved_cohorts = ApplicationPreferences::ContextResolution.resolved_cohorts_from_resources(resources)
resolved_cohorts.evaluation_context(skip_cohort_type_requirements: true)
end
- For
nhr_document_list(which hascohort_types: [account]), this builds a cohort context containing the current account - Passes this context to
ApplicationPreference.send("nhr_document_list_enabled?", context: cohort_context)
- Cohort Resolution:
/Users/USER/workspace/doctolib/doctolib/lib/application_preferences/context_resolution.rb- Maps Account/Organization resources toCohorts::Identityobjects - Groups identities by resource type (e.g.,:account,:organization) - Returns cohort membership data that determines if the feature is enabled
4. Complete Data Flow for nhr_document_list
YAML config (feature_switches.yml)
↓
ApplicationPreference::FEATURES (loaded at startup)
↓
feature_switches_sent_to_front (filter send_to_front: true)
↓
evaluate_feature_switch_pair (transform nhr_document_list → NHR_DOCUMENT_LIST_ENABLED)
↓
ApplicationPreference.nhr_document_list_enabled?(context: cohort_context)
↓
Check if account's cohort IDs match active cohorts for nhr_document_list
↓
build_constants → JSON serialization
↓
window.dl_constants.NHR_DOCUMENT_LIST_ENABLED (frontend JavaScript)
Key Files Referenced
- Configuration:
/Users/USER/workspace/doctolib/doctolib/config/feature_switches.yml - Feature Logic:
/Users/USER/workspace/doctolib/doctolib/app/models/application_preference.rb - Frontend Constants Mapper:
/Users/USER/workspace/doctolib/doctolib/app/helpers/frontend_constants_helper.rb - Cohort Resolution:
/Users/USER/workspace/doctolib/doctolib/lib/application_preferences/context_resolution.rb - Constants Controller:
/Users/USER/workspace/doctolib/doctolib/app/controllers/doctor_app/constants_controller.rb