Skip to content

Fix smoking prevention check from rules#633

Open
phc007 wants to merge 5 commits intodevelopfrom
628-bug-prevention-smoking-is-handled-as-if-its-positive-for-ldct
Open

Fix smoking prevention check from rules#633
phc007 wants to merge 5 commits intodevelopfrom
628-bug-prevention-smoking-is-handled-as-if-its-positive-for-ldct

Conversation

@phc007
Copy link
Collaborator

@phc007 phc007 commented Mar 14, 2026

User description

Fix smoking status evaluation used by LDCT eligibility rules by replacing reliance on the incorrect 'never' prevention flag with refused-status-based checks.

Description

Fixes smoking trigger for Low Dose CT prevention alert

Related Issues

Fixes #628

How Was This Tested?

Screenshots

Checklist

  • My commits are signed off for the DCO (git commit -s)
  • My commits follow Conventional Commits format, or I've written clear commit messages and will use the format next time
  • I have not included any patient data (PHI) in this PR
  • I have added tests for new functionality, or this change doesn't need new tests
  • I have read the contributing guide

Summary by Sourcery

Align smoking-related prevention logic with correct refused status semantics for eligibility rules.

New Features:

  • Expose raw refused status values on prevention records and items for use in downstream logic.
  • Add a helper to determine whether a patient is currently smoking based on the latest prevention record.

Bug Fixes:

  • Fix smoking status evaluation used by LDCT eligibility rules by replacing reliance on the incorrect 'never' prevention flag with refused-status-based checks.

Enhancements:

  • Document and standardize the encoding of refused status, including its repurposing for smoking history in the prevention model and rules.

Description

  • Removed the incorrect smoking prevention check and replaced it with a more accurate method for determining current smoking status.
  • Introduced new methods in the Prevention and PreventionItem classes to handle smoking history more effectively.
  • Updated DRL rules to reflect the changes in smoking status evaluation.

Changes walkthrough 📝

Relevant files
Enhancement
Prevention.java
Enhance Prevention Model with Refused Status Handling       

src/main/java/io/github/carlos_emr/carlos/commn/model/Prevention.java

  • Added getRefusedRawValue() method to expose raw refused status.
  • Updated documentation for smoking prevention encoding.
  • +17/-0   
    Prevention.java
    Add Methods for Smoking Status Evaluation                               

    src/main/java/io/github/carlos_emr/carlos/prevention/Prevention.java

  • Introduced getRefusedStatus(String) and isCurrentlySmoking(String)
    methods.
  • Updated documentation to clarify smoking status checks.
  • +54/-0   
    PreventionItem.java
    Update PreventionItem with Refused Status Logic                   

    src/main/java/io/github/carlos_emr/carlos/prevention/PreventionItem.java

  • Added getRefusedStatus() method to retrieve raw refused status.
  • Updated constructor to initialize refusedStatus.
  • +21/-0   
    prevention.drl
    Revise DRL Rules for Smoking Status Checks                             

    src/main/resources/oscar/oscarPrevention/prevention.drl

  • Updated rules to use isCurrentlySmoking("Smoking") instead of
    isPreventionNever("Smoking").
  • Clarified comments regarding smoking status encoding.
  • +19/-6   

    💡 Penify usage:
    Comment /help on the PR to get a list of all available Penify tools and their descriptions

    Summary by CodeRabbit

    • New Features

      • Patient-level refused-value reporting and a query to determine current smoking status.
      • Screening logic now uses the new smoking-status query when evaluating LDCT eligibility.
    • Bug Fixes

      • Fixed control-flow issue in the prevention-status check to ensure correct delegation.
    • Documentation

      • Clarified smoking-status semantics and recommended usage in clinical rule comments.

    @phc007 phc007 linked an issue Mar 14, 2026 that may be closed by this pull request
    4 tasks
    @coderabbitai
    Copy link
    Contributor

    coderabbitai bot commented Mar 14, 2026

    No actionable comments were generated in the recent review. 🎉

    ℹ️ Recent review info
    ⚙️ Run configuration

    Configuration used: defaults

    Review profile: CHILL

    Plan: Pro

    Run ID: aeb111f9-83f6-45a0-a0a8-3a951590ce24

    📥 Commits

    Reviewing files that changed from the base of the PR and between 758ffc7 and d39d54c.

    📒 Files selected for processing (1)
    • src/main/java/io/github/carlos_emr/carlos/prevention/Prevention.java

    📝 Walkthrough

    Walkthrough

    Adds a refused-value API through the Prevention model and PreventionItem, introduces Prevention-level helpers to read refused status and interpret Smoking semantics, and updates an LDCT DRL rule to use the new smoking-status API instead of the previous "never" check.

    Changes

    Cohort / File(s) Summary
    Prevention model
    src/main/java/io/github/carlos_emr/carlos/commn/model/Prevention.java
    Added public int getRefusedRawValue() to decode the refused character to an int (0–3) or return -1 when invalid.
    Prevention item
    src/main/java/io/github/carlos_emr/carlos/prevention/PreventionItem.java
    Added refusedStatus field, initialized from Prevention.getRefusedRawValue(), and public int getRefusedStatus() accessor with documented encoding and Smoking-specific note.
    Prevention business logic
    src/main/java/io/github/carlos_emr/carlos/prevention/Prevention.java
    Fixed recursion bug in isNotPreventionNever; added int getRefusedStatus(String preventionType) and boolean isCurrentlySmoking(String preventionType) to expose latest refused status and interpret Smoking (refused == 0 => currently smoking).
    DRL rules
    src/main/resources/oscar/oscarPrevention/prevention.drl
    Updated header comments to document new APIs and Smoking encoding; changed LDCT precondition from isPreventionNever("Smoking") to isCurrentlySmoking("Smoking").

    Estimated code review effort

    🎯 3 (Moderate) | ⏱️ ~20 minutes

    Suggested reviewers

    • yingbull

    Poem

    🐰 I nudged a char and found a clue,
    A tiny number where a question grew.
    Now Smoking sings when zero's shown,
    Rules follow truth, not silence known.

    🚥 Pre-merge checks | ✅ 4 | ❌ 1

    ❌ Failed checks (1 warning)

    Check name Status Explanation Resolution
    Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
    ✅ Passed checks (4 passed)
    Check name Status Explanation
    Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
    Title check ✅ Passed The title directly corresponds to the main change: fixing a smoking prevention check in the LDCT rules by replacing isPreventionNever with isCurrentlySmoking.
    Linked Issues check ✅ Passed The PR implements all requirements from issue #628: exposes refused-status values via getRefusedRawValue/getRefusedStatus methods, adds isCurrentlySmoking helper, fixes infinite recursion in isNotPreventionNever, and updates DRL to use the correct smoking check.
    Out of Scope Changes check ✅ Passed All changes are directly related to fixing the smoking prevention check: Prevention.java/PreventionItem.java methods for refused-status exposure, DRL rule updates using the new API, and fixing the infinite recursion bug.

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

    ✨ Finishing Touches
    • 📝 Generate docstrings (stacked PR)
    • 📝 Generate docstrings (commit on current branch)
    🧪 Generate unit tests (beta)
    • Create PR with unit tests
    • Commit unit tests in branch 628-bug-prevention-smoking-is-handled-as-if-its-positive-for-ldct
    📝 Coding Plan
    • Generate coding plan for human review comments

    Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

    ❤️ Share

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

    @sourcery-ai
    Copy link
    Contributor

    sourcery-ai bot commented Mar 14, 2026

    Reviewer's Guide

    Refactors how smoking status is derived from prevention records by exposing the raw refused status through the model, plumbs that status into prevention items, and updates both Java helpers and Drools rules to use a new isCurrentlySmoking check instead of the previous isPreventionNever-based smoking prevention logic.

    Sequence diagram for LDCT eligibility smoking check using isCurrentlySmoking

    sequenceDiagram
        actor Clinician
        participant EMR_UI
        participant DroolsEngine
        participant prevention_Prevention
        participant PreventionItem
    
        Clinician->>EMR_UI: Open LDCT eligibility screen
        EMR_UI->>DroolsEngine: Request LDCT eligibility evaluation
    
        DroolsEngine->>prevention_Prevention: isCurrentlySmoking("Smoking")
        activate prevention_Prevention
        prevention_Prevention->>prevention_Prevention: getRefusedStatus("Smoking")
        prevention_Prevention->>PreventionItem: getRefusedStatus()
        PreventionItem-->>prevention_Prevention: refusedStatus (0,1,2,...)
        prevention_Prevention-->>DroolsEngine: true if status == 0
        deactivate prevention_Prevention
    
        DroolsEngine-->>EMR_UI: LDCT eligibility result
        EMR_UI-->>Clinician: Display eligibility based on current smoking status
    
    Loading

    Class diagram for updated prevention smoking status handling

    classDiagram
        class commn_model_Prevention {
            +int getRefusedRawValue()
        }
    
        class prevention_Prevention {
            +int getRefusedStatus(String preventionType)
            +boolean isCurrentlySmoking(String preventionType)
        }
    
        class PreventionItem {
            -int refusedStatus
            +PreventionItem(Prevention pp)
            +int getRefusedStatus()
        }
    
        commn_model_Prevention <.. PreventionItem : constructs_from
        prevention_Prevention o--> PreventionItem : uses_most_recent
    
    Loading

    File-Level Changes

    Change Details Files
    Expose raw refused status from the core Prevention model so higher layers can interpret smoking history correctly.
    • Add getRefusedRawValue() method that converts the internal refused char into its integer value.
    • Document the standard refused semantics and their repurposing for the "Smoking" prevention type.
    • Ensure this method is available to any code needing the low-level refused status.
    src/main/java/io/github/carlos_emr/carlos/commn/model/Prevention.java
    Extend the prevention domain service to derive smoking status from refused codes rather than the never flag.
    • Add getRefusedStatus(String) to look up the latest PreventionItem for a given type and return its refusedStatus.
    • Add isCurrentlySmoking(String) helper that returns true when the latest refusedStatus is 0.
    • Document why isPreventionNever("Smoking") is incorrect and direct callers to use isCurrentlySmoking instead.
    src/main/java/io/github/carlos_emr/carlos/prevention/Prevention.java
    Augment PreventionItem to carry and expose the raw refused status from the underlying record.
    • Introduce a refusedStatus field with documentation for both generic and smoking-specific semantics.
    • Initialize refusedStatus from the underlying model via getRefusedRawValue() in the constructor.
    • Provide a public getRefusedStatus() accessor for use by higher-level logic.
    src/main/java/io/github/carlos_emr/carlos/prevention/PreventionItem.java
    Update Drools prevention rules to use the new smoking status helper for LDCT and related logic.
    • Replace smoking-related checks based on isPreventionNever("Smoking") with isCurrentlySmoking("Smoking").
    • Align rule comments with the new encoding and clarify how smoking status is determined from refused codes.
    src/main/resources/oscar/oscarPrevention/prevention.drl

    Assessment against linked issues

    Issue Objective Addressed Explanation
    #628 Update LDCT eligibility rules so that smoking prevention entries only trigger LDCT suggestions when the patient is a current smoker, and do not trigger LDCT when the smoking prevention answer is 'No'.
    #628 Extend the prevention data model and related helper methods so that smoking status can be correctly derived from the underlying prevention record fields (including the repurposed refused field) and used by the rules engine.

    Tips and commands

    Interacting with Sourcery

    • Trigger a new review: Comment @sourcery-ai review on the pull request.
    • Continue discussions: Reply directly to Sourcery's review comments.
    • Generate a GitHub issue from a review comment: Ask Sourcery to create an
      issue from a review comment by replying to it. You can also reply to a
      review comment with @sourcery-ai issue to create an issue from it.
    • Generate a pull request title: Write @sourcery-ai anywhere in the pull
      request title to generate a title at any time. You can also comment
      @sourcery-ai title on the pull request to (re-)generate the title at any time.
    • Generate a pull request summary: Write @sourcery-ai summary anywhere in
      the pull request body to generate a PR summary at any time exactly where you
      want it. You can also comment @sourcery-ai summary on the pull request to
      (re-)generate the summary at any time.
    • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
      request to (re-)generate the reviewer's guide at any time.
    • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
      pull request to resolve all Sourcery comments. Useful if you've already
      addressed all the comments and don't want to see them anymore.
    • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
      request to dismiss all existing Sourcery reviews. Especially useful if you
      want to start fresh with a new review - don't forget to comment
      @sourcery-ai review to trigger a new review!

    Customizing Your Experience

    Access your dashboard to:

    • Enable or disable review features such as the Sourcery-generated pull request
      summary, the reviewer's guide, and others.
    • Change the review language.
    • Add, remove or edit custom review instructions.
    • Adjust other review settings.

    Getting Help

    @gemini-code-assist
    Copy link
    Contributor

    Summary of Changes

    Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

    This pull request refines the logic for the "LDCT 1" prevention rule by removing a specific check related to smoking prevention. This change aims to streamline the rule's evaluation process, potentially simplifying the criteria for this particular medical prevention guideline.

    Highlights

    • Prevention Rules: Removed the eval( !prev.isPreventionNever("Smoking") ) condition from the "LDCT 1" rule in prevention.drl.

    🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

    Changelog
    • src/main/resources/oscar/oscarPrevention/prevention.drl
      • Removed a smoking prevention check from the "LDCT 1" rule.
    Using Gemini Code Assist

    The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

    Invoking Gemini

    You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

    Feature Command Description
    Code Review /gemini review Performs a code review for the current pull request in its current state.
    Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
    Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
    Help /gemini help Displays a list of available commands.

    Customization

    To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

    Limitations & Feedback

    Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

    You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

    Footnotes

    1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

    @github-actions
    Copy link
    Contributor

    github-actions bot commented Mar 14, 2026

    Dependency Review

    ✅ No vulnerabilities or license issues or OpenSSF Scorecard issues found.

    Snapshot Warnings

    ⚠️: No snapshots were found for the head SHA 839c963.
    Ensure that dependencies are being submitted on PR branches and consider enabling retry-on-snapshot-warnings. See the documentation for more information and troubleshooting advice.

    Scanned Files

    None

    Copy link
    Contributor

    @gemini-code-assist gemini-code-assist bot left a comment

    Choose a reason for hiding this comment

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

    Code Review

    This pull request removes a check from the 'LDCT 1' Drools rule that verifies a patient is not a 'never-smoker'. While a small change, this has a critical impact on the clinical decision support logic. It will cause the system to incorrectly recommend lung cancer screening for non-smokers. My review includes a critical comment to restore this necessary check to ensure the rule targets the correct patient population (high-risk smokers).

    @@ -1162,7 +1162,6 @@ rule "LDCT 1"
    eval( prev.getAgeInYears() >= 55 )
    eval( prev.getAgeInYears() <= 74 )
    eval( prev.getNumberOfPreventionType("Smoking") > 0 )
    Copy link
    Contributor

    Choose a reason for hiding this comment

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

    critical

    Removing the !prev.isPreventionNever("Smoking") check makes this rule trigger for patients who are documented non-smokers. The rule is intended for high-risk smokers, so this change will generate incorrect clinical warnings.

    The comment block for the LDCT rules (lines 1150-1153) explicitly states that the logic should check that the patient is not a "never-smoker".

    // Precondition (heavy smoker detection): Smoking assessment must be on record AND
    //   not marked as "never" (non-smoker). This uses the Prevention module's "Smoking" type.
    //   A Smoking record exists (getNumberOfPreventionType("Smoking") > 0) AND
    //   the record is NOT marked as never-smoked (!isPreventionNever("Smoking")).
    

    This change makes the implementation inconsistent with the documentation and the clinical purpose of the rule. The check should be restored.

    If this change is intentional, the comment block and the rule's warning message should be updated to reflect that it no longer targets only smokers.

            eval( prev.getNumberOfPreventionType("Smoking") > 0 )
            eval( !prev.isPreventionNever("Smoking") )
    

    @phc007
    Copy link
    Collaborator Author

    phc007 commented Mar 14, 2026

    @claude
    The way the smoking preventions is coded
    Yes as in smoking in this context is stored as preventions.refused=0
    No as in not smoking is stored as preventions.refused=1
    Previous as in previously smoking is stored as preventions.refused=2

    I have removed the erroneous eval( !prev.isPreventionNever("Smoking") ) as preventions.never is 0 for all these states
    and appears to have a different use. Please evaluate and document it in the drl to limit future confusion

    Please write a public function in prevention.java to give refused status
    and write a rule to replace eval( !prev.isPreventionNever("Smoking") ) that I removed
    with one that captures preventions.refused=0 for "Smoking" as triggering the rule

    @github-actions
    Copy link
    Contributor

    github-actions bot commented Mar 14, 2026

    Claude finished @phc007's task in 4m 28s —— View job


    Implementation Complete ✓

    Root cause confirmed: The Smoking prevention type stores the answer in the refused column (not never). The never column is always 0 for all smoking states, making isPreventionNever("Smoking") always return false — so that check never filtered anyone out.

    Smoking refused column encoding:

    Value Meaning
    0 Yes — currently smoking → LDCT should fire
    1 No — non-smoker → LDCT should not fire
    2 Previous — ex-smoker → LDCT should not fire

    Changes committed (aa3a3d41):

    commn/model/Prevention.java

    • Added getRefusedRawValue() — returns raw refused char as int (0/1/2/3)

    PreventionItem.java

    • Added refusedStatus int field populated from Prevention.getRefusedRawValue()
    • Added getRefusedStatus() getter

    prevention/Prevention.java (DRL fact class)

    • Added getRefusedStatus(String preventionType) — returns raw refused value for most recent record, or -1 if no record
    • Added isCurrentlySmoking(String preventionType) — returns true when refused=0; JavaDoc explains why isPreventionNever cannot be used for Smoking checks

    prevention.drl

    • LDCT 1 rule: replaced the removed eval( !prev.isPreventionNever("Smoking") ) with eval( prev.isCurrentlySmoking("Smoking") ) which correctly fires only for current smokers
    • Header API docs: added isCurrentlySmoking, getRefusedStatus with note about why isPreventionNever is wrong for Smoking
    • LDCT comment block: fully documented the refused column encoding to prevent future confusion

    --- | Branch

    …er column
    
    The Smoking prevention type stores the smoking answer in the `refused`
    column (0=Yes/current, 1=No, 2=Previous), not the `never` column.
    The `never` column is always 0 for all three smoking states, so the
    previous `!isPreventionNever("Smoking")` check was always true and
    failed to exclude non-smokers from LDCT recommendations.
    
    Changes:
    - commn/model/Prevention.java: add getRefusedRawValue() to expose raw
      refused char as int
    - PreventionItem.java: add refusedStatus field and getRefusedStatus()
      getter, populated from Prevention.getRefusedRawValue()
    - prevention/Prevention.java: add getRefusedStatus(String) and
      isCurrentlySmoking(String) public methods for DRL use; document the
      smoking refused column encoding and why isPreventionNever is incorrect
    - prevention.drl: replace removed eval with isCurrentlySmoking("Smoking"),
      update LDCT comment block and header API docs to explain the encoding
    
    Co-authored-by: phc007 <phc007@users.noreply.github.com>
    @phc007 phc007 marked this pull request as ready for review March 14, 2026 16:38
    @phc007 phc007 requested a review from yingbull as a code owner March 14, 2026 16:38
    @penify-dev
    Copy link
    Contributor

    penify-dev bot commented Mar 14, 2026

    PR Review 🔍

    ⏱️ Estimated effort to review [1-5]

    4, because the changes involve multiple classes and methods, along with updates to DRL rules. The new logic for handling smoking history adds complexity that requires careful review to ensure correctness and adherence to the intended functionality.

    🧪 Relevant tests

    No

    ⚡ Possible issues

    Logic Change: The repurposing of the refused field for smoking history may lead to confusion if not properly documented. Ensure that all parts of the codebase that interact with this field are updated accordingly.

    Potential Misuse: The note about isPreventionNever("Smoking") being incorrect must be communicated clearly to prevent future misuse in the codebase.

    🔒 Security concerns

    No

    @penify-dev
    Copy link
    Contributor

    penify-dev bot commented Mar 14, 2026

    PR Code Suggestions ✨

    CategorySuggestion                                                                                                                                    Score
    Possible bug
    Add a null check for the prevention type to prevent potential NullPointerExceptions

    The method getRefusedStatus should handle the case where the preventionType does not exist
    in the preventionTypes map to avoid potential NullPointerExceptions.

    src/main/java/io/github/carlos_emr/carlos/prevention/Prevention.java [329-335]

    -int status = -1;
    +int status = (vec != null && !vec.isEmpty()) ? ((PreventionItem) vec.get(vec.size() - 1)).getRefusedStatus() : -1;
     
    Suggestion importance[1-10]: 9

    Why: This suggestion effectively prevents a potential NullPointerException, which is a critical issue that could lead to runtime errors.

    9
    Ensure the conversion of the refused status is valid by using a proper integer conversion method

    The calculation of the raw refused status value should ensure that the character
    subtraction is valid by explicitly converting refused to an integer.

    src/main/java/io/github/carlos_emr/carlos/commn/model/Prevention.java [252]

    -return refused - '0';
    +return refused - Character.getNumericValue('0');
     
    Suggestion importance[1-10]: 8

    Why: The suggestion addresses a potential issue with character subtraction that could lead to incorrect results, making it a significant improvement.

    8
    Best practice
    Clarify comments to emphasize the correct method for checking smoking status

    The comments regarding smoking prevention should clarify that the isCurrentlySmoking
    method should be used instead of isPreventionNever for accurate smoking status checks.

    src/main/resources/oscar/oscarPrevention/prevention.drl [55]

    -//                NOTE: isPreventionNever("Smoking") is INCORRECT for smoking checks — the
    +//                NOTE: Use isCurrentlySmoking("Smoking") instead of isPreventionNever("Smoking") for accurate smoking status checks.
     
    Suggestion importance[1-10]: 7

    Why: This suggestion enhances clarity in the comments, which is important for maintainability, but it does not address a critical issue in the code itself.

    7
    Maintainability
    Initialize the refusedStatus field with a default value that indicates an uninitialized state

    The refusedStatus field should be initialized with a meaningful default value that
    reflects the state of the prevention item.

    src/main/java/io/github/carlos_emr/carlos/prevention/PreventionItem.java [51]

    -private int refusedStatus = 0;
    +private int refusedStatus = -1; // Default to -1 to indicate an uninitialized state.
     
    Suggestion importance[1-10]: 5

    Why: While initializing with a meaningful default value is good practice, changing it from 0 to -1 is a minor improvement that does not significantly impact functionality.

    5

    Copy link
    Contributor

    @sourcery-ai sourcery-ai bot left a comment

    Choose a reason for hiding this comment

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

    Hey - I've left some high level feedback:

    • The new getRefusedRawValue() implementation assumes refused is always a digit character ('0'–'3'); if the field can be null or contain other values from the database, consider validating and handling unexpected values (e.g., returning -1 or throwing) instead of blindly doing refused - '0'.
    • In PreventionItem, you now track both boolean refused and int refusedStatus; to avoid state divergence and confusion, consider deriving the boolean from the int or consolidating to a single source of truth with clearer naming (e.g., statusCode vs. isRefused).
    Prompt for AI Agents
    Please address the comments from this code review:
    
    ## Overall Comments
    - The new `getRefusedRawValue()` implementation assumes `refused` is always a digit character ('0'–'3'); if the field can be null or contain other values from the database, consider validating and handling unexpected values (e.g., returning `-1` or throwing) instead of blindly doing `refused - '0'`.
    - In `PreventionItem`, you now track both `boolean refused` and `int refusedStatus`; to avoid state divergence and confusion, consider deriving the boolean from the int or consolidating to a single source of truth with clearer naming (e.g., `statusCode` vs. `isRefused`).

    Sourcery is free for open source - if you like our reviews please consider sharing them ✨
    Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

    Copy link
    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)
    src/main/java/io/github/carlos_emr/carlos/prevention/Prevention.java (1)

    355-357: Constrain isCurrentlySmoking to the Smoking type to prevent semantic misuse.

    As written, any prevention type with status 0 would return true. Given the method name, guarding for "Smoking" avoids accidental misuse.

    💡 Proposed refactor
         public boolean isCurrentlySmoking(String preventionType) {
    -        return getRefusedStatus(preventionType) == 0;
    +        if (!"Smoking".equals(preventionType)) {
    +            return false;
    +        }
    +        return getRefusedStatus(preventionType) == 0;
         }
    🤖 Prompt for AI Agents
    Verify each finding against the current code and only fix it if needed.
    
    In `@src/main/java/io/github/carlos_emr/carlos/prevention/Prevention.java` around
    lines 355 - 357, The method isCurrentlySmoking currently accepts any
    preventionType and returns true for any type with status 0; change it to a
    no-argument method that specifically checks the Smoking type to avoid semantic
    misuse by calling getRefusedStatus with the Smoking constant (e.g.,
    isCurrentlySmoking() { return getRefusedStatus("Smoking") == 0; }) or, if you
    have a PreventionType enum, change the signature to isCurrentlySmoking() and
    call getRefusedStatus(PreventionType.SMOKING) == 0 so only the Smoking type is
    evaluated.
    
    🤖 Prompt for all review comments with AI agents
    Verify each finding against the current code and only fix it if needed.
    
    Inline comments:
    In `@src/main/java/io/github/carlos_emr/carlos/commn/model/Prevention.java`:
    - Around line 251-253: The getRefusedRawValue method in class Prevention assumes
    the char field refused is between '0' and '3'; change it to defensively validate
    the refused field: if refused is between '0' and '3' return refused - '0',
    otherwise return a safe default (e.g. 0) or clamp to 0–3; update method
    getRefusedRawValue to perform the range check on the refused field and return
    the validated integer so the method always yields 0–3 as promised.
    
    In `@src/main/java/io/github/carlos_emr/carlos/prevention/PreventionItem.java`:
    - Around line 49-51: The field refusedStatus in class PreventionItem is
    defaulting to 0 (current smoker) which can misclassify items; change its default
    to an explicit unknown sentinel (e.g., -1 or a named constant like
    REFUSED_STATUS_UNKNOWN) and update the PreventionItem class to declare that
    constant, initialize refusedStatus to it, and ensure all constructors and any
    deserialization/hydration paths set refusedStatus explicitly (or leave as the
    sentinel) so no instance is implicitly treated as 0; also update any logic that
    checks refusedStatus (getters, isRefused/isSmoker methods, or switch handling)
    to handle the sentinel case appropriately.
    
    ---
    
    Nitpick comments:
    In `@src/main/java/io/github/carlos_emr/carlos/prevention/Prevention.java`:
    - Around line 355-357: The method isCurrentlySmoking currently accepts any
    preventionType and returns true for any type with status 0; change it to a
    no-argument method that specifically checks the Smoking type to avoid semantic
    misuse by calling getRefusedStatus with the Smoking constant (e.g.,
    isCurrentlySmoking() { return getRefusedStatus("Smoking") == 0; }) or, if you
    have a PreventionType enum, change the signature to isCurrentlySmoking() and
    call getRefusedStatus(PreventionType.SMOKING) == 0 so only the Smoking type is
    evaluated.
    

    ℹ️ Review info
    ⚙️ Run configuration

    Configuration used: defaults

    Review profile: CHILL

    Plan: Pro

    Run ID: b5f21e4c-f1e8-40fd-920a-7bea0eb3a9e3

    📥 Commits

    Reviewing files that changed from the base of the PR and between ed4da1a and aa3a3d4.

    📒 Files selected for processing (4)
    • src/main/java/io/github/carlos_emr/carlos/commn/model/Prevention.java
    • src/main/java/io/github/carlos_emr/carlos/prevention/Prevention.java
    • src/main/java/io/github/carlos_emr/carlos/prevention/PreventionItem.java
    • src/main/resources/oscar/oscarPrevention/prevention.drl

    Comment on lines +251 to +253
    public int getRefusedRawValue() {
    return refused - '0';
    }
    Copy link
    Contributor

    Choose a reason for hiding this comment

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

    ⚠️ Potential issue | 🟡 Minor

    Harden raw refused decoding against unexpected DB values.

    Line 252 assumes refused is always '0'..'3'. If data is malformed, this returns an out-of-contract value while JavaDoc promises 0–3.

    💡 Proposed fix
         public int getRefusedRawValue() {
    -        return refused - '0';
    +        if (refused >= '0' && refused <= '3') {
    +            return refused - '0';
    +        }
    +        return -1;
         }
    🤖 Prompt for AI Agents
    Verify each finding against the current code and only fix it if needed.
    
    In `@src/main/java/io/github/carlos_emr/carlos/commn/model/Prevention.java` around
    lines 251 - 253, The getRefusedRawValue method in class Prevention assumes the
    char field refused is between '0' and '3'; change it to defensively validate the
    refused field: if refused is between '0' and '3' return refused - '0', otherwise
    return a safe default (e.g. 0) or clamp to 0–3; update method getRefusedRawValue
    to perform the range check on the refused field and return the validated integer
    so the method always yields 0–3 as promised.
    

    Comment on lines +49 to +51
    // Raw refused status value (0=active, 1=refused, 2=ineligible, 3=completedExternally).
    // For the "Smoking" type: 0=Yes (current), 1=No (non-smoker), 2=Previous (ex-smoker).
    private int refusedStatus = 0;
    Copy link
    Contributor

    Choose a reason for hiding this comment

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

    ⚠️ Potential issue | 🟠 Major

    Initialize refusedStatus to an unknown sentinel, not smoker status.

    Line 51 defaulting to 0 means “current smoker.” If this object is created through constructors that don’t hydrate refusedStatus, it can misclassify status and reintroduce false LDCT triggers.

    💡 Proposed fix
    -    private int refusedStatus = 0;
    +    private int refusedStatus = -1;
    📝 Committable suggestion

    ‼️ IMPORTANT
    Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

    Suggested change
    // Raw refused status value (0=active, 1=refused, 2=ineligible, 3=completedExternally).
    // For the "Smoking" type: 0=Yes (current), 1=No (non-smoker), 2=Previous (ex-smoker).
    private int refusedStatus = 0;
    // Raw refused status value (0=active, 1=refused, 2=ineligible, 3=completedExternally).
    // For the "Smoking" type: 0=Yes (current), 1=No (non-smoker), 2=Previous (ex-smoker).
    private int refusedStatus = -1;
    🤖 Prompt for AI Agents
    Verify each finding against the current code and only fix it if needed.
    
    In `@src/main/java/io/github/carlos_emr/carlos/prevention/PreventionItem.java`
    around lines 49 - 51, The field refusedStatus in class PreventionItem is
    defaulting to 0 (current smoker) which can misclassify items; change its default
    to an explicit unknown sentinel (e.g., -1 or a named constant like
    REFUSED_STATUS_UNKNOWN) and update the PreventionItem class to declare that
    constant, initialize refusedStatus to it, and ensure all constructors and any
    deserialization/hydration paths set refusedStatus explicitly (or leave as the
    sentinel) so no instance is implicitly treated as 0; also update any logic that
    checks refusedStatus (getters, isRefused/isSmoker methods, or switch handling)
    to handle the sentinel case appropriately.
    

    @phc007 phc007 changed the title Remove smoking prevention check from rules Fix smoking prevention check from rules Mar 16, 2026
    @phc007
    Copy link
    Collaborator Author

    phc007 commented Mar 17, 2026

    @claude fix all bot issues and check if LGTM

    @github-actions
    Copy link
    Contributor

    github-actions bot commented Mar 17, 2026

    Claude finished @phc007's task in 2m 37s —— View job


    Bot Issues Fixed + LGTM Review

    • Read changed files
    • Fix: getRefusedRawValue() - harden against unexpected DB char values
    • Fix: PreventionItem.refusedStatus default from 0 to -1 sentinel
    • Fix: isCurrentlySmoking() - guard against non-Smoking prevention types
    • Review for LGTM
    • Commit and push fixes (758ffc7a)

    Changes Made

    Three issues flagged by CodeRabbit, Sourcery, and Penify have been addressed:

    1. commn/model/Prevention.javagetRefusedRawValue() range guard
    The raw char subtraction refused - '0' was unguarded. If the DB ever contains an unexpected char (e.g. from a legacy migration or manual SQL edit), the method would return a garbage int while the JavaDoc promised 0–3. Now returns -1 for values outside '0''3'.

    2. prevention/PreventionItem.java — default refusedStatus = -1
    The field was initialized to 0, which encodes "current smoker" for the Smoking prevention type. Items constructed via the no-arg or partial constructors (which don't take a Prevention record) would silently report 0 — potentially misclassifying a non-smoker as a current smoker and triggering LDCT. Changed to -1 (sentinel for "unknown/not hydrated").

    3. prevention/Prevention.javaisCurrentlySmoking() type guard
    The method accepted any preventionType string and would return true for any type where refused = 0 (the "active/completed" state). Added an early return false for any non-"Smoking" type to prevent semantic misuse in future rules.


    LGTM Assessment ✅

    The PR is LGTM with the fixes applied. Summary of what the PR does:

    Layer Change Correct?
    DRL rule LDCT 1 !isPreventionNever("Smoking")isCurrentlySmoking("Smoking") ✅ Root bug fix
    prevention.Prevention Added getRefusedStatus() + isCurrentlySmoking()
    PreventionItem Added refusedStatus field + getter, populated from getRefusedRawValue()
    commn/model/Prevention Added getRefusedRawValue()
    Documentation DRL header + LDCT comment block explain the refused column encoding

    One pre-existing concern not introduced by this PR (out of scope): PreventionItem carries both a boolean refused and int refusedStatus. The boolean predates this PR and derives from pp.isRefused() (which checks refused == '1'), while the int captures the full status. They're logically derived from the same column and won't diverge in practice, but a future cleanup could unify them. Not blocking.
    | Branch

    - Harden getRefusedRawValue() with char range guard (returns -1 for
      unexpected DB values instead of garbage int)
    - Initialize PreventionItem.refusedStatus to -1 sentinel instead of 0
      to avoid false 'current smoker' classification for items created via
      constructors that do not hydrate from a Prevention record
    - Guard isCurrentlySmoking() to return false for non-Smoking types,
      preventing semantic misuse across other prevention categories
    
    Co-authored-by: phc007 <phc007@users.noreply.github.com>
    Copy link
    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.

    Caution

    Some comments are outside the diff and can’t be posted inline due to platform limitations.

    ⚠️ Outside diff range comments (1)
    src/main/java/io/github/carlos_emr/carlos/prevention/Prevention.java (1)

    281-283: ⚠️ Potential issue | 🔴 Critical

    Fix infinite recursion in isNotPreventionNever.

    This public method calls itself recursively without a base case, causing StackOverflowError when invoked. Change line 282 to call the working isPreventionNever method instead:

    return !isPreventionNever(preventionType);

    The method has no test coverage and no internal usages detected, making this untested code path that will crash on invocation.

    🤖 Prompt for AI Agents
    Verify each finding against the current code and only fix it if needed.
    
    In `@src/main/java/io/github/carlos_emr/carlos/prevention/Prevention.java` around
    lines 281 - 283, The method isNotPreventionNever currently calls itself causing
    infinite recursion; change its implementation to return the negation of the
    existing working method isPreventionNever by replacing the self-call with
    "return !isPreventionNever(preventionType);" so isNotPreventionNever(String
    preventionType) delegates to isPreventionNever(String) instead of recursing.
    
    🤖 Prompt for all review comments with AI agents
    Verify each finding against the current code and only fix it if needed.
    
    Outside diff comments:
    In `@src/main/java/io/github/carlos_emr/carlos/prevention/Prevention.java`:
    - Around line 281-283: The method isNotPreventionNever currently calls itself
    causing infinite recursion; change its implementation to return the negation of
    the existing working method isPreventionNever by replacing the self-call with
    "return !isPreventionNever(preventionType);" so isNotPreventionNever(String
    preventionType) delegates to isPreventionNever(String) instead of recursing.
    

    ℹ️ Review info
    ⚙️ Run configuration

    Configuration used: defaults

    Review profile: CHILL

    Plan: Pro

    Run ID: 5ef084b8-829f-4763-adf8-1d0eede71f13

    📥 Commits

    Reviewing files that changed from the base of the PR and between aa3a3d4 and 758ffc7.

    📒 Files selected for processing (3)
    • src/main/java/io/github/carlos_emr/carlos/commn/model/Prevention.java
    • src/main/java/io/github/carlos_emr/carlos/prevention/Prevention.java
    • src/main/java/io/github/carlos_emr/carlos/prevention/PreventionItem.java

    @phc007
    Copy link
    Collaborator Author

    phc007 commented Mar 17, 2026

    @claude
    Verify each finding against the current code and only fix it if needed.

    Outside diff comments:
    In @src/main/java/io/github/carlos_emr/carlos/prevention/Prevention.java:

    • Around line 281-283: The method isNotPreventionNever currently calls itself
      causing infinite recursion; change its implementation to return the negation of
      the existing working method isPreventionNever by replacing the self-call with
      "return !isPreventionNever(preventionType);" so isNotPreventionNever(String
      preventionType) delegates to isPreventionNever(String) instead of recursing.

    @github-actions
    Copy link
    Contributor

    github-actions bot commented Mar 17, 2026

    Claude finished @phc007's task in 45s —— View job


    Fixed: infinite recursion in isNotPreventionNever

    • Read task
    • Verify finding in prevention/Prevention.java around line 281-283
    • Fix confirmed and applied
    • Committed and pushed

    Finding confirmed. Line 282 was calling !isNotPreventionNever(preventionType) (itself), guaranteeing a StackOverflowError on any invocation. Fixed to delegate to isPreventionNever:

      public boolean isNotPreventionNever(String preventionType) {
    -     return !isNotPreventionNever(preventionType);
    +     return !isPreventionNever(preventionType);
      }

    Committed as d39d54cd.
    | Branch

    isNotPreventionNever was calling itself instead of isPreventionNever,
    causing a StackOverflowError on any invocation.
    
    Co-authored-by: phc007 <phc007@users.noreply.github.com>
    Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

    Projects

    None yet

    Development

    Successfully merging this pull request may close these issues.

    [Bug]: Prevention smoking is handled as if its positive for LDCT

    1 participant