Skip to content

CARE-23: implement fixtures v2#3618

Open
praffq wants to merge 6 commits intodevelopfrom
prafful/feat/fixture-v2
Open

CARE-23: implement fixtures v2#3618
praffq wants to merge 6 commits intodevelopfrom
prafful/feat/fixture-v2

Conversation

@praffq
Copy link
Copy Markdown
Contributor

@praffq praffq commented Apr 5, 2026

Proposed Changes

  • Updated fixtures

Fixes : (will add issue from Jira)

Only PR's with test cases included and passing lint and test pipelines will be reviewed

@ohcnetwork/care-backend-maintainers @ohcnetwork/care-backend-admins

Summary by CodeRabbit

  • Refactor

    • Fixture loader simplified and modularized; CLI options removed; now runs via a shared fixture context and emits concise start/success messaging.
  • New Features

    • Reusable fixture framework and helpers for deterministic local data: fixed geography/org, one facility layout (Ward A, 5 beds), 5 devices, 10 patients with encounters, questionnaires, lab definitions, inventory items, and managing-organization/user role setup driven by shared constants.
  • Documentation

    • Added comprehensive fixtures guide with examples for seeding local development.

@praffq praffq requested a review from a team as a code owner April 5, 2026 23:12
Copy link
Copy Markdown

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your free trial has ended. If you'd like to continue receiving code reviews, you can add a payment method here.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 5, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Replaces inline fixture generation with a transactional fixture framework: a context manager that yields an authenticated API-backed helper (CareFixtureBase), large fixture constants, and a refactored management command that delegates seeding to those utilities. (Yes, it actually rolls back on error.)

Changes

Cohort / File(s) Summary
Management Command Refactor
care/emr/management/commands/load_fixtures.py
Replaced monolithic in-command fixture logic with orchestration that uses care_fixture_context() and self.load_fixtures(base); removed add_arguments(), changed help, simplified handle() to start/success messages, and added helper methods load_fixtures, load_lab_definitions, load_inventory, and setup_managing_organization.
Fixture Framework Base
care/fixtures/base.py
New CareFixtureBase and FixtureError providing validated post/get wrappers and many API-backed creators (organizations, facilities, facility-orgs, locations, devices, users, patients, encounters, questionnaires), clinical/lab and inventory builders (resource categories, specimen/observation definitions, healthcare services, charge item/activity definitions, product knowledge/products, delivery orders/supply deliveries), composite creators (create_lab_test, create_inventory_item), and role/user linking helpers.
Fixture Constants & Data
care/fixtures/constants.py
New large constants and builder helpers: UCUM/HL7/SNOMED/LOINC entries, build_price_components, make_range, make_container, make_type_tested, and prepared datasets LAB_TESTS, INVENTORY_ITEMS, and MANAGING_ORG_USERS used by fixture orchestration.
Fixture Context Manager
care/fixtures/context.py
New care_fixture_context() and _NoOpLock that lower audit logging, run sync_permissions_roles and sync_valueset, patch PatientCreateLock to a no-op, ensure an admin superuser (password admin), create/force-authenticate a DRF APIClient, and run fixture creation inside a DB transaction yielding CareFixtureBase.
Documentation
care/fixtures/fixtures.md
Added documentation describing the fixture system, module responsibilities (base.py, constants.py, context.py), example scripts, and instructions for seeding development/test data.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description is largely incomplete, missing most required sections including a proper brief of changes, architecture details, and a complete merge checklist. Expand the description with detailed change summary, architecture explanation, and complete the merge checklist with test/lint/docs status confirmation.
Docstring Coverage ⚠️ Warning Docstring coverage is 8.16% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (1 passed)
Check name Status Explanation
Title check ✅ Passed The title 'CARE-23: implement fixtures v2' clearly identifies the primary change—implementing a new fixtures system version.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch prafful/feat/fixture-v2

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🧹 Nitpick comments (6)
care/emr/management/commands/load_fixtures.py (2)

310-312: Unused loop variable _name.

The underscore prefix suggests awareness that the variable is unused, but iterating with .items() when you only need values is slightly wasteful. Using .values() would be marginally cleaner.

♻️ Proposed simplification
-        for _name, org in role_orgs.items():
+        for org in role_orgs.values():
             base.link_managing_org(org["id"], managing_org_id)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@care/emr/management/commands/load_fixtures.py` around lines 310 - 312, The
loop is iterating role_orgs.items() but never uses the key (_name); change the
iteration to loop over role_orgs.values() instead so you only get the org dicts
and avoid the unused _name variable; update the for loop that calls
base.link_managing_org(org["id"], managing_org_id) to iterate directly over
role_orgs.values() and keep the subsequent log call unchanged.

129-131: Hardcoded password for fixture users.

The static analysis flags this (S105), and while it's expected for development fixtures, it would be courteous to add a comment noting this is intentional and should never be used in production — the DEBUG check helps, but documentation never hurts.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@care/emr/management/commands/load_fixtures.py` around lines 129 - 131, The
hardcoded password assigned to the variable password in load_fixtures.py is
flagged (S105); add an explicit inline comment next to the assignment (or above
it) stating this is an intentional development-only fixture password, must never
be used in production, and that the command is guarded by the Django DEBUG
check; reference the variable name password and the surrounding context (e.g.,
the Users section where roles = base.get_roles()) so reviewers and static
analyzers understand this is deliberate.
care/fixtures/base.py (3)

43-45: Slug minimum length enforcement with magic number.

The noqa: PLR2004 suggests awareness of the magic number 5. A named constant would make the intent clearer, though the comment suggests this is intentional minimum slug length.

♻️ Consider extracting constant
+MIN_SLUG_LENGTH = 5
+
 def slugify(text, max_length=36):
     slug = re.sub(r"[^a-z0-9]+", "-", text.lower()).strip("-")[:max_length]
-    return slug if len(slug) >= 5 else slug.ljust(5, "-")  # noqa: PLR2004
+    return slug if len(slug) >= MIN_SLUG_LENGTH else slug.ljust(MIN_SLUG_LENGTH, "-")
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@care/fixtures/base.py` around lines 43 - 45, Replace the magic number in
slugify with a named constant: introduce a module-level constant (e.g.,
MIN_SLUG_LENGTH = 5) and use it instead of the literal 5 in the return
expression inside slugify (and consider using the constant when
validating/documenting the function); remove the noqa PLR2004 comment and ensure
the behavior (min length padding) remains unchanged while max_length parameter
is still applied.

159-159: Hardcoded default password.

Similar to the management command, this default password is fine for development fixtures but should probably be documented as such to avoid any confusion.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@care/fixtures/base.py` at line 159, The fixture currently contains a
hardcoded default password ("Ohcn@123") under the "password" key; update the
code and docs to mark this value as development-only and avoid shipping it as a
production secret: replace the literal with a clearly named constant or helper
(e.g., DEV_FIXTURE_PASSWORD) and load from an environment variable or settings
when available, add a short inline comment above the "password" entry stating
"development-only fixture password — do not use in production", and update any
accompanying fixture documentation to mention the development-only nature and
how to override it.

6-6: Using secrets.choice for non-cryptographic randomness.

secrets.choice (imported on line 6, used on lines 89 and 97) is designed for cryptographic purposes. For fixture data generation, random.choice would be more semantically appropriate and slightly more performant. It's not wrong per se, just... overkill.

♻️ Consider using random.choice instead
-from secrets import choice
+from random import choice

Also applies to: 89-89, 97-97

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@care/fixtures/base.py` at line 6, The file imports secrets.choice for fixture
randomness but that's intended for crypto use; replace it with the
non-cryptographic random.choice to be semantically correct and faster: change
the import statement "from secrets import choice" to use the random module
(e.g., "from random import choice" or "import random" and call random.choice)
and update any usages referenced (the choice calls around the previous line 89
and 97) so they call the random implementation instead of secrets.choice.
care/fixtures/context.py (1)

43-56: Unconditional password reset and save.

The code always calls set_password() and save() even when the user already exists with the correct attributes. This works, but it's slightly inefficient. It might also be intentional to ensure the password is reset to a known value — if so, a brief comment would help future readers understand this isn't just an oversight.

♻️ Optional: Only save when necessary
             superuser, created = user_model.objects.get_or_create(
                 username="admin",
                 defaults={
                     "first_name": "Admin",
                     "last_name": "User",
                     "email": "admin@care.test",
                     "is_superuser": True,
                     "is_staff": True,
                 },
             )
-            superuser.set_password("admin")
-            superuser.is_superuser = True
-            superuser.is_staff = True
-            superuser.save()
+            if created:
+                superuser.set_password("admin")
+                superuser.save()
+            elif not superuser.is_superuser or not superuser.is_staff:
+                # Ensure existing user has required permissions
+                superuser.is_superuser = True
+                superuser.is_staff = True
+                superuser.save()
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@care/fixtures/context.py` around lines 43 - 56, The code unconditionally
resets the admin password and re-saves the user after get_or_create; update the
block around user_model.objects.get_or_create to only call
superuser.set_password("admin") and superuser.save() when the user was newly
created (check the second return value from get_or_create) or when
attributes/password differ, or if the reset is intentional add a brief comment
above the set_password/save lines explaining that the admin password must be
enforced on every run; reference the superuser variable and the
set_password/save calls to locate where to change behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@care/emr/management/commands/load_fixtures.py`:
- Around line 327-335: The current catch-all except in the "assign" branch
swallows all errors; update the try/except around base.get_user(...) and
base.assign_org_role(...) to catch the specific FixtureError (import
FixtureError) and handle it by logging the existing warning, and optionally add
a second except Exception as e that re-raises or logs the exception type and
details (use type(e).__name__ with the message) so unexpected errors aren't
silently ignored; reference user_def, base.get_user, base.assign_org_role, and
the log call when making the change.

In `@care/fixtures/base.py`:
- Around line 213-218: The loop over `questionnaires` is silently swallowing
`FixtureError` in the `for questionnaire_data in questionnaires:` block by using
`except FixtureError: pass`; change this to log a warning instead (e.g.,
`logger.warning` or `self.logger.warning`) including the `questionnaire_data`
context and the exception details (use `exc_info=True` or include `str(e)`),
then continue; update the `except FixtureError as e:` clause around the call to
`self.create_questionnaire(organizations, questionnaire_data)` so failures are
recorded rather than ignored.

In `@care/fixtures/constants.py`:
- Around line 299-315: The function make_container defines parameters
is_derived, preference, and single_use but does not use them; update the
function to either remove these unused parameters from the signature or include
them in the returned dictionary (e.g., add keys "is_derived": is_derived,
"preference": preference, and "single_use": single_use) so the signature matches
the output; modify make_container accordingly and adjust any callers/tests if
you choose to remove parameters.

In `@care/fixtures/context.py`:
- Around line 18-25: The _NoOpLock class is missing the context manager protocol
used by the Lock base class; add __enter__ and __exit__ methods to _NoOpLock so
it can be used with "with". Implement __enter__ to call acquire() and return
self, and implement __exit__(exc_type, exc, tb) to call release() (and return
False/None so exceptions propagate). Keep the existing acquire() and release()
behavior and ensure method names match exactly: class _NoOpLock, __enter__,
__exit__, acquire, release.
- Around line 11-15: Currently the module-level lambda assignment to
care.emr.utils.valueset_coding_type.validate_valueset creates a global
side-effect on import; remove that top-level monkey-patch and instead patch
validate_valueset inside the context manager care_fixture_context (or a setup
method on CareFixtureBase) so the override is applied only while the fixture
context is active, capturing the original function before replacing it and
restoring the original on context exit to avoid leaking the change to other
tests or modules; reference the validate_valueset symbol and
care_fixture_context (or CareFixtureBase setup/teardown) when implementing the
scoped patch and restore logic.

---

Nitpick comments:
In `@care/emr/management/commands/load_fixtures.py`:
- Around line 310-312: The loop is iterating role_orgs.items() but never uses
the key (_name); change the iteration to loop over role_orgs.values() instead so
you only get the org dicts and avoid the unused _name variable; update the for
loop that calls base.link_managing_org(org["id"], managing_org_id) to iterate
directly over role_orgs.values() and keep the subsequent log call unchanged.
- Around line 129-131: The hardcoded password assigned to the variable password
in load_fixtures.py is flagged (S105); add an explicit inline comment next to
the assignment (or above it) stating this is an intentional development-only
fixture password, must never be used in production, and that the command is
guarded by the Django DEBUG check; reference the variable name password and the
surrounding context (e.g., the Users section where roles = base.get_roles()) so
reviewers and static analyzers understand this is deliberate.

In `@care/fixtures/base.py`:
- Around line 43-45: Replace the magic number in slugify with a named constant:
introduce a module-level constant (e.g., MIN_SLUG_LENGTH = 5) and use it instead
of the literal 5 in the return expression inside slugify (and consider using the
constant when validating/documenting the function); remove the noqa PLR2004
comment and ensure the behavior (min length padding) remains unchanged while
max_length parameter is still applied.
- Line 159: The fixture currently contains a hardcoded default password
("Ohcn@123") under the "password" key; update the code and docs to mark this
value as development-only and avoid shipping it as a production secret: replace
the literal with a clearly named constant or helper (e.g., DEV_FIXTURE_PASSWORD)
and load from an environment variable or settings when available, add a short
inline comment above the "password" entry stating "development-only fixture
password — do not use in production", and update any accompanying fixture
documentation to mention the development-only nature and how to override it.
- Line 6: The file imports secrets.choice for fixture randomness but that's
intended for crypto use; replace it with the non-cryptographic random.choice to
be semantically correct and faster: change the import statement "from secrets
import choice" to use the random module (e.g., "from random import choice" or
"import random" and call random.choice) and update any usages referenced (the
choice calls around the previous line 89 and 97) so they call the random
implementation instead of secrets.choice.

In `@care/fixtures/context.py`:
- Around line 43-56: The code unconditionally resets the admin password and
re-saves the user after get_or_create; update the block around
user_model.objects.get_or_create to only call superuser.set_password("admin")
and superuser.save() when the user was newly created (check the second return
value from get_or_create) or when attributes/password differ, or if the reset is
intentional add a brief comment above the set_password/save lines explaining
that the admin password must be enforced on every run; reference the superuser
variable and the set_password/save calls to locate where to change behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: b79bbdb9-4964-4030-a0f3-a3e8e9cdd324

📥 Commits

Reviewing files that changed from the base of the PR and between 2645cb0 and 48af5a7.

📒 Files selected for processing (5)
  • care/emr/management/commands/load_fixtures.py
  • care/fixtures/__init__.py
  • care/fixtures/base.py
  • care/fixtures/constants.py
  • care/fixtures/context.py

Comment thread care/emr/management/commands/load_fixtures.py Outdated
Comment thread care/fixtures/base.py
Comment thread care/fixtures/constants.py
Comment thread care/fixtures/context.py
Comment thread care/fixtures/context.py
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@care/fixtures/fixtures.md`:
- Around line 7-14: Update the fenced code block in fixtures.md so the opening
triple backticks include a language tag (e.g., change ``` to ```text) to satisfy
markdown linting and improve tooling; locate the fenced block that contains the
directory listing (starts with "care/fixtures/") and modify its opening
backticks to include the language identifier.
- Line 252: The tip currently suggests using base.get_roles() but the docs also
expose get_role_org_roles(); update the text to explicitly state the difference
and recommend which to use: use base.get_roles() when you need the complete set
of roles across contexts, and use base.get_role_org_roles() when you only want
organization-scoped (ROLE_ORG) roles; change the sentence mentioning
base.get_roles() to mention both methods and a short guidance clause indicating
which method to call depending on whether you need all roles or only org-scoped
roles (reference base.get_roles() and base.get_role_org_roles()).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: bd30a64f-9167-4570-8c64-4771159ef02c

📥 Commits

Reviewing files that changed from the base of the PR and between 48af5a7 and 5f5b9e1.

📒 Files selected for processing (1)
  • care/fixtures/fixtures.md

Comment thread care/fixtures/fixtures.md
Comment thread care/fixtures/fixtures.md
@praffq praffq changed the title fixtures v2 CARE-23: implement fixtures v2 Apr 5, 2026
@codecov
Copy link
Copy Markdown

codecov bot commented Apr 5, 2026

Codecov Report

❌ Patch coverage is 0% with 279 lines in your changes missing coverage. Please review.
✅ Project coverage is 76.25%. Comparing base (2645cb0) to head (a806041).

Files with missing lines Patch % Lines
care/fixtures/base.py 0.00% 173 Missing ⚠️
care/fixtures/constants.py 0.00% 71 Missing ⚠️
care/fixtures/context.py 0.00% 35 Missing ⚠️
Additional details and impacted files
@@             Coverage Diff             @@
##           develop    #3618      +/-   ##
===========================================
- Coverage    77.20%   76.25%   -0.95%     
===========================================
  Files          474      477       +3     
  Lines        22421    22700     +279     
  Branches      2348     2361      +13     
===========================================
  Hits         17310    17310              
- Misses        4531     4810     +279     
  Partials       580      580              

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
care/emr/management/commands/load_fixtures.py (1)

38-38: load_fixtures is still doing too much despite noqa.

The suppression works, sure, but this method is still a long orchestration blob. Consider extracting chunks (org bootstrap, facility bootstrap, user bootstrap, patient/encounter bootstrap, summary output) into smaller methods.

As per coding guidelines, "Prioritize readability and maintainability; follow Django's coding style guide (PEP 8 compliance)."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@care/emr/management/commands/load_fixtures.py` at line 38, The load_fixtures
method is too large and should be split into smaller, focused helpers: extract
organization bootstrap logic into a method like _bootstrap_organizations(base),
facility creation into _bootstrap_facilities(base, orgs), user/account setup
into _bootstrap_users(base, facilities), patient and encounter creation into
_bootstrap_patients_and_encounters(base, users, facilities), and summary/logging
into _print_fixture_summary(results); then have load_fixtures orchestrate by
calling these new methods in sequence, pass only necessary data between them,
move related blocks of code into their respective functions, update any local
variables to returned values, and adjust or add tests if they reference internal
state.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@care/emr/management/commands/load_fixtures.py`:
- Around line 182-190: The lab and pharmacy locations are being attached to
departments["General Medicine"] incorrectly; update the calls to
base.create_location that create lab_location and pharmacy_location (and the
analogous block at lines ~218-226) so their organizations argument uses the
correct department IDs departments["Laboratory"]["id"] and
departments["Pharmacy"]["id"] respectively instead of general_medicine["id"],
ensuring the Laboratory and Pharmacy departments are actually referenced.
- Line 40: The file sets a hardcoded variable named password and prints it
later; replace the plaintext credential with a secure source (e.g., read from an
environment variable like os.environ.get("FIXTURE_ADMIN_PASSWORD") or prompt
with getpass.getpass() inside the management command), remove any direct
printing of the password (the print/log calls around lines that print the
password), and ensure the command falls back to a safe generated or randomized
password if none is provided; update the variable usage (password) in the
command’s handle/setup logic accordingly and remove any stdout/stderr leaks so
credentials are never written to logs or console.

---

Nitpick comments:
In `@care/emr/management/commands/load_fixtures.py`:
- Line 38: The load_fixtures method is too large and should be split into
smaller, focused helpers: extract organization bootstrap logic into a method
like _bootstrap_organizations(base), facility creation into
_bootstrap_facilities(base, orgs), user/account setup into
_bootstrap_users(base, facilities), patient and encounter creation into
_bootstrap_patients_and_encounters(base, users, facilities), and summary/logging
into _print_fixture_summary(results); then have load_fixtures orchestrate by
calling these new methods in sequence, pass only necessary data between them,
move related blocks of code into their respective functions, update any local
variables to returned values, and adjust or add tests if they reference internal
state.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: ec2ba151-60bf-47da-b052-e1f6cdfdc8e8

📥 Commits

Reviewing files that changed from the base of the PR and between 5f5b9e1 and 410d51c.

📒 Files selected for processing (1)
  • care/emr/management/commands/load_fixtures.py

Comment thread care/emr/management/commands/load_fixtures.py
Comment thread care/emr/management/commands/load_fixtures.py Outdated
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
care/emr/management/commands/load_fixtures.py (1)

50-57: List comprehension would be more Pythonic.

Not that there's anything wrong with append() in a loop, but list comprehensions are generally preferred in Python for simple cases like this.

♻️ Suggested refactor
-        suppliers = []
-        for _ in range(3):
-            suppliers.append(
-                base.create_organization(
-                    org_type=OrganizationTypeChoices.product_supplier.value,
-                    name=f"Supplier {base.fake.company()}",
-                )
-            )
+        suppliers = [
+            base.create_organization(
+                org_type=OrganizationTypeChoices.product_supplier.value,
+                name=f"Supplier {base.fake.company()}",
+            )
+            for _ in range(3)
+        ]

Similarly for patients at lines 142-144:

-        patients = []
-        for _ in range(10):
-            patients.append(base.create_patient(geo_organization["id"]))
+        patients = [base.create_patient(geo_organization["id"]) for _ in range(10)]
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@care/emr/management/commands/load_fixtures.py` around lines 50 - 57, Replace
the manual loop that builds suppliers with a list comprehension to be more
Pythonic: create the suppliers list using base.create_organization(...) inside a
comprehension that iterates three times and uses
OrganizationTypeChoices.product_supplier.value and f"Supplier
{base.fake.company()}" (same change should be applied to the patients list
construction where a loop is used around base.create_patient or similar); update
the variables named suppliers and patients accordingly to use the comprehension
form.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@care/emr/management/commands/load_fixtures.py`:
- Around line 296-298: The code assumes base.get_user(user_def["username"])
returns a dict with an "id" key; update the assign branch to explicitly validate
the user response from get_user before calling base.assign_org_role (check that
user_data is truthy and contains "id"); if missing, raise a clear exception or
log an explicit error mentioning user_def["username"], managing_org_id and
role_id so the failure distinguishes "user not found" from other errors and
fails fast inside the transaction. Ensure you reference get_user and
assign_org_role and use user_def["username"] in the diagnostic message.

---

Nitpick comments:
In `@care/emr/management/commands/load_fixtures.py`:
- Around line 50-57: Replace the manual loop that builds suppliers with a list
comprehension to be more Pythonic: create the suppliers list using
base.create_organization(...) inside a comprehension that iterates three times
and uses OrganizationTypeChoices.product_supplier.value and f"Supplier
{base.fake.company()}" (same change should be applied to the patients list
construction where a loop is used around base.create_patient or similar); update
the variables named suppliers and patients accordingly to use the comprehension
form.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: fc12ca96-6258-443d-8c55-d3517516cf64

📥 Commits

Reviewing files that changed from the base of the PR and between 410d51c and 168fdee.

📒 Files selected for processing (1)
  • care/emr/management/commands/load_fixtures.py

Comment thread care/emr/management/commands/load_fixtures.py Outdated
Comment thread care/fixtures/fixtures.md Outdated
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (4)
care/emr/management/commands/load_fixtures.py (2)

294-296: Consider explicit error handling for user lookup during assignment.

The assign action calls base.get_user() which will raise FixtureError if the user doesn't exist (HTTP 404). The transaction will rollback cleanly, but the error message might not clearly indicate that the user was not found.

It might be worth wrapping this in a try/except to provide a more descriptive message, or at least adding a comment noting the assumption that these users exist.

🛡️ Example explicit handling
             elif user_def["action"] == "assign":
+                try:
-                user_data = base.get_user(user_def["username"])
-                base.assign_org_role(managing_org_id, user_data.id, role_id)
+                    user_data = base.get_user(user_def["username"])
+                    base.assign_org_role(managing_org_id, user_data.id, role_id)
+                except FixtureError:
+                    self.stderr.write(
+                        self.style.WARNING(
+                            f"  User '{user_def['username']}' not found, skipping role assignment"
+                        )
+                    )

This would require importing FixtureError from care.fixtures.base.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@care/emr/management/commands/load_fixtures.py` around lines 294 - 296, Wrap
the base.get_user(...) call inside the "assign" branch in a try/except that
catches care.fixtures.base.FixtureError, so you can raise or log a clearer
message that the specified username for user_def["username"] wasn't found before
calling base.assign_org_role(managing_org_id, user_data.id, role_id); import
FixtureError from care.fixtures.base and ensure the new message includes the
username and context (e.g., "failed to assign role: user not found") to make the
cause explicit.

168-177: Printing passwords to stdout in credential table.

The credential table helpfully shows usernames and passwords, which is fine for local development, but these will appear in Docker logs, CI outputs, and anywhere stdout is captured. Just something to be aware of if these fixtures ever run in shared environments.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@care/emr/management/commands/load_fixtures.py` around lines 168 - 177, The
fixture loader is printing plaintext passwords to stdout (see log calls that
reference password, plus the admin and looped entries using default_users and
MANAGING_ORG_USERS); change the output to avoid exposing credentials by either
removing the password column entirely or printing a masked value (e.g., replace
usages of the password variable in the formatted strings with a fixed mask like
"********" or "REDACTED") for all log calls including the admin line and the
loops that reference default_users and MANAGING_ORG_USERS while keeping
usernames and roles intact.
care/fixtures/base.py (2)

180-180: Hardcoded password in user fixture.

The password "Ohcn@123" is hardcoded here as well as in the management command. It would be somewhat convenient to have a single source of truth for fixture passwords, perhaps as a constant or configuration value.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@care/fixtures/base.py` at line 180, The fixture currently hardcodes the
password string ("Ohcn@123") which duplicates a value used elsewhere; introduce
a single source of truth (e.g., define USER_FIXTURE_PASSWORD or FIXTURE_PASSWORD
constant at the top of care/fixtures/base.py or in project settings) and replace
the literal "Ohcn@123" in the fixture (and in the management command) with a
reference to that constant; update the management command to import or read the
same constant (or settings.FIXTURE_PASSWORD) so both places use the single
shared symbol.

113-113: Phone number truncation may produce invalid values.

The [:14] slice on self.fake.phone_number() is a bit... optimistic. Faker's phone_number() can return various formats, and truncation might yield malformed numbers that fail API validation downstream.

Consider using the generate_phone() helper defined earlier in this file (line 58-61), which reliably produces valid Indian mobile numbers.

♻️ Proposed fix
             "address": self.fake.address(),
             "pincode": self.fake.random_int(min=100000, max=999999),
-            "phone_number": self.fake.phone_number()[:14],
+            "phone_number": generate_phone(),
             "latitude": float(self.fake.latitude()),
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@care/fixtures/base.py` at line 113, The current fixture uses a sliced Faker
value ("phone_number": self.fake.phone_number()[:14]) which can produce
malformed numbers; replace that expression with the dedicated helper defined
earlier (generate_phone() at lines ~58-61) — e.g., call generate_phone() or
self.generate_phone() as appropriate in this class so the fixture uses the
guaranteed-valid Indian mobile number generator instead of truncating Faker
output.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@care/emr/management/commands/load_fixtures.py`:
- Around line 294-296: Wrap the base.get_user(...) call inside the "assign"
branch in a try/except that catches care.fixtures.base.FixtureError, so you can
raise or log a clearer message that the specified username for
user_def["username"] wasn't found before calling
base.assign_org_role(managing_org_id, user_data.id, role_id); import
FixtureError from care.fixtures.base and ensure the new message includes the
username and context (e.g., "failed to assign role: user not found") to make the
cause explicit.
- Around line 168-177: The fixture loader is printing plaintext passwords to
stdout (see log calls that reference password, plus the admin and looped entries
using default_users and MANAGING_ORG_USERS); change the output to avoid exposing
credentials by either removing the password column entirely or printing a masked
value (e.g., replace usages of the password variable in the formatted strings
with a fixed mask like "********" or "REDACTED") for all log calls including the
admin line and the loops that reference default_users and MANAGING_ORG_USERS
while keeping usernames and roles intact.

In `@care/fixtures/base.py`:
- Line 180: The fixture currently hardcodes the password string ("Ohcn@123")
which duplicates a value used elsewhere; introduce a single source of truth
(e.g., define USER_FIXTURE_PASSWORD or FIXTURE_PASSWORD constant at the top of
care/fixtures/base.py or in project settings) and replace the literal "Ohcn@123"
in the fixture (and in the management command) with a reference to that
constant; update the management command to import or read the same constant (or
settings.FIXTURE_PASSWORD) so both places use the single shared symbol.
- Line 113: The current fixture uses a sliced Faker value ("phone_number":
self.fake.phone_number()[:14]) which can produce malformed numbers; replace that
expression with the dedicated helper defined earlier (generate_phone() at lines
~58-61) — e.g., call generate_phone() or self.generate_phone() as appropriate in
this class so the fixture uses the guaranteed-valid Indian mobile number
generator instead of truncating Faker output.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 12cc25bd-7455-42c2-970f-6bdccb0501f6

📥 Commits

Reviewing files that changed from the base of the PR and between 168fdee and 07c398b.

📒 Files selected for processing (3)
  • care/emr/management/commands/load_fixtures.py
  • care/fixtures/base.py
  • care/fixtures/fixtures.md

@praffq praffq requested a review from nandkishorr April 9, 2026 07:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants