codeant-ai-for-open-source[bot] commented on code in PR #40141:
URL: https://github.com/apache/superset/pull/40141#discussion_r3245318885


##########
tests/unit_tests/pandas_postprocessing/test_prophet.py:
##########
@@ -184,3 +184,112 @@ def test_prophet_incorrect_time_grain():
             periods=10,
             confidence_interval=0.8,
         )
+
+
+def test_prophet_uncertainty_lower_bound_can_be_negative_for_negative_series():
+    """
+    Regression for #21734: when the input series contains negative values,
+    the forecast's lower confidence bound (``__yhat_lower``) must be allowed
+    to go below zero. The original bug claimed Superset clipped the lower
+    bound at 0, hiding the natural shape of the uncertainty interval for
+    series like temperatures or signed deltas.
+
+    Superset's wrapper passes through Prophet's output unchanged (no
+    clipping in ``superset/utils/pandas_postprocessing/prophet.py``); this
+    test pins that contract end-to-end. If a future refactor introduces
+    a ``max(0, lower)`` clamp, this test fails immediately.
+    """
+    if find_spec("prophet") is None:
+        pytest.skip("prophet not installed")
+
+    # All-negative monthly series — any reasonable forecast must predict
+    # negative values (and therefore negative uncertainty bounds) too.
+    negative_df = pd.DataFrame(
+        {
+            DTTM_ALIAS: [datetime(2020, m, 1) for m in range(1, 13)]
+            + [datetime(2021, m, 1) for m in range(1, 13)],
+            "temperature": [
+                -5.0,
+                -7.0,
+                -3.0,
+                1.0,
+                8.0,
+                14.0,
+                17.0,
+                16.0,
+                11.0,
+                5.0,
+                -1.0,
+                -4.0,
+                -6.0,
+                -8.0,
+                -2.0,
+                2.0,
+                9.0,
+                15.0,
+                18.0,
+                17.0,
+                12.0,
+                6.0,
+                0.0,
+                -3.0,
+            ],
+        }
+    )
+
+    result = prophet(
+        df=negative_df,
+        time_grain="P1M",
+        periods=3,
+        confidence_interval=0.9,
+    )
+
+    assert "temperature__yhat_lower" in result.columns
+    # At least one forecast point's lower bound must be negative for a
+    # series whose actuals span both signs. A clamp at zero would force
+    # this assertion to fail.
+    assert (result["temperature__yhat_lower"] < 0).any(), (
+        "Forecast lower bound was non-negative everywhere despite a "
+        "series with negative actuals — suggests an unexpected clamp at "
+        "zero was reintroduced (regression of #21734)."
+    )
+
+
+def test_prophet_does_not_clamp_yhat_below_zero_for_negative_actuals():
+    """
+    Companion to the lower-bound test above: the central forecast
+    (``__yhat``) must also be allowed to go negative.
+    A bug that clamps the central forecast at zero would force the lower
+    bound non-negative as a side effect, masking the wider issue.
+    """
+    if find_spec("prophet") is None:
+        pytest.skip("prophet not installed")
+
+    negative_df = pd.DataFrame(
+        {
+            DTTM_ALIAS: [datetime(2020, m, 1) for m in range(1, 13)],
+            "balance": [
+                -100.0,
+                -110.0,
+                -95.0,
+                -120.0,
+                -130.0,
+                -125.0,
+                -140.0,
+                -135.0,
+                -150.0,
+                -145.0,
+                -160.0,
+                -155.0,
+            ],
+        }
+    )
+
+    result = prophet(
+        df=negative_df,
+        time_grain="P1M",
+        periods=2,
+        confidence_interval=0.8,
+    )
+
+    assert (result["balance__yhat"] < 0).any()

Review Comment:
   **Suggestion:** This assertion also evaluates all rows, including historical 
fitted points, rather than just predicted future points. If clamping is 
introduced only for future predictions, the test can still pass due to negative 
in-sample fitted values. Limit the check to forecast rows (the final `periods` 
rows) to correctly detect regressions. [incorrect condition logic]
   
   <details>
   <summary><b>Severity Level:</b> Major ⚠️</summary>
   
   ```mdx
   - ❌ Future-only central-forecast clamp not caught by current test.
   - ⚠️ Prophet wrapper regression for negative balances goes unguarded.
   ```
   </details>
   <details>
   <summary><b>Steps of Reproduction ✅ </b></summary>
   
   ```mdx
   1. As in suggestion 1, `_prophet_fit_and_predict()` in
   `superset/utils/pandas_postprocessing/prophet.py:75-77` builds `future =
   model.make_future_dataframe(periods=periods, freq=freq)` and joins the 
original `df` on
   `ds`, so the resulting dataframe contains both historical and forecast rows 
for each
   series.
   
   2. The `prophet()` wrapper in 
`superset/utils/pandas_postprocessing/prophet.py:80-170`
   uses that helper to produce a `target_df` where each series gets `__yhat`, 
`__yhat_lower`,
   `__yhat_upper`, and the original series column over the combined in-sample + 
future index.
   
   3. In `tests/unit_tests/pandas_postprocessing/test_prophet.py:109-145`,
   `test_prophet_does_not_clamp_yhat_below_zero_for_negative_actuals` builds an 
all-negative
   `balance` series and calls `prophet(df=negative_df, time_grain="P1M", 
periods=2,
   confidence_interval=0.8)`, so `result` again includes both the original 
balance history
   and two forecast rows.
   
   4. The final assertion at 
`tests/unit_tests/pandas_postprocessing/test_prophet.py:146`
   (`assert (result["balance__yhat"] < 0).any()`) evaluates the entire 
`"balance__yhat"`
   column; any negative in-sample fitted `yhat` value is enough for this to 
pass, so if a
   future-only clamp forced the last `periods` forecast `yhat` values to be 
non-negative, the
   regression test would still pass, meaning the condition should instead focus 
on the
   forecast horizon (e.g., the last `periods` rows).
   ```
   </details>
   
   [Fix in 
Cursor](https://app.codeant.ai/fix-in-ide?tool=cursor&prompt=This%20is%20a%20comment%20left%20during%20a%20code%20review.%0A%0A%2A%2APath%3A%2A%2A%20tests%2Funit_tests%2Fpandas_postprocessing%2Ftest_prophet.py%0A%2A%2ALine%3A%2A%2A%20295%3A295%0A%2A%2AComment%3A%2A%2A%0A%09%2AIncorrect%20Condition%20Logic%3A%20This%20assertion%20also%20evaluates%20all%20rows%2C%20including%20historical%20fitted%20points%2C%20rather%20than%20just%20predicted%20future%20points.%20If%20clamping%20is%20introduced%20only%20for%20future%20predictions%2C%20the%20test%20can%20still%20pass%20due%20to%20negative%20in-sample%20fitted%20values.%20Limit%20the%20check%20to%20forecast%20rows%20%28the%20final%20%60periods%60%20rows%29%20to%20correctly%20detect%20regressions.%0A%0AValidate%20the%20correctness%20of%20the%20flagged%20issue.%20If%20correct%2C%20How%20can%20I%20resolve%20this%3F%20If%20you%20propose%20a%20fix%2C%20implement%20it%20and%20please%20make%20it%20concise.%0AOnce%20fix%20is%20impleme
 
nted%2C%20also%20check%20other%20comments%20on%20the%20same%20PR%2C%20and%20ask%20user%20if%20the%20user%20wants%20to%20fix%20the%20rest%20of%20the%20comments%20as%20well.%20if%20said%20yes%2C%20then%20fetch%20all%20the%20comments%20validate%20the%20correctness%20and%20implement%20a%20minimal%20fix%0A)
 | [Fix in VSCode 
Claude](https://app.codeant.ai/fix-in-ide?tool=vscode-claude&prompt=This%20is%20a%20comment%20left%20during%20a%20code%20review.%0A%0A%2A%2APath%3A%2A%2A%20tests%2Funit_tests%2Fpandas_postprocessing%2Ftest_prophet.py%0A%2A%2ALine%3A%2A%2A%20295%3A295%0A%2A%2AComment%3A%2A%2A%0A%09%2AIncorrect%20Condition%20Logic%3A%20This%20assertion%20also%20evaluates%20all%20rows%2C%20including%20historical%20fitted%20points%2C%20rather%20than%20just%20predicted%20future%20points.%20If%20clamping%20is%20introduced%20only%20for%20future%20predictions%2C%20the%20test%20can%20still%20pass%20due%20to%20negative%20in-sample%20fitted%20values.%20Limit%20the%20check%20to%20forecast%20rows%
 
20%28the%20final%20%60periods%60%20rows%29%20to%20correctly%20detect%20regressions.%0A%0AValidate%20the%20correctness%20of%20the%20flagged%20issue.%20If%20correct%2C%20How%20can%20I%20resolve%20this%3F%20If%20you%20propose%20a%20fix%2C%20implement%20it%20and%20please%20make%20it%20concise.%0AOnce%20fix%20is%20implemented%2C%20also%20check%20other%20comments%20on%20the%20same%20PR%2C%20and%20ask%20user%20if%20the%20user%20wants%20to%20fix%20the%20rest%20of%20the%20comments%20as%20well.%20if%20said%20yes%2C%20then%20fetch%20all%20the%20comments%20validate%20the%20correctness%20and%20implement%20a%20minimal%20fix%0A)
   
   *(Use Cmd/Ctrl + Click for best experience)*
   <details>
   <summary><b>Prompt for AI Agent 🤖 </b></summary>
   
   ```mdx
   This is a comment left during a code review.
   
   **Path:** tests/unit_tests/pandas_postprocessing/test_prophet.py
   **Line:** 295:295
   **Comment:**
        *Incorrect Condition Logic: This assertion also evaluates all rows, 
including historical fitted points, rather than just predicted future points. 
If clamping is introduced only for future predictions, the test can still pass 
due to negative in-sample fitted values. Limit the check to forecast rows (the 
final `periods` rows) to correctly detect regressions.
   
   Validate the correctness of the flagged issue. If correct, How can I resolve 
this? If you propose a fix, implement it and please make it concise.
   Once fix is implemented, also check other comments on the same PR, and ask 
user if the user wants to fix the rest of the comments as well. if said yes, 
then fetch all the comments validate the correctness and implement a minimal fix
   ```
   </details>
   <a 
href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F40141&comment_hash=75c943f6b89ce77ab4c1ab6b7ece355c082eddf8aad185b7bf5293d31bba3228&reaction=like'>👍</a>
 | <a 
href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F40141&comment_hash=75c943f6b89ce77ab4c1ab6b7ece355c082eddf8aad185b7bf5293d31bba3228&reaction=dislike'>👎</a>



##########
tests/unit_tests/pandas_postprocessing/test_prophet.py:
##########
@@ -184,3 +184,112 @@ def test_prophet_incorrect_time_grain():
             periods=10,
             confidence_interval=0.8,
         )
+
+
+def test_prophet_uncertainty_lower_bound_can_be_negative_for_negative_series():
+    """
+    Regression for #21734: when the input series contains negative values,
+    the forecast's lower confidence bound (``__yhat_lower``) must be allowed
+    to go below zero. The original bug claimed Superset clipped the lower
+    bound at 0, hiding the natural shape of the uncertainty interval for
+    series like temperatures or signed deltas.
+
+    Superset's wrapper passes through Prophet's output unchanged (no
+    clipping in ``superset/utils/pandas_postprocessing/prophet.py``); this
+    test pins that contract end-to-end. If a future refactor introduces
+    a ``max(0, lower)`` clamp, this test fails immediately.
+    """
+    if find_spec("prophet") is None:
+        pytest.skip("prophet not installed")
+
+    # All-negative monthly series — any reasonable forecast must predict
+    # negative values (and therefore negative uncertainty bounds) too.
+    negative_df = pd.DataFrame(
+        {
+            DTTM_ALIAS: [datetime(2020, m, 1) for m in range(1, 13)]
+            + [datetime(2021, m, 1) for m in range(1, 13)],
+            "temperature": [
+                -5.0,
+                -7.0,
+                -3.0,
+                1.0,
+                8.0,
+                14.0,
+                17.0,
+                16.0,
+                11.0,
+                5.0,
+                -1.0,
+                -4.0,
+                -6.0,
+                -8.0,
+                -2.0,
+                2.0,
+                9.0,
+                15.0,
+                18.0,
+                17.0,
+                12.0,
+                6.0,
+                0.0,
+                -3.0,
+            ],
+        }
+    )
+
+    result = prophet(
+        df=negative_df,
+        time_grain="P1M",
+        periods=3,
+        confidence_interval=0.9,
+    )
+
+    assert "temperature__yhat_lower" in result.columns
+    # At least one forecast point's lower bound must be negative for a
+    # series whose actuals span both signs. A clamp at zero would force
+    # this assertion to fail.
+    assert (result["temperature__yhat_lower"] < 0).any(), (
+        "Forecast lower bound was non-negative everywhere despite a "
+        "series with negative actuals — suggests an unexpected clamp at "
+        "zero was reintroduced (regression of #21734)."
+    )
+

Review Comment:
   **Suggestion:** The assertion checks `temperature__yhat_lower` across the 
entire returned dataframe, which includes both historical fit rows and forecast 
rows. This can pass even if a future-only clamp is introduced, because 
historical fitted points may still be negative. Restrict the assertion to the 
forecast horizon (e.g., the last `periods` rows) so the test actually validates 
future forecast behavior. [incorrect condition logic]
   
   <details>
   <summary><b>Severity Level:</b> Major ⚠️</summary>
   
   ```mdx
   - ❌ Future-only lower-bound clamp escapes detection in regression test.
   - ⚠️ Negative historical bounds can satisfy current assertion condition.
   ```
   </details>
   <details>
   <summary><b>Steps of Reproduction ✅ </b></summary>
   
   ```mdx
   1. Observe `_prophet_fit_and_predict()` in
   `superset/utils/pandas_postprocessing/prophet.py:75-77`, which calls
   `model.make_future_dataframe(periods=periods, freq=freq)` and then 
`model.predict(future)`
   before joining the original `df` on `ds`, so the returned `forecast` 
includes both
   in-sample and forecast rows.
   
   2. The public `prophet()` wrapper in
   `superset/utils/pandas_postprocessing/prophet.py:80-170` iterates series, 
calls
   `_prophet_fit_and_predict()` with `periods` from the caller, and stores its 
result in
   `fit_df`, so each resulting dataframe used by callers also contains 
historical and future
   rows together.
   
   3. In `tests/unit_tests/pandas_postprocessing/test_prophet.py:40-106`,
   `test_prophet_uncertainty_lower_bound_can_be_negative_for_negative_series` 
calls
   `prophet(df=negative_df, time_grain="P1M", periods=3, 
confidence_interval=0.9)`, so the
   `result` dataframe has `len(negative_df) + 3` rows (24 in-sample + 3 
forecast) based on
   the implementation above.
   
   4. The assertion at 
`tests/unit_tests/pandas_postprocessing/test_prophet.py:98-106`
   (`assert (result["temperature__yhat_lower"] < 0).any()`) checks the entire
   `"temperature__yhat_lower"` column without restricting to the last `periods` 
rows, so any
   negative lower bound on the in-sample portion can make the test pass even if 
a future-only
   clamp forces all forecast lower bounds to be non-negative, meaning the 
test's condition is
   too broad for the intended regression guard.
   ```
   </details>
   
   [Fix in 
Cursor](https://app.codeant.ai/fix-in-ide?tool=cursor&prompt=This%20is%20a%20comment%20left%20during%20a%20code%20review.%0A%0A%2A%2APath%3A%2A%2A%20tests%2Funit_tests%2Fpandas_postprocessing%2Ftest_prophet.py%0A%2A%2ALine%3A%2A%2A%20253%3A256%0A%2A%2AComment%3A%2A%2A%0A%09%2AIncorrect%20Condition%20Logic%3A%20The%20assertion%20checks%20%60temperature__yhat_lower%60%20across%20the%20entire%20returned%20dataframe%2C%20which%20includes%20both%20historical%20fit%20rows%20and%20forecast%20rows.%20This%20can%20pass%20even%20if%20a%20future-only%20clamp%20is%20introduced%2C%20because%20historical%20fitted%20points%20may%20still%20be%20negative.%20Restrict%20the%20assertion%20to%20the%20forecast%20horizon%20%28e.g.%2C%20the%20last%20%60periods%60%20rows%29%20so%20the%20test%20actually%20validates%20future%20forecast%20behavior.%0A%0AValidate%20the%20correctness%20of%20the%20flagged%20issue.%20If%20correct%2C%20How%20can%20I%20resolve%20this%3F%20If%20you%20propose%20a%20fix%2C%20
 
implement%20it%20and%20please%20make%20it%20concise.%0AOnce%20fix%20is%20implemented%2C%20also%20check%20other%20comments%20on%20the%20same%20PR%2C%20and%20ask%20user%20if%20the%20user%20wants%20to%20fix%20the%20rest%20of%20the%20comments%20as%20well.%20if%20said%20yes%2C%20then%20fetch%20all%20the%20comments%20validate%20the%20correctness%20and%20implement%20a%20minimal%20fix%0A)
 | [Fix in VSCode 
Claude](https://app.codeant.ai/fix-in-ide?tool=vscode-claude&prompt=This%20is%20a%20comment%20left%20during%20a%20code%20review.%0A%0A%2A%2APath%3A%2A%2A%20tests%2Funit_tests%2Fpandas_postprocessing%2Ftest_prophet.py%0A%2A%2ALine%3A%2A%2A%20253%3A256%0A%2A%2AComment%3A%2A%2A%0A%09%2AIncorrect%20Condition%20Logic%3A%20The%20assertion%20checks%20%60temperature__yhat_lower%60%20across%20the%20entire%20returned%20dataframe%2C%20which%20includes%20both%20historical%20fit%20rows%20and%20forecast%20rows.%20This%20can%20pass%20even%20if%20a%20future-only%20clamp%20is%20introduced%2C%20because%20hi
 
storical%20fitted%20points%20may%20still%20be%20negative.%20Restrict%20the%20assertion%20to%20the%20forecast%20horizon%20%28e.g.%2C%20the%20last%20%60periods%60%20rows%29%20so%20the%20test%20actually%20validates%20future%20forecast%20behavior.%0A%0AValidate%20the%20correctness%20of%20the%20flagged%20issue.%20If%20correct%2C%20How%20can%20I%20resolve%20this%3F%20If%20you%20propose%20a%20fix%2C%20implement%20it%20and%20please%20make%20it%20concise.%0AOnce%20fix%20is%20implemented%2C%20also%20check%20other%20comments%20on%20the%20same%20PR%2C%20and%20ask%20user%20if%20the%20user%20wants%20to%20fix%20the%20rest%20of%20the%20comments%20as%20well.%20if%20said%20yes%2C%20then%20fetch%20all%20the%20comments%20validate%20the%20correctness%20and%20implement%20a%20minimal%20fix%0A)
   
   *(Use Cmd/Ctrl + Click for best experience)*
   <details>
   <summary><b>Prompt for AI Agent 🤖 </b></summary>
   
   ```mdx
   This is a comment left during a code review.
   
   **Path:** tests/unit_tests/pandas_postprocessing/test_prophet.py
   **Line:** 253:256
   **Comment:**
        *Incorrect Condition Logic: The assertion checks 
`temperature__yhat_lower` across the entire returned dataframe, which includes 
both historical fit rows and forecast rows. This can pass even if a future-only 
clamp is introduced, because historical fitted points may still be negative. 
Restrict the assertion to the forecast horizon (e.g., the last `periods` rows) 
so the test actually validates future forecast behavior.
   
   Validate the correctness of the flagged issue. If correct, How can I resolve 
this? If you propose a fix, implement it and please make it concise.
   Once fix is implemented, also check other comments on the same PR, and ask 
user if the user wants to fix the rest of the comments as well. if said yes, 
then fetch all the comments validate the correctness and implement a minimal fix
   ```
   </details>
   <a 
href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F40141&comment_hash=e6c0898696431ef7ef92103ef7278bdee3fc292ae0c26a2cfd824957c6ed5e6a&reaction=like'>👍</a>
 | <a 
href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F40141&comment_hash=e6c0898696431ef7ef92103ef7278bdee3fc292ae0c26a2cfd824957c6ed5e6a&reaction=dislike'>👎</a>



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to