Skip to content

Forecast Source Autodetection#4674

Open
hlbmtc wants to merge 4 commits intomainfrom
feat/forecast-source-autodetection
Open

Forecast Source Autodetection#4674
hlbmtc wants to merge 4 commits intomainfrom
feat/forecast-source-autodetection

Conversation

@hlbmtc
Copy link
Copy Markdown
Contributor

@hlbmtc hlbmtc commented Apr 29, 2026

Summary by CodeRabbit

  • Improvements

    • Forecast submissions now automatically determine the request source based on authentication and origin information, removing the need for manual specification and improving data consistency.
  • API Changes

    • The source field is no longer accepted in forecast submission requests; the system handles this automatically.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 29, 2026

📝 Walkthrough

Walkthrough

The changes implement automatic server-side determination of the forecast source field, replacing client-side control. The frontend no longer sends source, the serializer no longer validates it, and the backend determines it based on request type via a new is_internal_request utility function.

Changes

Cohort / File(s) Summary
Frontend API Client
front_end/src/services/api/questions/questions.server.ts
Removes hardcoded source: "ui" from the forecast submission payload.
Serializer
questions/serializers/common.py
Removes the source field from ForecastWriteSerializer, preventing client-provided source values from being validated or processed.
Backend View & Request Detection
questions/views.py, utils/requests.py
Adds is_internal_request utility function to classify requests as internal or external; updates forecast creation views to automatically inject source based on request classification (UI for internal, API for external).
Tests
tests/unit/test_questions/test_views.py, tests/unit/test_utils/test_requests.py
Updates forecast creation test expectations to include source: Forecast.SourceChoices.API; adds comprehensive test coverage for the new is_internal_request function including authentication, host normalization, and configuration fallback scenarios.

Sequence Diagram

sequenceDiagram
    participant Client as Client/Frontend
    participant View as Forecast View
    participant RequestUtil as is_internal_request
    participant Serializer as ForecastWriteSerializer
    participant DB as Database

    Client->>View: POST forecast (no source field)
    View->>RequestUtil: Check if request is internal
    RequestUtil->>RequestUtil: Inspect auth type & host
    RequestUtil-->>View: Return true/false
    
    alt Internal Request
        View->>View: Set source = "UI"
    else External Request
        View->>View: Set source = "API"
    end
    
    View->>View: Inject source into payload
    View->>Serializer: Validate forecast data
    Serializer-->>View: Valid (source auto-set)
    View->>DB: Create forecast with source
    DB-->>View: Success
    View-->>Client: Forecast created
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 No more does the client cry "UI!",
The server now sees with its eye—
Internal or API, it knows,
As through the request logic it flows.
Auto-sourced forecasts fly high! ✨

🚥 Pre-merge checks | ✅ 4
✅ 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 'Forecast Source Autodetection' directly summarizes the main change—removing hardcoded source fields and implementing automatic detection based on request type.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/forecast-source-autodetection

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
Review rate limit: 0/1 reviews remaining, refill in 60 minutes.

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

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Apr 29, 2026

🚀 Preview Environment

Your preview environment is ready!

Resource Details
🌐 Preview URL https://metaculus-pr-4674-feat-forecast-source-autodetec-preview.mtcl.cc
📦 Docker Image ghcr.io/metaculus/metaculus:feat-forecast-source-autodetection-77662e9
🗄️ PostgreSQL NeonDB branch preview/pr-4674-feat-forecast-source-autodetec
Redis Fly Redis mtc-redis-pr-4674-feat-forecast-source-autodetec

Details

  • Commit: 77662e9f84c4a4fad25d109b55ffd2b9d3e28c17
  • Branch: feat/forecast-source-autodetection
  • Fly App: metaculus-pr-4674-feat-forecast-source-autodetec

ℹ️ Preview Environment Info

Isolation:

  • PostgreSQL and Redis are fully isolated from production
  • Each PR gets its own database branch and Redis instance
  • Changes pushed to this PR will trigger a new deployment

Limitations:

  • Background workers and cron jobs are not deployed in preview environments
  • If you need to test background jobs, use Heroku staging environments

Cleanup:

  • This preview will be automatically destroyed when the PR is closed

@hlbmtc
Copy link
Copy Markdown
Contributor Author

hlbmtc commented Apr 29, 2026

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 29, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@hlbmtc hlbmtc requested review from elisescu and lsabor April 29, 2026 13:24
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
tests/unit/test_utils/test_requests.py (1)

14-45: Good test coverage, consider adding a case for uppercase APP_DOMAIN.

The tests comprehensively cover the main scenarios. However, there's no test verifying behavior when APP_DOMAIN itself contains uppercase characters (e.g., settings.APP_DOMAIN = "Metaculus.com" with host = "metaculus.com"). This would help catch the normalization issue in the implementation.

💡 Suggested additional test case
def test_uppercase_app_domain_still_matches_lowercase_host(self, settings):
    settings.APP_DOMAIN = "Metaculus.com"
    request = make_request("metaculus.com")
    assert is_internal_request(request) is False
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/unit/test_utils/test_requests.py` around lines 14 - 45, Add a unit test
to TestIsInternalRequest that verifies APP_DOMAIN normalization by setting
settings.APP_DOMAIN = "Metaculus.com" (mixed case), creating a request with host
"metaculus.com" via make_request, and asserting is_internal_request(request) is
False; name it e.g. test_uppercase_app_domain_still_matches_lowercase_host and
place it alongside the other tests to ensure the APP_DOMAIN is compared
case-insensitively.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@utils/requests.py`:
- Around line 31-33: The comparison uses a lowercased local host but compares it
directly to settings.APP_DOMAIN which may have different casing; update the
check in the function that computes host to normalize settings.APP_DOMAIN too
(e.g., compare host to settings.APP_DOMAIN.lower().strip() or a cached
normalized APP_DOMAIN) so both sides use the same casing/whitespace
normalization before returning host != settings.APP_DOMAIN; reference the local
variable host and settings.APP_DOMAIN when making this change.

---

Nitpick comments:
In `@tests/unit/test_utils/test_requests.py`:
- Around line 14-45: Add a unit test to TestIsInternalRequest that verifies
APP_DOMAIN normalization by setting settings.APP_DOMAIN = "Metaculus.com" (mixed
case), creating a request with host "metaculus.com" via make_request, and
asserting is_internal_request(request) is False; name it e.g.
test_uppercase_app_domain_still_matches_lowercase_host and place it alongside
the other tests to ensure the APP_DOMAIN is compared case-insensitively.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

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

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: fca477ca-b9dd-4b99-8739-330cc3cf1a51

📥 Commits

Reviewing files that changed from the base of the PR and between adf6b81 and 77662e9.

📒 Files selected for processing (6)
  • front_end/src/services/api/questions/questions.server.ts
  • questions/serializers/common.py
  • questions/views.py
  • tests/unit/test_questions/test_views.py
  • tests/unit/test_utils/test_requests.py
  • utils/requests.py
💤 Files with no reviewable changes (2)
  • front_end/src/services/api/questions/questions.server.ts
  • questions/serializers/common.py

Comment thread utils/requests.py
Comment on lines +31 to +33
host = (request.get_host() or "").split(":")[0].lower()

return host != settings.APP_DOMAIN
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Missing normalization for settings.APP_DOMAIN.

The host variable is lowercased, but settings.APP_DOMAIN is compared directly. If APP_DOMAIN is configured with different casing (e.g., "Metaculus.com"), the comparison will incorrectly classify the request as internal.

🛠️ Proposed fix
     host = (request.get_host() or "").split(":")[0].lower()
+    app_domain = (settings.APP_DOMAIN or "").lower()
 
-    return host != settings.APP_DOMAIN
+    return host != app_domain
📝 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
host = (request.get_host() or "").split(":")[0].lower()
return host != settings.APP_DOMAIN
host = (request.get_host() or "").split(":")[0].lower()
app_domain = (settings.APP_DOMAIN or "").lower()
return host != app_domain
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@utils/requests.py` around lines 31 - 33, The comparison uses a lowercased
local host but compares it directly to settings.APP_DOMAIN which may have
different casing; update the check in the function that computes host to
normalize settings.APP_DOMAIN too (e.g., compare host to
settings.APP_DOMAIN.lower().strip() or a cached normalized APP_DOMAIN) so both
sides use the same casing/whitespace normalization before returning host !=
settings.APP_DOMAIN; reference the local variable host and settings.APP_DOMAIN
when making this change.

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.

1 participant