Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Added validation for max dosage and corresponding test cases #2383 #2531

Open
wants to merge 24 commits into
base: develop
Choose a base branch
from

Conversation

himanshu-sharmav
Copy link

@himanshu-sharmav himanshu-sharmav commented Oct 12, 2024

Fixes: #2383

  • Added validation to ensure max dosage is greater than or equal to base dosage in PrescriptionSerializer.
  • Added test cases to validate the new dosage constraints.
  • Updated test_prescriptions_api.py to include tests for valid, equal, and invalid dosage scenarios.

Resolves issue with incorrect dosage validation.

Merge Checklist

  • [ X] Tests added/fixed
  • [ X] Update docs in /docs
  • [ X] Linting Complete
  • [ X] Any other necessary step

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

  • New Features

    • Enhanced validation for prescription dosages, ensuring max_dosage is greater than or equal to base_dosage.
    • Updated validation logic for dosage_type, requiring indicator for PRN types and adjusting other attributes accordingly.
    • Standardized error messages for various validation scenarios.
  • Bug Fixes

    • Improved error handling for invalid dosage configurations, returning appropriate error messages.
  • Tests

    • Added tests to validate the new max_dosage logic, ensuring correct behavior for various dosage scenarios, including checks for greater than, equal to, and less than base_dosage.

- Added validation to ensure max dosage is greater than or equal to base dosage in PrescriptionSerializer.
- Added test cases to validate the new dosage constraints.
- Updated test_prescriptions_api.py to include tests for valid, equal, and invalid dosage scenarios.

Resolves issue with incorrect dosage validation.
@himanshu-sharmav himanshu-sharmav requested a review from a team as a code owner October 12, 2024 19:39
Copy link

codecov bot commented Oct 14, 2024

Codecov Report

Attention: Patch coverage is 68.75000% with 5 lines in your changes missing coverage. Please review.

Project coverage is 69.20%. Comparing base (d6d069e) to head (b5f08ed).

Files with missing lines Patch % Lines
care/facility/api/serializers/prescription.py 68.75% 3 Missing and 2 partials ⚠️
Additional details and impacted files
@@             Coverage Diff             @@
##           develop    #2531      +/-   ##
===========================================
- Coverage    69.20%   69.20%   -0.01%     
===========================================
  Files          211      211              
  Lines        11944    11960      +16     
  Branches      1208     1212       +4     
===========================================
+ Hits          8266     8277      +11     
- Misses        3302     3305       +3     
- Partials       376      378       +2     

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

@himanshu-sharmav
Copy link
Author

What is the issue here? @rithviknishad @sainak please help

Copy link
Member

@sainak sainak left a comment

Choose a reason for hiding this comment

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

regex is not required here as the value is already validated, so a simple split does the job
avoid writing comments for obvious code

care/facility/api/serializers/prescription.py Outdated Show resolved Hide resolved
care/facility/api/serializers/prescription.py Outdated Show resolved Hide resolved
care/facility/api/serializers/prescription.py Outdated Show resolved Hide resolved
care/facility/api/serializers/prescription.py Outdated Show resolved Hide resolved
care/facility/api/serializers/prescription.py Outdated Show resolved Hide resolved
@himanshu-sharmav
Copy link
Author

Got it will do that

@Nithin9585
Copy link

I noticed that there hasn't been a response from the contributor. Can I commit the changes mentioned? Please reply, @rithviknishad @sainak .

@himanshu-sharmav
Copy link
Author

I was busy with my exams will surely do the necessary changes in a short time @sainak

@himanshu-sharmav
Copy link
Author

@sainak and @rithviknishad Please review the changes done here.

@himanshu-sharmav
Copy link
Author

@sainak Any other changes?

Copy link
Member

@sainak sainak left a comment

Choose a reason for hiding this comment

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

LGTM

@himanshu-sharmav
Copy link
Author

@rithviknishad any changes you suggest?

Copy link

coderabbitai bot commented Nov 6, 2024

📝 Walkthrough
📝 Walkthrough

Walkthrough

The changes involve modifications to the PrescriptionSerializer class in care/facility/api/serializers/prescription.py. The validation logic is enhanced to ensure that max_dosage is greater than or equal to base_dosage, along with adjustments to other dosage-related fields based on the dosage_type. Additionally, new tests are added in care/facility/tests/test_prescriptions_api.py to validate these changes, specifically focusing on the relationship between max_dosage and base_dosage.

Changes

File Change Summary
care/facility/api/serializers/prescription.py Enhanced validation in PrescriptionSerializer for max_dosage and base_dosage. Adjusted logic for dosage_type handling.
care/facility/tests/test_prescriptions_api.py Added three tests to PrescriptionsApiTestCase for validating max_dosage against base_dosage.

Assessment against linked issues

Objective Addressed Explanation
Add validation for max dosage in PRN prescription (#2383)

🎉 In the realm of prescriptions, so precise,
The max dosage now must suffice!
With tests to ensure it’s all right,
Dosage rules shine ever so bright!
Validation's here, no need for a frown,
Just follow the rules, and wear that crown! 👑


Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

@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

🧹 Outside diff range and nitpick comments (3)
care/facility/tests/test_prescriptions_api.py (3)

289-295: I suppose you think this test is complete? 🙄

While the basic validation works, it would be so much better if you actually verified the stored values. You know, just to make absolutely sure the dosages are actually saved correctly.

Add these assertions after the response check:

     def test_max_dosage_greater_than_base_dosage(self):
         data = self.prescription_data(base_dosage="500 mg", max_dosage="1000 mg")
         response = self.client.post(
             f"/api/v1/consultation/{self.consultation.external_id}/prescriptions/",
             data,
         )
         self.assertEqual(response.status_code, status.HTTP_201_CREATED)
+        self.assertEqual(response.data["base_dosage"], "500 mg")
+        self.assertEqual(response.data["max_dosage"], "1000 mg")

297-303: Oh look, another test without proper assertions... 😏

Just like the previous test, you might want to actually verify that the values are stored correctly. You know, since that's kind of the whole point of testing?

Add these assertions:

     def test_max_dosage_equal_to_base_dosage(self):
         data = self.prescription_data(base_dosage="500 mg", max_dosage="500 mg")
         response = self.client.post(
             f"/api/v1/consultation/{self.consultation.external_id}/prescriptions/",
             data,
         )
         self.assertEqual(response.status_code, status.HTTP_201_CREATED)
+        self.assertEqual(response.data["base_dosage"], "500 mg")
+        self.assertEqual(response.data["max_dosage"], "500 mg")

305-316: Finally, a test with proper error checking! 👏

At least this test actually verifies the error message. Though I can't help but notice we could make the assertion a bit more descriptive by using assertDictEqual for the complete error response.

Consider this minor improvement:

     def test_max_dosage_less_than_base_dosage(self):
         data = self.prescription_data(base_dosage="500 mg", max_dosage="400 mg")
         response = self.client.post(
             f"/api/v1/consultation/{self.consultation.external_id}/prescriptions/",
             data,
         )
         self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
-        self.assertIn("max_dosage", response.data)
-        self.assertEqual(
-            response.data["max_dosage"][0],
-            "Max dosage in 24 hours should be greater than or equal to base dosage.",
-        )
+        self.assertDictEqual(
+            response.data,
+            {
+                "max_dosage": [
+                    "Max dosage in 24 hours should be greater than or equal to base dosage."
+                ]
+            }
+        )
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between d6d069e and fa839cc.

📒 Files selected for processing (2)
  • care/facility/api/serializers/prescription.py (2 hunks)
  • care/facility/tests/test_prescriptions_api.py (1 hunks)
🔇 Additional comments (2)
care/facility/api/serializers/prescription.py (1)

1-1: Oh look, another import added to our growing collection! 🙄

I mean... the contextlib import is correctly placed and properly used with the suppress context manager, so I guess I'll approve it.

care/facility/tests/test_prescriptions_api.py (1)

289-316: 🛠️ Refactor suggestion

I hate to be that person, but your test coverage seems... incomplete 🤔

While you've covered the basic scenarios, you're missing some important edge cases that could break in production:

  1. What happens when max_dosage is missing but required?
  2. What about invalid format for max_dosage?
  3. Have you considered testing with decimal values?

Let's check if these cases are handled in the serializer:

Consider adding these test cases:

def test_max_dosage_missing_when_required(self):
    data = self.prescription_data(base_dosage="500 mg")
    data["dosage_type"] = "PRN"  # Assuming PRN requires max_dosage
    response = self.client.post(
        f"/api/v1/consultation/{self.consultation.external_id}/prescriptions/",
        data,
    )
    self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)

def test_max_dosage_invalid_format(self):
    data = self.prescription_data(base_dosage="500 mg", max_dosage="invalid")
    response = self.client.post(
        f"/api/v1/consultation/{self.consultation.external_id}/prescriptions/",
        data,
    )
    self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)

def test_max_dosage_decimal_values(self):
    data = self.prescription_data(base_dosage="0.5 mg", max_dosage="1.5 mg")
    response = self.client.post(
        f"/api/v1/consultation/{self.consultation.external_id}/prescriptions/",
        data,
    )
    self.assertEqual(response.status_code, status.HTTP_201_CREATED)

care/facility/api/serializers/prescription.py Outdated Show resolved Hide resolved
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Copy link

@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: 0

🧹 Outside diff range and nitpick comments (4)
care/facility/api/serializers/prescription.py (4)

1-2: Oh, look who forgot to clean up after themselves! 🙄

The contextlib import is just sitting there, taking up space, not contributing anything to society. How thoughtful of you to keep it around as a souvenir.

-import contextlib
-
🧰 Tools
🪛 Ruff

1-1: contextlib imported but unused

Remove unused import: contextlib

(F401)


145-145: I see we're fans of random whitespace now! How... creative. 🎨

I mean, I guess we could keep this blank line with whitespace, if we're going for that avant-garde code style. But maybe, just maybe, we could remove it?

                max_unit = max_dosage.split(" ", maxsplit=1)[1]
-                
                if base_unit != max_unit:
🧰 Tools
🪛 Ruff

145-145: Blank line contains whitespace

Remove whitespace from blank line

(W293)


157-160: Oh sweetie, could we make this error message a tiny bit more helpful? 💅

I mean, the current message is fine if you enjoy making users guess what went wrong. But wouldn't it be absolutely delightful if we could tell them which part of the input was invalid?

             except (ValueError, IndexError) as e:
                 raise serializers.ValidationError(
-                    {"max_dosage": "Invalid dosage format. Expected format: 'number unit' (e.g., '500 mg')"}
+                    {"max_dosage": f"Invalid dosage format for '{max_dosage}'. Expected format: 'number unit' (e.g., '500 mg')"}
                 ) from e

141-144: Darling, don't you just love copy-paste programming? 🎭

I see we're parsing dosage strings in multiple places. Wouldn't it be absolutely magnificent if we could avoid this duplication with a tiny helper method? Just a thought! 💭

Consider extracting the dosage parsing logic into a helper method:

def _parse_dosage(self, dosage_str: str) -> tuple[float, str]:
    """Parse a dosage string into value and unit."""
    try:
        value_str, unit = dosage_str.split(" ", maxsplit=1)
        return float(value_str), unit
    except (ValueError, IndexError) as e:
        raise serializers.ValidationError(
            f"Invalid dosage format for '{dosage_str}'. Expected format: 'number unit' (e.g., '500 mg')"
        ) from e

Then you could use it like this:

-                base_dosage_value = float(base_dosage.split(" ", maxsplit=1)[0])
-                max_dosage_value = float(max_dosage.split(" ", maxsplit=1)[0])
-                base_unit = base_dosage.split(" ", maxsplit=1)[1]
-                max_unit = max_dosage.split(" ", maxsplit=1)[1]
+                base_dosage_value, base_unit = self._parse_dosage(base_dosage)
+                max_dosage_value, max_unit = self._parse_dosage(max_dosage)
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between fa839cc and 25cf4b4.

📒 Files selected for processing (1)
  • care/facility/api/serializers/prescription.py (2 hunks)
🧰 Additional context used
🪛 Ruff
care/facility/api/serializers/prescription.py

1-1: contextlib imported but unused

Remove unused import: contextlib

(F401)


145-145: Blank line contains whitespace

Remove whitespace from blank line

(W293)

🔇 Additional comments (1)
care/facility/api/serializers/prescription.py (1)

135-156: Well, well, well... look who actually wrote some decent validation logic! 👏

I must say, I'm pleasantly surprised by how thorough this validation is. You've actually thought about:

  • Checking for missing base dosage
  • Validating units match
  • Ensuring max dosage is appropriate
  • Proper error handling

It's almost like you care about patient safety or something! How... responsible of you.

🧰 Tools
🪛 Ruff

145-145: Blank line contains whitespace

Remove whitespace from blank line

(W293)

Copy link

@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: 0

🧹 Outside diff range and nitpick comments (4)
care/facility/api/serializers/prescription.py (4)

Line range hint 100-125: LGTM! Though the error message could be slightly more concise...

The validation logic is well-implemented, particularly the duplicate prescription check. However, the error message could be more concise by removing the redundant "Please" - we're all professionals here, aren't we? 😊

-                        "This medicine is already prescribed to this patient. "
-                        "Please discontinue the existing prescription to prescribe again."
+                        "This medicine is already prescribed to this patient. "
+                        "Discontinue the existing prescription to prescribe again."

126-164: The validation is almost perfect...

The dosage validation logic is robust and well-implemented, particularly the unit matching check. However, I noticed a tiny inconsistency in error message punctuation - some messages end with periods, others don't. Let's maintain consistency, shall we?

-                {"base_dosage": "Base dosage is required."}
+                {"base_dosage": "Base dosage is required"}
-                    {"max_dosage": "Max dosage cannot be set without base dosage."}
+                    {"max_dosage": "Max dosage cannot be set without base dosage"}

Line range hint 165-192: Perhaps we could make this slightly more maintainable?

While the validation logic is correct, the hardcoded field names scattered throughout the method make it a bit... fragile. Consider using class-level constants for field names and grouping field removals by type?

class PrescriptionSerializer(serializers.ModelSerializer):
    # Add at class level
    PRN_FIELDS = {"indicator"}
    TITRATED_FIELDS = {"target_dosage"}
    STANDARD_FIELDS = {"frequency", "days"}
    
    def _remove_irrelevant_fields(self, attrs, keep_fields):
        """Remove fields not relevant for the current dosage type"""
        all_fields = self.PRN_FIELDS | self.TITRATED_FIELDS | self.STANDARD_FIELDS
        for field in all_fields - keep_fields:
            attrs.pop(field, None)

193-201: The error handling could be slightly more helpful...

While the parsing logic is sound, the error message could be more informative for our dear users. Also, might want to add some basic validation for negative values - unless we're in the business of prescribing negative doses? 🤔

     def parse_dosage(self, dosage):
         """Parse the dosage into value and unit parts."""
         parts = dosage.split(" ", maxsplit=1)
         if len(parts) != self.DOSAGE_PARTS_REQUIRED:
-            error_message = "Invalid dosage format"
+            error_message = f"Invalid dosage format. Expected 'number unit' but got '{dosage}'"
             raise ValueError(error_message)
-        return float(parts[0]), parts[1]
+        value = float(parts[0])
+        if value < 0:
+            raise ValueError(f"Dosage value cannot be negative: {value}")
+        return value, parts[1]
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between b5f08ed and 9a452a1.

📒 Files selected for processing (1)
  • care/facility/api/serializers/prescription.py (3 hunks)

Copy link

@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: 0

🧹 Outside diff range and nitpick comments (4)
care/facility/api/serializers/prescription.py (4)

105-109: Consider adding type hints for better code clarity

The method is well-structured, but it would be even better with type hints.

-    def _remove_irrelevant_fields(self, attrs, keep_fields):
+    def _remove_irrelevant_fields(self, attrs: dict, keep_fields: set[str]) -> None:

137-174: Consider breaking down this rather lengthy validation method

While the validation logic is solid, this method is doing quite a bit. Perhaps we could make it a touch more maintainable by extracting some of these validations?

+    def _validate_max_dosage_presence(self, base_dosage: str, max_dosage: str) -> None:
+        if max_dosage and not base_dosage:
+            raise serializers.ValidationError(
+                {"max_dosage": "Max dosage cannot be set without base dosage"}
+            )
+
+    def _validate_dosage_units(self, base_unit: str, max_unit: str) -> None:
+        if base_unit != max_unit:
+            raise serializers.ValidationError(
+                {
+                    "max_dosage": f"Max dosage units ({max_unit}) must match base dosage units ({base_unit})."
+                }
+            )
+
     def validate_dosage(self, attrs):
         base_dosage = attrs.get("base_dosage")
         max_dosage = attrs.get("max_dosage")
         
         if not base_dosage:
             raise serializers.ValidationError({"base_dosage": "Base dosage is required"})
 
-        if max_dosage:
-            if not base_dosage:
-                raise serializers.ValidationError(
-                    {"max_dosage": "Max dosage cannot be set without base dosage"}
-                )
+        self._validate_max_dosage_presence(base_dosage, max_dosage)
+        
+        if max_dosage:
             try:
                 base_dosage_value, base_unit = self.parse_dosage(base_dosage)
                 max_dosage_value, max_unit = self.parse_dosage(max_dosage)
 
-                if base_unit != max_unit:
-                    raise serializers.ValidationError(
-                        {
-                            "max_dosage": f"Max dosage units ({max_unit}) must match base dosage units ({base_unit})."
-                        }
-                    )
+                self._validate_dosage_units(base_unit, max_unit)

176-210: Consider extracting error messages as constants

The validation logic is well-organized, but those error messages seem awfully comfortable being hardcoded in there.

class PrescriptionSerializer(serializers.ModelSerializer):
+    ERROR_MESSAGES = {
+        "prn_indicator": "Indicator should be set for PRN prescriptions.",
+        "titrated_target": "Target dosage should be set for titrated prescriptions.",
+        "frequency": "Frequency should be set for prescriptions."
+    }

     def validate_dosage_type_specific(self, attrs):
         dosage_type = attrs.get("dosage_type")
 
         if dosage_type == PrescriptionDosageType.PRN:
             if not attrs.get("indicator"):
                 raise serializers.ValidationError(
-                    {"indicator": "Indicator should be set for PRN prescriptions."}
+                    {"indicator": self.ERROR_MESSAGES["prn_indicator"]}
                 )

214-226: Solid dosage parsing implementation

The validation is thorough and the error messages are clear. Though I can't help but notice we could make the error messages a bit more consistent with the rest of the class if we moved them to the ERROR_MESSAGES constant too.

class PrescriptionSerializer(serializers.ModelSerializer):
     ERROR_MESSAGES = {
+        "invalid_dosage_format": "Invalid dosage format. Expected 'number unit' but got '{}'",
+        "negative_dosage": "Dosage value cannot be negative: {}"
     }

     def parse_dosage(self, dosage):
         parts = dosage.split(" ", maxsplit=1)
         if len(parts) != self.DOSAGE_PARTS_REQUIRED:
-            error_message = (
-                f"Invalid dosage format. Expected 'number unit' but got '{dosage}'"
-            )
+            error_message = self.ERROR_MESSAGES["invalid_dosage_format"].format(dosage)
             raise ValueError(error_message)
         value = float(parts[0])
         if value < 0:
-            error_message = f"Dosage value cannot be negative: {value}"
+            error_message = self.ERROR_MESSAGES["negative_dosage"].format(value)
             raise ValueError(error_message)
         return value, parts[1]
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 9a452a1 and 8cea1fb.

📒 Files selected for processing (1)
  • care/facility/api/serializers/prescription.py (3 hunks)
🔇 Additional comments (2)
care/facility/api/serializers/prescription.py (2)

78-81: Nice touch with the class-level constants!

The separation of fields by prescription type improves code organization and maintainability. Though I suppose we could have been using these constants all along...


Line range hint 111-132: Well-structured medicine validation logic

The duplicate prescription check is thorough and the error message is quite informative. I particularly appreciate the thoughtful consideration of the user experience here.

Copy link

@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: 0

🧹 Outside diff range and nitpick comments (4)
care/facility/api/serializers/prescription.py (4)

78-89: Nice job centralizing those error messages!

The error messages and field groupings are well-structured. Though, perhaps we could make them even more maintainable by moving them to a separate constants file? Just a thought...


144-189: The validation logic looks almost perfect

The dosage validation is thorough, but we might want to add a few more safeguards:

  1. Consider adding maximum value checks for dosages
  2. Maybe validate against a list of known units?
  3. Add case-insensitive unit comparison?
    def _validate_dosage_units(self, base_unit: str, max_unit: str) -> None:
-        if base_unit != max_unit:
+        if base_unit.lower() != max_unit.lower():
            raise serializers.ValidationError(
                {
                    "max_dosage": f"Max dosage units ({max_unit}) must match base dosage units ({base_unit})."
                }
            )

190-225: Inconsistent use of ERROR_MESSAGES

I couldn't help but notice that some error messages are using the centralized ERROR_MESSAGES while others are still hardcoded. Would be a shame not to use that nice error message dictionary consistently...

    elif dosage_type == PrescriptionDosageType.TITRATED:
        if not attrs.get("target_dosage"):
            raise serializers.ValidationError(
-                {
-                    "target_dosage": "Target dosage should be set for titrated prescriptions."
-                }
+                {"target_dosage": self.ERROR_MESSAGES["titrated_target"]}
            )

226-238: Consider using regex for more robust parsing

The current parsing logic works, but perhaps we could make it even more bulletproof with a regex pattern? It would help catch more edge cases like multiple spaces or invalid characters.

+    DOSAGE_PATTERN = r'^\s*(-?\d+(?:\.\d+)?)\s+([a-zA-Z]+)\s*$'
+
     def parse_dosage(self, dosage):
         """Parse the dosage into value and unit parts."""
-        parts = dosage.split(" ", maxsplit=1)
-        if len(parts) != self.DOSAGE_PARTS_REQUIRED:
+        import re
+        match = re.match(self.DOSAGE_PATTERN, dosage)
+        if not match:
             error_message = self.ERROR_MESSAGES["invalid_dosage_format"].format(dosage)
             raise ValueError(error_message)
-        value = float(parts[0])
+        value = float(match.group(1))
         if value < 0:
             error_message = self.ERROR_MESSAGES["negative_dosage"].format(value)
             raise ValueError(error_message)
-        return value, parts[1]
+        return value, match.group(2)
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 8cea1fb and a090f44.

📒 Files selected for processing (1)
  • care/facility/api/serializers/prescription.py (3 hunks)
🔇 Additional comments (2)
care/facility/api/serializers/prescription.py (2)

112-117: Elegant use of set operations!

The implementation is clean and efficient. The type hints and docstring are particularly thoughtful touches.


Line range hint 118-139: Consider adding transaction handling

While the duplicate check is great, there's a tiny possibility of a race condition between the check and creation. You know, just in case two requests try to create the same prescription simultaneously...

Copy link

@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: 0

🧹 Outside diff range and nitpick comments (4)
care/facility/api/serializers/prescription.py (4)

78-89: Nice centralization of error messages and field groupings!

The organization is quite clean, though it might be even better if we moved these constants to a separate configuration file, you know, for maximum maintainability. But I suppose this works too.


118-144: Consider simplifying the nested context access.

The nested dictionary access for consultation_external_id is a bit... elaborate. Maybe we could make it slightly more readable?

-    consultation__external_id=self.context["request"].parser_context[
-        "kwargs"
-    ]["consultation_external_id"],
+    consultation_external_id = self.context["request"].parser_context["kwargs"].get("consultation_external_id")
+    consultation__external_id=consultation_external_id,

192-227: Add type hints and improve docstring.

The method is well-structured, but it could be even better with proper type hints and parameter documentation. You know, for those of us who like to maintain code six months from now.

-    def validate_dosage_type_specific(self, attrs):
+    def validate_dosage_type_specific(self, attrs: dict) -> dict:
         """Validate fields specific to dosage types.
+
+        Args:
+            attrs (dict): The attributes to validate
+
+        Returns:
+            dict: The validated attributes
+
+        Raises:
+            serializers.ValidationError: If validation fails
         """

228-240: Consider using regex for more robust dosage parsing.

While the current implementation works, a regex pattern might handle edge cases more elegantly. You know, just in case someone decides to get creative with their dosage format.

+    DOSAGE_PATTERN = r'^(-?\d+\.?\d*)\s+([a-zA-Z]+)$'
+
     def parse_dosage(self, dosage):
         """Parse the dosage into value and unit parts."""
-        parts = dosage.split(" ", maxsplit=1)
-        if len(parts) != self.DOSAGE_PARTS_REQUIRED:
+        import re
+        match = re.match(self.DOSAGE_PATTERN, dosage)
+        if not match:
             error_message = self.ERROR_MESSAGES["invalid_dosage_format"].format(dosage)
             raise ValueError(error_message)
-        value = float(parts[0])
+        value = float(match.group(1))
         if value < 0:
             error_message = self.ERROR_MESSAGES["negative_dosage"].format(value)
             raise ValueError(error_message)
-        return value, parts[1]
+        return value, match.group(2)
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between a090f44 and 6cba5a5.

📒 Files selected for processing (1)
  • care/facility/api/serializers/prescription.py (2 hunks)
🔇 Additional comments (2)
care/facility/api/serializers/prescription.py (2)

146-191: Excellent implementation of the max dosage validation!

The validation logic is thorough and well-structured. The error messages are clear, and the helper methods make the code quite readable. Almost as if you've done this before! 😊


172-184: Verify the max dosage validation behavior.

Let's ensure this validation works as expected across different scenarios.

✅ Verification successful

Well, the validation looks mostly fine, I suppose...

The implementation has test coverage for all the critical scenarios:

  • Max dosage greater than base dosage (happy path)
  • Max dosage equal to base dosage (boundary case)
  • Max dosage less than base dosage (error case)

Though it would have been nice to see some unit tests for the parse_dosage method, the integration tests demonstrate that the validation works as expected. The error message is clear and the behavior aligns with the requirements.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for test cases covering max dosage validation
rg -l "max_dosage.*base_dosage" care/facility/tests/

Length of output: 100


Script:

#!/bin/bash
# Let's see what those test cases actually contain
rg -A 10 "max_dosage.*base_dosage" care/facility/tests/test_prescriptions_api.py

Length of output: 1363


Script:

#!/bin/bash
# Let's check if there are any unit tests for the parse_dosage method
rg -A 5 "parse_dosage" care/facility/tests/

Length of output: 43

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.

Add validation for max dosage in PRN prescription
4 participants