Skip to content

Add MonAI Project to TinyFish CookBook#227

Open
MANASMATHUR wants to merge 3 commits into
tinyfish-io:mainfrom
MANASMATHUR:manas/add-monai
Open

Add MonAI Project to TinyFish CookBook#227
MANASMATHUR wants to merge 3 commits into
tinyfish-io:mainfrom
MANASMATHUR:manas/add-monai

Conversation

@MANASMATHUR

@MANASMATHUR MANASMATHUR commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Adds MonAI — Vietnam F&B trend intelligence for menu gaps, forecasts, regional comparison, supplier discovery, and RFQ outreach.
  • Uses TinyFish Search (api.search.tinyfish.ai) for live Vietnamese web signals and TinyFish Fetch (api.fetch.tinyfish.ai) for competitor menu extraction.
  • Includes FastAPI backend with structured intelligence reports and a React dashboard (Vietnamese street-food themed landing + analysis console).
  • Live demo: https://mon-ai-alpha.vercel.app

Summary by CodeRabbit

  • New Features
    • Added a new landing + dashboard experience with live trend board and an AI Trend Intelligence Console (menu-gap analysis, trend forecasting, regional comparison, supplier discovery, and RFQ outreach).
    • Introduced new backend API endpoints for trends, menu-gap analysis, and supplier discovery, with structured report outputs.
  • Documentation
    • Expanded documentation and added environment templates (example/template) for API configuration and CORS.
  • Bug Fixes
    • Improved reliability of AI/API response handling with safer JSON parsing/normalization and more consistent internal error responses.

@coderabbitai

coderabbitai Bot commented Jul 6, 2026

Copy link
Copy Markdown

Review Change Stack

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 00015379-ad38-4a14-9c36-c803d2cbf99f

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

This PR adds the MonAI cookbook recipe: a FastAPI backend using TinyFish and optional OpenAI analysis for trend, menu-gap, supplier, and RFQ workflows, plus a React/Vite/TanStack Router frontend with landing and dashboard experiences, shared UI primitives, deployment/test config, and docs.

Changes

MonAI recipe: backend + frontend + docs

Layer / File(s) Summary
Backend services
monai/app/services/*
Adds TinyFish search/fetch helpers, OpenAI access, JSON parsing/normalization, search-based analysis builders, and report generation utilities.
API routes and backend entrypoint
monai/app/http_errors.py, monai/app/routes/*, monai/main.py
Adds the FastAPI routers for trends, analysis, and suppliers, shared internal-error handling, and the backend app entrypoint with CORS, root, and health endpoints.
Backend config and smoke test
monai/.env.example, .env.template, .gitignore, .python-version, render.yaml, requirements.txt, runtime.txt, scripts/smoke_test.py
Adds environment templates, ignore rules, runtime/dependency pins, Render deployment config, and a smoke test covering backend and frontend routes.
Frontend app shell and build config
monai/frontend/index.html, src/main.tsx, src/routeTree.gen.ts, src/routes/*, vite.config.ts, tsconfig*.json, vite-env.d.ts, .env.example, .gitignore, vercel.json, package.json
Adds the Vite/TanStack Router entrypoint, route modules, HTML shell, build/type config, frontend env/deploy settings, and package metadata.
Reusable UI primitives
monai/frontend/src/components/ui/*, hooks/use-mobile.ts
Adds the Radix-based styled component set and the mobile breakpoint hook used throughout the frontend.
Landing page sections and static data
monai/frontend/src/components/monai/*
Adds the landing page sections, static data, and visual content components used on the home page.
Analysis dashboard, report rendering, and client helpers
AnalysisPanel.tsx, IntelligenceReport.tsx, analysisExamples.ts, lib/api.ts, lib/buildClientReport.ts, lib/normalizeResult.ts, lib/utils.ts, styles.css
Adds the tabbed analysis dashboard, report rendering, example outputs, client API calls, normalization/report extraction utilities, and global styling.
Documentation
README.md, monai/README.md
Adds the cookbook table entry and the MonAI project README with usage, setup, architecture, and deployment notes.

Estimated code review effort: 4 (Complex) | ~75 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant AnalysisRoute
  participant TinyFishClient
  participant AIClient
  participant ReportBuilder
  Client->>AnalysisRoute: POST /api/analysis/menu-gap
  AnalysisRoute->>TinyFishClient: search_tinyfish(trends query)
  AnalysisRoute->>TinyFishClient: fetch_tinyfish(competitor URLs)
  AnalysisRoute->>AIClient: analyze_data(prompt) [if has_openai]
  AIClient-->>AnalysisRoute: raw JSON text
  AnalysisRoute->>ReportBuilder: build_menu_gap_report(analysis)
  ReportBuilder-->>AnalysisRoute: report
  AnalysisRoute-->>Client: menu_gap_analysis + report
Loading
sequenceDiagram
  participant User
  participant AnalysisPanel
  participant api_ts as api.ts
  participant FastAPIBackend
  participant normalizeResult
  participant buildClientReport
  User->>AnalysisPanel: submit analysis form
  AnalysisPanel->>api_ts: runMenuGapAnalysis / runTrendForecast / ...
  api_ts->>FastAPIBackend: POST or GET analysis endpoint
  FastAPIBackend-->>api_ts: analysis payload + report
  api_ts-->>AnalysisPanel: typed result
  AnalysisPanel->>normalizeResult: resolveReport(result)
  normalizeResult->>buildClientReport: extract supported report shape
  buildClientReport-->>AnalysisPanel: IntelligenceReport or null
  AnalysisPanel-->>User: render report or preview
Loading

Poem

A rabbit hops through data streams,
TinyFish twinkles, JSON gleams,
Trends and menus, side by side,
MonAI dashboards open wide. 🐰
Hop, hop — reports in taste-filled light!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: adding the MonAI project to the TinyFish CookBook.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

Contribute MonAI as a cookbook recipe: Vietnam F&B trend intelligence using TinyFish Search and Fetch with a FastAPI backend and React dashboard.
@MANASMATHUR

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 6, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 10

🧹 Nitpick comments (13)
monai/frontend/src/components/ui/radio-group.tsx (1)

7-34: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Standard shadcn pattern, works fine.

Minor note: React 19 makes ref a regular prop, so React.forwardRef is no longer required, though still supported. Not worth refactoring this generated boilerplate now.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@monai/frontend/src/components/ui/radio-group.tsx` around lines 7 - 34, No
code change is needed in RadioGroup or RadioGroupItem: the current
React.forwardRef pattern in the radio-group component is acceptable and should
be left as generated boilerplate. Keep the existing RadioGroupPrimitive.Root and
RadioGroupPrimitive.Item wrappers unchanged unless there is a separate
functional issue to address.
monai/frontend/tsconfig.json (1)

2-22: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Enable strict mode.

strict is absent from compilerOptions, so TypeScript defaults to non-strict (no strictNullChecks, noImplicitAny, etc.) across the whole frontend. Standard Vite React+TS scaffolds always set "strict": true"; without it, null/undefined and implicit-any bugs across ~40 new component files won't be caught at compile time.

🛠️ Proposed fix
   "compilerOptions": {
     "target": "es2023",
     "module": "esnext",
     "lib": ["ES2023", "DOM"],
     "types": ["vite/client"],
     "allowArbitraryExtensions": true,
     "skipLibCheck": true,
+    "strict": true,
 
     /* Bundler mode */
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@monai/frontend/tsconfig.json` around lines 2 - 22, Enable TypeScript strict
mode in the frontend tsconfig by adding the strict compiler option inside
compilerOptions. Update the monai/frontend/tsconfig.json configuration so the
existing TypeScript setup used by the Vite frontend is running with strict type
checking, and keep the change localized to the tsconfig compilerOptions block.
monai/frontend/src/components/ui/toggle-group.tsx (1)

8-11: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Item-level variant/size overrides can never take effect.

The context default ({ size: "default", variant: "default" }) is always truthy, so context.variant || variant / context.size || size always resolve to the context value — an individual ToggleGroupItem's own variant/size prop is silently ignored in every case.

🛠️ Proposed fix
-const ToggleGroupContext = React.createContext<VariantProps<typeof toggleVariants>>({
-  size: "default",
-  variant: "default",
-});
+const ToggleGroupContext = React.createContext<VariantProps<typeof toggleVariants>>({
+  size: undefined,
+  variant: undefined,
+});
       <ToggleGroupPrimitive.Item
         ref={ref}
         className={cn(
           toggleVariants({
-            variant: context.variant || variant,
-            size: context.size || size,
+            variant: variant || context.variant,
+            size: size || context.size,
           }),

Also applies to: 40-43

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@monai/frontend/src/components/ui/toggle-group.tsx` around lines 8 - 11, The
ToggleGroupContext default values are forcing every ToggleGroupItem to use the
context’s size and variant, so item-level overrides never win. Update the
ToggleGroupContext and the ToggleGroupItem prop resolution so an item can supply
its own variant/size when present, instead of always falling back to the default
context values. Focus on the ToggleGroupContext definition and the item
rendering logic that reads context.variant and context.size.
monai/frontend/src/components/monai/VietnamMap.tsx (1)

3-9: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Contradictory a11y attributes.

aria-hidden="true" combined with role="img" and a <title> is contradictory: aria-hidden removes the element (and its title) from the accessibility tree entirely, so the descriptive title/role never reach assistive tech. If purely decorative, drop role/<title>; if meaningful, drop aria-hidden.

♻️ Optional fix (decorative-only)
     <svg
       viewBox="0 0 200 360"
       className="mx-auto h-32 w-auto"
       aria-hidden="true"
-      role="img"
     >
-      <title>Map of Vietnam</title>
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@monai/frontend/src/components/monai/VietnamMap.tsx` around lines 3 - 9, The
SVG in VietnamMap has conflicting accessibility attributes: aria-hidden="true"
prevents the element from being exposed to assistive tech, so its role="img" and
<title> are ineffective. Update the svg markup in VietnamMap to choose one path:
if the map is decorative, remove role and <title>; if it should be announced,
remove aria-hidden and keep the descriptive title/role.
monai/frontend/src/components/ui/progress.tsx (1)

15-18: 🎯 Functional Correctness | 🔵 Trivial | 💤 Low value

Consider clamping value to [0, 100].

translateX(-${100 - (value || 0)}%) doesn't clamp value; a value >100 or negative produces an overflowing/inverted bar. Low priority since it's boilerplate and callers likely pass valid percentages.

♻️ Optional clamp
-      style={{ transform: `translateX(-${100 - (value || 0)}%)` }}
+      style={{ transform: `translateX(-${100 - Math.min(100, Math.max(0, value || 0))}%)` }}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@monai/frontend/src/components/ui/progress.tsx` around lines 15 - 18, Clamp
the progress value used in ProgressPrimitive.Indicator so the translateX
calculation stays within 0–100; in the progress.tsx component, update the style
logic that currently uses value || 0 to normalize out-of-range inputs before
computing the transform. Keep the change localized to the Progress component so
callers can still pass raw values while the bar rendering remains stable for
negative or >100 percentages.
monai/frontend/src/components/monai/analysisExamples.ts (1)

284-291: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Duplicate report unwrap logic.

extractReport here duplicates the same record.report check already implemented in buildClientReport (monai/frontend/src/lib/buildClientReport.ts, lines 15-17). Consider having buildClientReport delegate to extractReport (or vice versa) to keep the unwrap logic in one place.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@monai/frontend/src/components/monai/analysisExamples.ts` around lines 284 -
291, The report unwrapping logic is duplicated between extractReport and
buildClientReport, so consolidate it into a single helper and have the other
function delegate to it. Update buildClientReport to call extractReport, or move
the shared record.report extraction into buildClientReport and reuse it from
extractReport, keeping the IntelligenceReport handling in one place and
preserving the same null behavior.
monai/frontend/src/components/monai/ProblemTicker.tsx (1)

3-28: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Ticker duplicates content for screen readers.

Items are duplicated for the scroll animation but nothing marks the ticker as decorative, so assistive tech will announce every source name twice.

♿ Proposed fix
-        <div className="overflow-hidden">
-          <div className="ticker-track gap-8">
+        <div className="overflow-hidden" aria-hidden="true">
+          <div className="ticker-track gap-8">
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@monai/frontend/src/components/monai/ProblemTicker.tsx` around lines 3 - 28,
The ticker in ProblemTicker duplicates its items for animation, which causes
screen readers to announce the same content twice. Update the ProblemTicker
component so the repeated ticker track is treated as decorative by assistive
tech, using the existing section/div/span structure and the ticker-track/items
rendering as the place to apply the fix. Ensure only one accessible announcement
remains by hiding the duplicated scrolling content from screen readers while
keeping the visual animation unchanged.
monai/frontend/src/lib/api.ts (1)

12-46: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

No request timeout on API calls.

apiFetch has no timeout, so a slow/hanging backend (e.g. Render cold start) leaves the caller waiting indefinitely with no way to recover except a page reload.

⏱️ Proposed fix using AbortController
 async function apiFetch<T>(path: string, init?: RequestInit): Promise<T> {
   const base = getApiBase();
   if (base === null) {
     throw new Error("API is not configured. Set VITE_API_BASE_URL or run the dev server with backend.");
   }
 
-  const res = await fetch(`${base}${path}`, {
-    ...init,
-    headers: {
-      "Content-Type": "application/json",
-      ...init?.headers,
-    },
-  });
+  const controller = new AbortController();
+  const timeoutId = window.setTimeout(() => controller.abort(), 30000);
+  let res: Response;
+  try {
+    res = await fetch(`${base}${path}`, {
+      ...init,
+      signal: controller.signal,
+      headers: {
+        "Content-Type": "application/json",
+        ...init?.headers,
+      },
+    });
+  } catch (err) {
+    if (err instanceof DOMException && err.name === "AbortError") {
+      throw new Error(`Request to ${base}${path} timed out.`);
+    }
+    throw err;
+  } finally {
+    window.clearTimeout(timeoutId);
+  }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@monai/frontend/src/lib/api.ts` around lines 12 - 46, The apiFetch helper
currently waits forever on slow or hanging requests because it does not enforce
a timeout. Update apiFetch to create and pass an AbortController signal into
fetch, add a reasonable timeout that aborts the request, and ensure the timeout
is cleared once the request settles. Keep the change localized to apiFetch in
api.ts so callers automatically benefit, and make sure the thrown error clearly
indicates the request timed out.
monai/app/services/tinyfish_client.py (2)

39-47: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

New httpx.AsyncClient created per request.

Both search_tinyfish and fetch_tinyfish open a fresh AsyncClient (and TCP/TLS handshake) on every call instead of reusing a pooled client. Given these are core, frequently-invoked calls (menu-gap, suppliers, trends routes all call into this module), this adds avoidable latency under load.

♻️ Suggested fix: module-level shared client
-async def search_tinyfish(
+_client: httpx.AsyncClient | None = None
+
+
+def _get_client(timeout: float) -> httpx.AsyncClient:
+    global _client
+    if _client is None:
+        _client = httpx.AsyncClient(timeout=timeout)
+    return _client
+
+
+async def search_tinyfish(
     query: str,
     max_results: int = 10,
     *,
     location: str | None = None,
     language: str | None = None,
     purpose: str | None = None,
 ) -> dict:
     ...
-    async with httpx.AsyncClient(timeout=30.0) as client:
-        response = await client.get(TINYFISH_SEARCH_URL, params=params, headers=_headers())
-        response.raise_for_status()
-        data = response.json()
+    client = _get_client(30.0)
+    response = await client.get(TINYFISH_SEARCH_URL, params=params, headers=_headers())
+    response.raise_for_status()
+    data = response.json()

Note: a shared client needs an explicit close on app shutdown (e.g., FastAPI lifespan) to avoid leaking connections.

Also applies to: 54-61

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@monai/app/services/tinyfish_client.py` around lines 39 - 47,
`search_tinyfish` and `fetch_tinyfish` are creating a new `httpx.AsyncClient`
for every call, which bypasses connection pooling and adds latency. Refactor
`tinyfish_client.py` to reuse a module-level shared `AsyncClient` (or an
app-scoped client) in both functions, keeping the existing request logic intact,
and make sure the shared client is explicitly closed during app shutdown via the
FastAPI lifespan/startup-shutdown lifecycle.

22-61: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

No retry/backoff for transient TinyFish API failures.

Both functions propagate any httpx error (timeout, connection reset, 5xx) directly via raise_for_status(); a single transient blip fails the whole route (caught generically upstream as a 500) with no retry. Since these are idempotent read operations, adding limited retries with backoff (e.g., via tenacity or manual retry loop) would improve resilience against TinyFish API flakiness.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@monai/app/services/tinyfish_client.py` around lines 22 - 61, Both
search_tinyfish and fetch_tinyfish currently fail immediately on transient httpx
errors, so add limited retry-with-backoff around the TinyFish requests in these
two functions. Keep the calls idempotent but wrap the AsyncClient get/post plus
response.raise_for_status handling in a small retry loop or tenacity policy,
retrying on timeouts, connection errors, and 5xx responses before giving up. Use
the existing search_tinyfish and fetch_tinyfish entry points so the behavior is
localized and easy to locate.
monai/app/routes/suppliers.py (2)

29-71: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Duplicated "AI-or-fallback" pattern across both handlers.

discover_suppliers and generate_outreach repeat the same if has_openai(): prompt → analyze_data → parse_llm_json → fallback-on-type-mismatch shape, and SYSTEM_JSON/similar prompts also appear in other route files per the cross-file context (e.g., analysis.py). Extracting a shared helper (e.g., async def ai_or_fallback(prompt, system_prompt, expected_type, fallback_fn)) in a services module would reduce duplication across suppliers.py, analysis.py, and trends.py.

Also applies to: 74-101

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@monai/app/routes/suppliers.py` around lines 29 - 71, The AI-or-fallback flow
in discover_suppliers (and the matching handler in the same module) is
duplicated and should be centralized. Extract the repeated has_openai() →
analyze_data() → parse_llm_json() → type-check → fallback logic into a shared
helper in a services module, and have discover_suppliers call it with its
prompt, SYSTEM_JSON, and suppliers_from_search fallback so the same pattern can
be reused by generate_outreach and other routes like analysis.py/trends.py.

70-71: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Raw exception details returned to API clients.

detail=str(e) surfaces internal exception text (e.g., third-party API error bodies, or the "TINYFISH_API_KEY environment variable not set" config error) directly in the HTTP response. Prefer logging the full exception server-side and returning a generic message to the client.

🛡️ Suggested fix
     except Exception as e:
-        raise HTTPException(status_code=500, detail=str(e)) from e
+        logger.exception("discover_suppliers failed")
+        raise HTTPException(status_code=500, detail="Failed to discover suppliers") from e

Also applies to: 100-101

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@monai/app/routes/suppliers.py` around lines 70 - 71, The exception handler in
the suppliers route is returning raw internal exception text via
HTTPException(detail=str(e)), which can leak sensitive implementation details to
API clients. Update the except Exception as e block to log the full exception
server-side and raise HTTPException with a generic 500 message instead of using
the exception string; apply the same fix anywhere else in this route logic that
uses the same pattern.
monai/app/services/search_analysis.py (1)

136-147: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Remove unused menu_tokens.

menu_tokens is computed but never referenced afterward (only menu_words is used for the overlap comparison).

♻️ Proposed fix
     trend_signals = _sources_from_results(trends_data, 8)
-    menu_tokens = {item.lower().strip() for item in current_menu}
     menu_words = set()
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@monai/app/services/search_analysis.py` around lines 136 - 147, The menu
overlap logic in menu_gap_from_search computes menu_tokens but never uses it, so
remove that unused variable and keep the tokenization flow focused on menu_words
and the later overlap comparison. Make the cleanup in the menu_gap_from_search
function without changing the existing behavior of trend_signals, current_menu
processing, or competitor_menu analysis.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@monai/app/routes/analysis.py`:
- Around line 90-91: The exception handler in the analysis route is leaking
internal error text by passing str(e) directly into HTTPException(detail=...).
Update the except Exception as e block in the analysis endpoint to log the
caught exception server-side and return a generic 500 response message instead
of the raw exception string. Keep the fix localized to the analysis.py route
handler so the client only receives a safe, non-sensitive error detail.

In `@monai/app/routes/trends.py`:
- Around line 52-53: The three endpoint exception handlers in trends.py are
returning raw Exception text in HTTPException(detail=str(e)), which exposes
internal/upstream error details to clients. Update each of those route handlers
to log the caught exception server-side and return a generic 500 message instead
of str(e), matching the safer pattern used elsewhere (including analysis.py).
Keep the raise from e behavior if needed, but ensure the response detail is
sanitized in all three handlers.
- Around line 74-78: In trends.py, the parse_llm_json fallback in the main
forecast flow should not replace a failed LLM JSON response with only raw
reasoning, because that drops the structured forecast fields. Update the branch
around parse_llm_json so that when ai_response is not a dict, it falls back to
the same search-derived builder used in the non-LLM path (like
forecast_from_search or the equivalent helper pattern used by
get_emerging_trends, compare_regional_trends, and analyze_menu_gap), preserving
confidence_score, projected_mainstream_days, and key_drivers in the returned
payload.

In `@monai/app/services/ai_client.py`:
- Around line 15-22: The _get_client() helper currently creates AsyncOpenAI with
the SDK default timeout, which can leave request handlers waiting too long.
Update the AsyncOpenAI initialization in _get_client() to set an explicit
shorter timeout, or ensure analyze_data() passes a per-request timeout when
using the shared client. Keep the change localized to the client creation path
so all downstream calls inherit the safer default.

In `@monai/app/services/report_builder.py`:
- Around line 108-115: The section built in report_builder’s report assembly can
still call _paragraphs(...)[0] when top has no why_it_matters or description,
which can raise IndexError. Update the logic in the section construction to
guard the first-paragraph access in the report_builder path, using a safe
fallback or omitting the section when no paragraph text exists. Keep the fix
localized around the top_name / top section assembly so the /api/trends/emerging
flow no longer fails on empty LLM content.

In `@monai/frontend/src/components/monai/AnalysisPanel.tsx`:
- Around line 92-118: Stale analysis results can overwrite the newly selected
tab because `runAnalysis` applies `setResult(data)` even after `handleTabChange`
switches tabs. Add a request guard in `AnalysisPanel` (for example, a token/id
or abort-style check) so only the latest in-flight `runAnalysis` call can update
state, and ignore/cancel earlier responses when `activeTab` changes. If you
prefer the simpler approach, also disable the `TabsList`/`TabsTrigger`s while
`loadingTab` is set so users cannot switch tabs during an active request.

In `@monai/frontend/src/hooks/use-mobile.ts`:
- Around line 5-19: The initial mobile state is being deferred until useEffect
runs, causing a brief desktop render on mobile devices. Update useIsMobile to
compute the initial value eagerly from window.matchMedia or window.innerWidth
during the first render, then keep the existing change listener in the effect to
update it on resize. Preserve the useIsMobile hook and its return value so
Sidebar gets the correct mobile state immediately.

In `@monai/main.py`:
- Around line 37-44: The CORS setup in app.add_middleware uses wildcard
allow_headers and expose_headers while allow_credentials=True, which conflicts
with FastAPI guidance. Update the CORSMiddleware configuration to use explicit
header lists instead of ["*"], keeping the existing allow_origins allowlist and
preserving credentials support; use the app.add_middleware call as the reference
point for tightening these settings.

In `@monai/requirements.txt`:
- Line 1: Update the FastAPI dependency in requirements so it is no longer
pinned to 0.111.0 and instead uses 0.115.0 or newer, since that version range
allows a safe Starlette release. Adjust the version specifier in
requirements.txt accordingly and keep the dependency pinned to a compatible
upgraded FastAPI release.

In `@monai/scripts/smoke_test.py`:
- Around line 85-93: The smoke test can crash when a non-JSON error body is
returned because the `check()` message arguments in `get_health`/`GET
/api/trends/emerging` are evaluated eagerly and call `.get()` on the response
body even when `get()` returns a string. Update the `check(...)` call sites in
`smoke_test.py` so any access to `health` or `emerging` fields is guarded by a
type check or deferred until after confirming the body is a dict, using the
existing `get()` helper and `check()` calls to keep failures reported as FAIL
instead of raising `AttributeError`.

---

Nitpick comments:
In `@monai/app/routes/suppliers.py`:
- Around line 29-71: The AI-or-fallback flow in discover_suppliers (and the
matching handler in the same module) is duplicated and should be centralized.
Extract the repeated has_openai() → analyze_data() → parse_llm_json() →
type-check → fallback logic into a shared helper in a services module, and have
discover_suppliers call it with its prompt, SYSTEM_JSON, and
suppliers_from_search fallback so the same pattern can be reused by
generate_outreach and other routes like analysis.py/trends.py.
- Around line 70-71: The exception handler in the suppliers route is returning
raw internal exception text via HTTPException(detail=str(e)), which can leak
sensitive implementation details to API clients. Update the except Exception as
e block to log the full exception server-side and raise HTTPException with a
generic 500 message instead of using the exception string; apply the same fix
anywhere else in this route logic that uses the same pattern.

In `@monai/app/services/search_analysis.py`:
- Around line 136-147: The menu overlap logic in menu_gap_from_search computes
menu_tokens but never uses it, so remove that unused variable and keep the
tokenization flow focused on menu_words and the later overlap comparison. Make
the cleanup in the menu_gap_from_search function without changing the existing
behavior of trend_signals, current_menu processing, or competitor_menu analysis.

In `@monai/app/services/tinyfish_client.py`:
- Around line 39-47: `search_tinyfish` and `fetch_tinyfish` are creating a new
`httpx.AsyncClient` for every call, which bypasses connection pooling and adds
latency. Refactor `tinyfish_client.py` to reuse a module-level shared
`AsyncClient` (or an app-scoped client) in both functions, keeping the existing
request logic intact, and make sure the shared client is explicitly closed
during app shutdown via the FastAPI lifespan/startup-shutdown lifecycle.
- Around line 22-61: Both search_tinyfish and fetch_tinyfish currently fail
immediately on transient httpx errors, so add limited retry-with-backoff around
the TinyFish requests in these two functions. Keep the calls idempotent but wrap
the AsyncClient get/post plus response.raise_for_status handling in a small
retry loop or tenacity policy, retrying on timeouts, connection errors, and 5xx
responses before giving up. Use the existing search_tinyfish and fetch_tinyfish
entry points so the behavior is localized and easy to locate.

In `@monai/frontend/src/components/monai/analysisExamples.ts`:
- Around line 284-291: The report unwrapping logic is duplicated between
extractReport and buildClientReport, so consolidate it into a single helper and
have the other function delegate to it. Update buildClientReport to call
extractReport, or move the shared record.report extraction into
buildClientReport and reuse it from extractReport, keeping the
IntelligenceReport handling in one place and preserving the same null behavior.

In `@monai/frontend/src/components/monai/ProblemTicker.tsx`:
- Around line 3-28: The ticker in ProblemTicker duplicates its items for
animation, which causes screen readers to announce the same content twice.
Update the ProblemTicker component so the repeated ticker track is treated as
decorative by assistive tech, using the existing section/div/span structure and
the ticker-track/items rendering as the place to apply the fix. Ensure only one
accessible announcement remains by hiding the duplicated scrolling content from
screen readers while keeping the visual animation unchanged.

In `@monai/frontend/src/components/monai/VietnamMap.tsx`:
- Around line 3-9: The SVG in VietnamMap has conflicting accessibility
attributes: aria-hidden="true" prevents the element from being exposed to
assistive tech, so its role="img" and <title> are ineffective. Update the svg
markup in VietnamMap to choose one path: if the map is decorative, remove role
and <title>; if it should be announced, remove aria-hidden and keep the
descriptive title/role.

In `@monai/frontend/src/components/ui/progress.tsx`:
- Around line 15-18: Clamp the progress value used in
ProgressPrimitive.Indicator so the translateX calculation stays within 0–100; in
the progress.tsx component, update the style logic that currently uses value ||
0 to normalize out-of-range inputs before computing the transform. Keep the
change localized to the Progress component so callers can still pass raw values
while the bar rendering remains stable for negative or >100 percentages.

In `@monai/frontend/src/components/ui/radio-group.tsx`:
- Around line 7-34: No code change is needed in RadioGroup or RadioGroupItem:
the current React.forwardRef pattern in the radio-group component is acceptable
and should be left as generated boilerplate. Keep the existing
RadioGroupPrimitive.Root and RadioGroupPrimitive.Item wrappers unchanged unless
there is a separate functional issue to address.

In `@monai/frontend/src/components/ui/toggle-group.tsx`:
- Around line 8-11: The ToggleGroupContext default values are forcing every
ToggleGroupItem to use the context’s size and variant, so item-level overrides
never win. Update the ToggleGroupContext and the ToggleGroupItem prop resolution
so an item can supply its own variant/size when present, instead of always
falling back to the default context values. Focus on the ToggleGroupContext
definition and the item rendering logic that reads context.variant and
context.size.

In `@monai/frontend/src/lib/api.ts`:
- Around line 12-46: The apiFetch helper currently waits forever on slow or
hanging requests because it does not enforce a timeout. Update apiFetch to
create and pass an AbortController signal into fetch, add a reasonable timeout
that aborts the request, and ensure the timeout is cleared once the request
settles. Keep the change localized to apiFetch in api.ts so callers
automatically benefit, and make sure the thrown error clearly indicates the
request timed out.

In `@monai/frontend/tsconfig.json`:
- Around line 2-22: Enable TypeScript strict mode in the frontend tsconfig by
adding the strict compiler option inside compilerOptions. Update the
monai/frontend/tsconfig.json configuration so the existing TypeScript setup used
by the Vite frontend is running with strict type checking, and keep the change
localized to the tsconfig compilerOptions block.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

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

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: eae4ef20-48cb-437a-982a-2fa2f9ab6e76

📥 Commits

Reviewing files that changed from the base of the PR and between 6156fbd and 887c971.

⛔ Files ignored due to path filters (13)
  • monai/frontend/package-lock.json is excluded by !**/package-lock.json
  • monai/frontend/public/favicon.svg is excluded by !**/*.svg
  • monai/frontend/public/icons.svg is excluded by !**/*.svg
  • monai/frontend/src/assets/banhmi.png is excluded by !**/*.png
  • monai/frontend/src/assets/banhxeo.png is excluded by !**/*.png
  • monai/frontend/src/assets/bunbohue.png is excluded by !**/*.png
  • monai/frontend/src/assets/capthrung.png is excluded by !**/*.png
  • monai/frontend/src/assets/hero.png is excluded by !**/*.png
  • monai/frontend/src/assets/leaf.jpg is excluded by !**/*.jpg
  • monai/frontend/src/assets/leaf.svg is excluded by !**/*.svg
  • monai/frontend/src/assets/pho.png is excluded by !**/*.png
  • monai/frontend/src/assets/typescript.svg is excluded by !**/*.svg
  • monai/frontend/src/assets/vite.svg is excluded by !**/*.svg
📒 Files selected for processing (92)
  • README.md
  • monai/.env.example
  • monai/.env.template
  • monai/.gitignore
  • monai/.python-version
  • monai/README.md
  • monai/app/__init__.py
  • monai/app/routes/__init__.py
  • monai/app/routes/analysis.py
  • monai/app/routes/suppliers.py
  • monai/app/routes/trends.py
  • monai/app/services/__init__.py
  • monai/app/services/ai_client.py
  • monai/app/services/report_builder.py
  • monai/app/services/response_parser.py
  • monai/app/services/search_analysis.py
  • monai/app/services/tinyfish_client.py
  • monai/frontend/.env.example
  • monai/frontend/.gitignore
  • monai/frontend/index.html
  • monai/frontend/package.json
  • monai/frontend/src/components/monai/AnalysisPanel.tsx
  • monai/frontend/src/components/monai/CtaFooter.tsx
  • monai/frontend/src/components/monai/FeaturesSection.tsx
  • monai/frontend/src/components/monai/HeroCollage.tsx
  • monai/frontend/src/components/monai/HeroSection.tsx
  • monai/frontend/src/components/monai/IntelligenceReport.tsx
  • monai/frontend/src/components/monai/ProblemTicker.tsx
  • monai/frontend/src/components/monai/SiteFooter.tsx
  • monai/frontend/src/components/monai/SiteHeader.tsx
  • monai/frontend/src/components/monai/StarBullet.tsx
  • monai/frontend/src/components/monai/TrendsBoard.tsx
  • monai/frontend/src/components/monai/UsersSection.tsx
  • monai/frontend/src/components/monai/VietnamMap.tsx
  • monai/frontend/src/components/monai/WorkflowSection.tsx
  • monai/frontend/src/components/monai/analysisExamples.ts
  • monai/frontend/src/components/monai/data.ts
  • monai/frontend/src/components/ui/accordion.tsx
  • monai/frontend/src/components/ui/button.tsx
  • monai/frontend/src/components/ui/checkbox.tsx
  • monai/frontend/src/components/ui/collapsible.tsx
  • monai/frontend/src/components/ui/command.tsx
  • monai/frontend/src/components/ui/context-menu.tsx
  • monai/frontend/src/components/ui/dialog.tsx
  • monai/frontend/src/components/ui/drawer.tsx
  • monai/frontend/src/components/ui/dropdown-menu.tsx
  • monai/frontend/src/components/ui/form.tsx
  • monai/frontend/src/components/ui/hover-card.tsx
  • monai/frontend/src/components/ui/input-otp.tsx
  • monai/frontend/src/components/ui/input.tsx
  • monai/frontend/src/components/ui/label.tsx
  • monai/frontend/src/components/ui/menubar.tsx
  • monai/frontend/src/components/ui/navigation-menu.tsx
  • monai/frontend/src/components/ui/pagination.tsx
  • monai/frontend/src/components/ui/popover.tsx
  • monai/frontend/src/components/ui/progress.tsx
  • monai/frontend/src/components/ui/radio-group.tsx
  • monai/frontend/src/components/ui/resizable.tsx
  • monai/frontend/src/components/ui/scroll-area.tsx
  • monai/frontend/src/components/ui/select.tsx
  • monai/frontend/src/components/ui/separator.tsx
  • monai/frontend/src/components/ui/sheet.tsx
  • monai/frontend/src/components/ui/sidebar.tsx
  • monai/frontend/src/components/ui/skeleton.tsx
  • monai/frontend/src/components/ui/switch.tsx
  • monai/frontend/src/components/ui/table.tsx
  • monai/frontend/src/components/ui/tabs.tsx
  • monai/frontend/src/components/ui/textarea.tsx
  • monai/frontend/src/components/ui/toggle-group.tsx
  • monai/frontend/src/components/ui/toggle.tsx
  • monai/frontend/src/components/ui/tooltip.tsx
  • monai/frontend/src/hooks/use-mobile.ts
  • monai/frontend/src/lib/api.ts
  • monai/frontend/src/lib/buildClientReport.ts
  • monai/frontend/src/lib/normalizeResult.ts
  • monai/frontend/src/lib/utils.ts
  • monai/frontend/src/main.tsx
  • monai/frontend/src/routeTree.gen.ts
  • monai/frontend/src/routes/__root.tsx
  • monai/frontend/src/routes/dashboard.tsx
  • monai/frontend/src/routes/index.tsx
  • monai/frontend/src/styles.css
  • monai/frontend/src/vite-env.d.ts
  • monai/frontend/tsconfig.app.json
  • monai/frontend/tsconfig.json
  • monai/frontend/vercel.json
  • monai/frontend/vite.config.ts
  • monai/main.py
  • monai/render.yaml
  • monai/requirements.txt
  • monai/runtime.txt
  • monai/scripts/smoke_test.py

Comment thread monai/app/routes/analysis.py Outdated
Comment thread monai/app/routes/trends.py Outdated
Comment thread monai/app/routes/trends.py
Comment thread monai/app/services/ai_client.py
Comment thread monai/app/services/report_builder.py Outdated
Comment thread monai/frontend/src/components/monai/AnalysisPanel.tsx
Comment thread monai/frontend/src/hooks/use-mobile.ts
Comment thread monai/main.py
Comment thread monai/requirements.txt Outdated
Comment thread monai/scripts/smoke_test.py
Harden API error handling, forecast fallback, report builder guards,
stale tab request handling, CORS headers, FastAPI upgrade, and smoke test robustness.
@MANASMATHUR

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 6, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

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

⚠️ Outside diff range comments (1)
monai/app/routes/suppliers.py (1)

73-99: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Add the /outreach route decorator generate_outreach is not registered with FastAPI, so /api/suppliers/outreach 404s even though the frontend already calls it. Add @router.post("/outreach") above the handler.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@monai/app/routes/suppliers.py` around lines 73 - 99, The generate_outreach
handler is defined but never registered, so the supplier outreach endpoint is
not exposed through FastAPI. Add the missing router decorator above
generate_outreach using the existing router in suppliers.py, and keep the
handler name and path consistent so the frontend call to the outreach endpoint
resolves correctly.
🧹 Nitpick comments (1)
monai/app/routes/trends.py (1)

98-107: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Sequential region fetches double latency.

results_a and results_b are awaited one after another instead of concurrently, doubling round-trip time for the regional comparison endpoint.

♻️ Proposed fix
-        results_a = await search_tinyfish(
-            f"{category} trends in {region_a} Vietnam",
-            max_results=5,
-            purpose=f"Find {category} trends in {region_a}",
-        )
-        results_b = await search_tinyfish(
-            f"{category} trends in {region_b} Vietnam",
-            max_results=5,
-            purpose=f"Find {category} trends in {region_b}",
-        )
+        results_a, results_b = await asyncio.gather(
+            search_tinyfish(
+                f"{category} trends in {region_a} Vietnam",
+                max_results=5,
+                purpose=f"Find {category} trends in {region_a}",
+            ),
+            search_tinyfish(
+                f"{category} trends in {region_b} Vietnam",
+                max_results=5,
+                purpose=f"Find {category} trends in {region_b}",
+            ),
+        )

(requires import asyncio at the top of the file)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@monai/app/routes/trends.py` around lines 98 - 107, The regional trend fetches
in the trends route are awaited sequentially, which unnecessarily doubles
latency for the comparison endpoint. Update the logic around search_tinyfish in
the trends handler to run both lookups concurrently using asyncio.gather, and
add the needed asyncio import at the top of the file so results_a and results_b
are retrieved in parallel.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@monai/app/routes/trends.py`:
- Around line 54-55: The two handlers in trends.py are defined but never exposed
because `forecast_trend` and `compare_regional_trends` are missing router
decorators. Add the appropriate `@router.get` or `@router.post` annotations
directly above those functions so they are registered on `trends.router` and
become available through the mounted router in main.py.

In `@monai/requirements.txt`:
- Line 1: Update the FastAPI dependency in requirements so it no longer keeps
Starlette below 0.42.0, since the current fastapi==0.115.6 pin blocks the
host-header fix. Adjust the version in requirements.txt to a FastAPI release
that permits the newer Starlette line, or add an explicit Starlette override if
that dependency constraint is intentional. Locate the change by the fastapi
dependency entry in requirements.txt.

---

Outside diff comments:
In `@monai/app/routes/suppliers.py`:
- Around line 73-99: The generate_outreach handler is defined but never
registered, so the supplier outreach endpoint is not exposed through FastAPI.
Add the missing router decorator above generate_outreach using the existing
router in suppliers.py, and keep the handler name and path consistent so the
frontend call to the outreach endpoint resolves correctly.

---

Nitpick comments:
In `@monai/app/routes/trends.py`:
- Around line 98-107: The regional trend fetches in the trends route are awaited
sequentially, which unnecessarily doubles latency for the comparison endpoint.
Update the logic around search_tinyfish in the trends handler to run both
lookups concurrently using asyncio.gather, and add the needed asyncio import at
the top of the file so results_a and results_b are retrieved in parallel.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

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

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: b0773d8b-e81d-4d8c-b044-777d15b92b79

📥 Commits

Reviewing files that changed from the base of the PR and between 887c971 and 5b8e058.

📒 Files selected for processing (11)
  • monai/app/http_errors.py
  • monai/app/routes/analysis.py
  • monai/app/routes/suppliers.py
  • monai/app/routes/trends.py
  • monai/app/services/ai_client.py
  • monai/app/services/report_builder.py
  • monai/frontend/src/components/monai/AnalysisPanel.tsx
  • monai/frontend/src/hooks/use-mobile.ts
  • monai/main.py
  • monai/requirements.txt
  • monai/scripts/smoke_test.py
🚧 Files skipped from review as they are similar to previous changes (7)
  • monai/frontend/src/hooks/use-mobile.ts
  • monai/app/services/ai_client.py
  • monai/main.py
  • monai/scripts/smoke_test.py
  • monai/app/routes/analysis.py
  • monai/frontend/src/components/monai/AnalysisPanel.tsx
  • monai/app/services/report_builder.py

Comment on lines +54 to +55
async def forecast_trend(trend_name: str, location: str):
"""Predicts mainstream adoption timeline for an emerging trend."""

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
rg -n -B3 'async def (forecast_trend|compare_regional_trends)' monai/app/routes/trends.py
rg -n '`@router`\.' monai/app/routes/trends.py

Repository: tinyfish-io/tinyfish-cookbook

Length of output: 555


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- trends.py (1-130) ---'
sed -n '1,130p' monai/app/routes/trends.py

echo
echo '--- router mounts ---'
rg -n 'include_router|APIRouter|trends\.router|routes\.trends' monai/app -g '*.py'

Repository: tinyfish-io/tinyfish-cookbook

Length of output: 6862


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- main / app entrypoints ---'
rg -n 'include_router|APIRouter|trends\.router|from app\.routes import trends|import .*trends' monai -g '*.py'

echo
echo '--- end of trends.py ---'
tail -n +120 monai/app/routes/trends.py | cat -n

Repository: tinyfish-io/tinyfish-cookbook

Length of output: 2395


Add route decorators to forecast_trend and compare_regional_trends. main.py already mounts trends.router, but these two handlers have no @router.* decorator, so they aren’t registered as endpoints. monai/app/routes/trends.py:54,95

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@monai/app/routes/trends.py` around lines 54 - 55, The two handlers in
trends.py are defined but never exposed because `forecast_trend` and
`compare_regional_trends` are missing router decorators. Add the appropriate
`@router.get` or `@router.post` annotations directly above those functions so
they are registered on `trends.router` and become available through the mounted
router in main.py.

Comment thread monai/requirements.txt Outdated
@@ -0,0 +1,6 @@
fastapi==0.115.6

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

git ls-files monai/requirements.txt pyproject.toml setup.py setup.cfg requirements*.txt

Repository: tinyfish-io/tinyfish-cookbook

Length of output: 190


🏁 Script executed:

cat -n monai/requirements.txt

Repository: tinyfish-io/tinyfish-cookbook

Length of output: 319


🏁 Script executed:

python3 - <<'PY'
import pathlib, re, sys
for p in pathlib.Path('.').rglob('*'):
    if p.is_file() and p.name in {'pyproject.toml','setup.py','setup.cfg','requirements.txt'}:
        text = p.read_text(errors='ignore')
        if 'fastapi' in text.lower() or 'starlette' in text.lower():
            print(f'== {p} ==')
            for i, line in enumerate(text.splitlines(), 1):
                if re.search(r'fastapi|starlette', line, re.I):
                    print(f'{i}: {line}')
PY

Repository: tinyfish-io/tinyfish-cookbook

Length of output: 216


🌐 Web query:

FastAPI 0.115.6 starlette dependency constraint release notes host-header validation fix Starlette version

💡 Result:

FastAPI version 0.115.6 does not include a fix for host-header validation because the host-header validation issue (tracked as CVE-2026-48710) was identified later and addressed in Starlette 1.0.1 [1][2]. FastAPI 0.115.6 enforces a dependency on Starlette within the range >=0.40.0, <0.42.0 [3][4]. Because this version range is capped below 1.0.1, FastAPI 0.115.6 is inherently vulnerable to the host-header validation flaw (often referred to as the BadHost vulnerability) if the application relies on request.url.path for security-sensitive decisions [1][5][6]. Key details regarding this vulnerability and dependency state: 1. Vulnerability: CVE-2026-48710 (GHSA-86qp-5c8j-p5mr) affects all Starlette versions prior to 1.0.1 [1][2]. It allows attackers to bypass path-based authentication by injecting crafted characters (such as /,?, or #) into the HTTP Host header, which causes Starlette to reconstruct an incorrect request.url.path [1][6][7]. 2. Dependency Constraint: FastAPI 0.115.6 explicitly limits Starlette to <0.42.0 [3][4]. To mitigate this vulnerability, users must ensure they are using a version of Starlette >=1.0.1 [1][6]. 3. Recommended Mitigation: If you are using an older version of FastAPI that cannot be upgraded to a version compatible with Starlette 1.0.1+, you should deploy an RFC-compliant reverse proxy (e.g., Nginx, Caddy, or Traefik) that validates and sanitizes the Host header before forwarding requests to the ASGI server [5][6][7]. Additionally, in your application code, replace references to request.url.path with request.scope["path"], which provides the raw, un-reconstructed path [5][7].

Citations:


Upgrade FastAPI past the Starlette cap. fastapi==0.115.6 still pins Starlette below 0.42.0, so it can’t pick up the 1.0.1+ host-header fix. Bump FastAPI to a release that allows that Starlette line, or add an explicit Starlette override if that’s intentional.

🧰 Tools
🪛 OSV Scanner (2.4.0)

[HIGH] 1-1: starlette 0.41.3: BadHost: Missing Host header validation poisons request.url.path, bypassing path-based security checks

(PYSEC-2026-161)


[HIGH] 1-1: starlette 0.41.3: undefined

(PYSEC-2026-248)


[HIGH] 1-1: starlette 0.41.3: undefined

(PYSEC-2026-249)


[HIGH] 1-1: starlette 0.41.3: Starlette has possible denial-of-service vector when parsing large files in multipart forms

(GHSA-2c2j-9gv5-cj73)


[HIGH] 1-1: starlette 0.41.3: Starlette vulnerable to O(n^2) DoS via Range header merging in starlette.responses.FileResponse

(GHSA-7f5h-v6xp-fcq8)


[HIGH] 1-1: starlette 0.41.3: Starlette: request.form() limits silently ignored for application/x-www-form-urlencoded enable DoS

(GHSA-82w8-qh3p-5jfq)


[HIGH] 1-1: starlette 0.41.3: Starlette has missing Host header validation that poisons request.url.path, bypassing path-based security checks

(GHSA-86qp-5c8j-p5mr)


[HIGH] 1-1: starlette 0.41.3: Starlette: Unvalidated request path concatenated into authority poisons request.url.hostname

(GHSA-jp82-jpqv-5vv3)


[HIGH] 1-1: starlette 0.41.3: Starlette: SSRF and NTLM credential theft via UNC paths in StaticFiles on Windows

(GHSA-wqp7-x3pw-xc5r)


[HIGH] 1-1: starlette 0.41.3: Starlette: Arbitrary HTTP method dispatched to HTTPEndpoint attributes via getattr

(GHSA-x746-7m8f-x49c)


[HIGH] 1-1: tqdm 4.9.0: undefined

(PYSEC-2017-74)


[HIGH] 1-1: tqdm 4.9.0: tqdm CLI arguments injection attack

(GHSA-g7vv-2v7x-gj9p)


[HIGH] 1-1: tqdm 4.9.0: TDQM Arbitrary Code Execution

(GHSA-r7q7-xcjw-qx8q)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@monai/requirements.txt` at line 1, Update the FastAPI dependency in
requirements so it no longer keeps Starlette below 0.42.0, since the current
fastapi==0.115.6 pin blocks the host-header fix. Adjust the version in
requirements.txt to a FastAPI release that permits the newer Starlette line, or
add an explicit Starlette override if that dependency constraint is intentional.
Locate the change by the fastapi dependency entry in requirements.txt.

Source: Linters/SAST tools

Re-add @router decorators dropped during error-handler refactor,
run regional TinyFish searches in parallel, and pin starlette>=0.42.
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