Skip to content

Add Finsight to Tinyfish Cookbook#226

Open
MANASMATHUR wants to merge 2 commits into
tinyfish-io:mainfrom
MANASMATHUR:manas/add-finsight
Open

Add Finsight to Tinyfish Cookbook#226
MANASMATHUR wants to merge 2 commits into
tinyfish-io:mainfrom
MANASMATHUR:manas/add-finsight

Conversation

@MANASMATHUR

@MANASMATHUR MANASMATHUR commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Adds finsight — a Vietnamese market intelligence console for enterprise expansion teams (leasing, F&B competitor pricing, SME finance, regulatory briefs).
  • Uses TinyFish Search for multi-angle Vietnamese web discovery and TinyFish Fetch for markdown extraction from JS-heavy sites (GrabFood, ShopeeFood, Batdongsan, SBV sources).
  • Includes FastAPI orchestration with source preflight, answer-quality gating, and a React dashboard with live pipeline trace.
  • Live demo: https://finsight.vercel.app

Summary by CodeRabbit

  • New Features

    • Added a new FinSight experience with live analysis queries, streaming progress updates, and structured report output.
    • Added a market pulse ticker and richer landing-page sections for architecture, playbooks, coverage, and query exploration.
    • Added responsive branding, markdown rendering, and updated visual styling across the frontend.
  • Documentation

    • Expanded README and environment setup guidance, including live demo links and deployment notes.
  • Chores

    • Added project configuration, ignore rules, and deployment settings for local, Render, and Vercel setups.

@coderabbitai

coderabbitai Bot commented Jul 6, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

This PR adds a new FinSight recipe: a FastAPI backend orchestrating a TinyFish search/fetch/LLM synthesis pipeline for Vietnam market-intelligence queries, and a React/Vite frontend landing page that streams pipeline progress and renders structured reports, plus environment, deployment, and README updates.

Changes

Backend API and agent workflow

Layer / File(s) Summary
Configuration, logging, package setup
finsight/api/__init__.py, finsight/api/core/config.py, finsight/api/core/logger.py
Adds Settings with runtime detection, Vercel/deep-mode adjustments, and JSON structured logging.
API schemas
finsight/api/models/*
Defines QueryType, QueryRequest, MetricItem, StructuredReport, PipelineEvent, DataQuality, IntelligenceResult, QueryResponse.
TinyFish client and pipeline emitter
finsight/api/services/tinyfish_client.py, finsight/api/services/pipeline.py
Adds retried search/fetch HTTP calls and an async pipeline event emitter with host-normalization helper.
Query planning, classification, and source ranking
finsight/api/services/query_planner.py, query_classifier.py, source_ranker.py
Adds search angle/recovery query generation, keyword-based query-type classification, and source scoring/filtering.
LLM prompts, client, and data quality
finsight/api/services/prompts.py, llm_client.py, data_quality.py
Adds extraction/synthesis prompt builders and JSON schemas, LLMClient fact extraction/synthesis, and data-quality scoring/tiering.
Agent workflow orchestration
finsight/api/services/agent_workflows.py
Implements AgentWorkflows.process_query orchestrating search, fetch, extraction, synthesis, caching, and response assembly.
FastAPI app and routes
finsight/api/index.py
Adds CORS, exception handlers, and health/streaming/query/ticker endpoints.

Frontend landing page and API integration

Layer / File(s) Summary
Frontend scaffolding
finsight/frontend/index.html, package.json, tsconfig.json, vite.config.ts, .gitignore, src/main.tsx
Adds Vite/React project setup and entrypoint.
Theme and design tokens
finsight/frontend/src/styles.css
Adds Tailwind theme tokens, animation keyframes, and markdown typography classes.
API client and intelligence context
finsight/frontend/src/lib/api.ts, src/context/IntelligenceContext.tsx
Adds typed API client with streaming SSE parsing and the IntelligenceProvider context driving query execution.
Shared visual components
finsight/frontend/src/components/BrandMark.tsx, MarkdownContent.tsx, SkylineHero.tsx, SkylineSilhouette.tsx
Adds reusable brand, markdown, and skyline SVG components.
Analysis console and report UI
finsight/frontend/src/components/landing/AnalysisConsole.tsx, SourceQualityMeter.tsx, LivePipelinePanel.tsx, IntelligenceReport.tsx
Implements the interactive query console with live pipeline trace and structured report rendering.
Landing marketing sections
finsight/frontend/src/components/landing/*, src/App.tsx
Adds hero, problem, pipeline, playbook, map, architecture, market pulse, query matrix, and CTA sections wired into App.

Docs and deployment configuration

Layer / File(s) Summary
Docs and deploy config
README.md, finsight/README.md, .env.template, .gitignore, pyproject.toml, requirements.txt, render.yaml, vercel.json, package.json
Adds README documentation, environment templates, and Render/Vercel/Python packaging configuration.

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

Sequence Diagram(s)

sequenceDiagram
  participant Frontend
  participant FastAPIApp
  participant AgentWorkflows
  participant TinyFishClient
  participant LLMClient
  Frontend->>FastAPIApp: POST /api/v1/intelligence/query/stream
  FastAPIApp->>AgentWorkflows: process_query(query, query_type, emitter)
  AgentWorkflows->>TinyFishClient: search()/fetch() per source
  AgentWorkflows->>LLMClient: extract_facts()/synthesize()
  AgentWorkflows-->>FastAPIApp: pipeline events + QueryResponse
  FastAPIApp-->>Frontend: SSE pipeline events then result
Loading

Poem

A rabbit hops through code so bright,
Search, fetch, synthesize — day and night 🌙
TinyFish swims the Vietnam sea,
Charts and drawers, gold and free.
Hop hop hooray, FinSight's alive tonight! 🐇✨

🚥 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 FinSight 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.

@MANASMATHUR MANASMATHUR force-pushed the manas/add-finsight branch from dc3b2e3 to 066bec9 Compare July 6, 2026 02:21
@MANASMATHUR MANASMATHUR force-pushed the manas/add-finsight branch from 066bec9 to 0f12dbc Compare July 6, 2026 02:24
@MANASMATHUR

Copy link
Copy Markdown
Contributor Author

@coderabbitai

@coderabbitai

coderabbitai Bot commented Jul 6, 2026

Copy link
Copy Markdown

@MANASMATHUR I see you've tagged me, but I don't see a specific request in your comment. Could you let me know what you'd like me to help with? For example, I can:

  • Review the PR changes (@coderabbitai review)
  • Generate a summary
  • Answer questions about the codebase or this PR
  • Help address specific review comments

Let me know how I can assist!

✅ 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.

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

🧹 Nitpick comments (17)
finsight/api/models/schemas.py (2)

15-18: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Stale query_type description.

The description lists only 'sme_loan', 'regulatory', or 'competitor' but QueryType has six members (REAL_ESTATE, MOBILITY, GENERAL are also defined at lines 6-12). Update the docstring to reflect all valid values, since this text will surface in generated OpenAPI docs.

Suggested fix
-    query_type: QueryType = Field(..., description="Type of query: 'sme_loan', 'regulatory', or 'competitor'")
+    query_type: QueryType = Field(..., description="Type of query: one of sme_loan, regulatory, competitor, real_estate, mobility, general")
🤖 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 `@finsight/api/models/schemas.py` around lines 15 - 18, The QueryRequest
query_type field description is stale and only mentions three values even though
QueryType includes six members. Update the Field description in QueryRequest to
list every valid QueryType option, keeping the OpenAPI docs aligned with the
enum definition and its actual members in QueryType.

39-49: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

stage/status/tier are freeform strings despite documented closed value sets.

PipelineEvent.stage/status (lines 40, 43) and DataQuality.tier (line 49) only constrain allowed values via description text ("search | fetch | preflight | synthesize", etc.), not via Literal/Enum. This allows typos to pass validation silently and lets frontend/backend drift on accepted values.

Suggested enforcement
+from typing import Literal
+
 class PipelineEvent(BaseModel):
-    stage: str = Field(..., description="search | fetch | preflight | synthesize")
+    stage: Literal["search", "fetch", "preflight", "synthesize"] = Field(...)
     message: str
     url: Optional[str] = None
-    status: Optional[str] = Field(None, description="ok | failed | warn | skipped | pending")
+    status: Optional[Literal["ok", "failed", "warn", "skipped", "pending"]] = None
     meta: Optional[Dict[str, Any]] = None
🤖 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 `@finsight/api/models/schemas.py` around lines 39 - 49, `PipelineEvent.stage`,
`PipelineEvent.status`, and `DataQuality.tier` are still unconstrained freeform
strings; update the `PipelineEvent` and `DataQuality` model fields to use
explicit `Literal` values or shared `Enum` types so validation rejects
unsupported values. Make the allowed sets match the documented options in the
field descriptions, and ensure any serialization/deserialization paths that
consume these models continue to use the same symbols (`PipelineEvent`,
`DataQuality`) with the new closed value definitions.
finsight/api/core/logger.py (2)

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

datetime.utcnow() is deprecated.

Prefer datetime.now(timezone.utc) for timezone-aware timestamps going forward.

🤖 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 `@finsight/api/core/logger.py` at line 9, The timestamp generation in the
logger setup uses datetime.utcnow(), which should be replaced with a
timezone-aware UTC timestamp. Update the timestamp assignment in the logger code
to use datetime.now(timezone.utc) and keep the existing ISO formatting so the
output remains consistent while avoiding the deprecated API.

7-17: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

No fallback if json.dumps fails.

format() has no try/except around json.dumps; if a future caller passes non-JSON-serializable extra data or the log message itself raises during formatting, the entire logging call will raise and could mask the original error being logged.

Suggested defensive fallback
     def format(self, record: logging.LogRecord) -> str:
         log_data = {
             "timestamp": datetime.utcnow().isoformat(),
             "level": record.levelname,
             "message": record.getMessage(),
             "module": record.module,
             "funcName": record.funcName,
         }
         if record.exc_info:
             log_data["exception"] = self.formatException(record.exc_info)
-        return json.dumps(log_data)
+        try:
+            return json.dumps(log_data, default=str)
+        except (TypeError, ValueError):
+            return json.dumps({"level": record.levelname, "message": str(record.getMessage())})
🤖 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 `@finsight/api/core/logger.py` around lines 7 - 17, The formatter in the
`format` method should not let `json.dumps` failures break logging; wrap the
JSON serialization in a defensive fallback so `logging` still emits a usable
message if non-serializable data or message formatting errors occur. Update the
`format` implementation in the logger class to catch serialization issues around
the `log_data` payload, and return a safe plain-text fallback that still
includes key fields like level, message, and exception context.
finsight/api/core/config.py (1)

93-109: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

analysis_mode is silently overwritten when not on Vercel.

model_post_init always forces self.analysis_mode = "deep" (line 108) when is_vercel is false, regardless of what the ANALYSIS_MODE env var was set to. This makes the field non-configurable for local/Render testing of the "fast" path, even though analysis_mode is exposed as a public settable Field with a description implying it's user-controlled.

🤖 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 `@finsight/api/core/config.py` around lines 93 - 109, `model_post_init` is
overriding the user-configured `analysis_mode` on non-Vercel runs. Update
`Config.model_post_init` so it only forces the Vercel-specific fast settings
inside the `is_vercel` branch, and preserves the existing `analysis_mode` value
otherwise instead of unconditionally setting it to "deep". Keep
`_apply_deep_pipeline` behavior aligned with the effective mode so the
`analysis_mode` field remains configurable via env or constructor inputs.
finsight/frontend/src/styles.css (1)

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

Drop the unused shadcn palette and dark variant.
bg-primary/bg-card/bg-popover/dark: aren’t used outside this file, while the app’s actual styling only uses the fs-* tokens. Keeping the generic :root/.dark block and @custom-variant dark adds dead weight unless dark mode is coming soon.

🤖 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 `@finsight/frontend/src/styles.css` at line 4, Remove the unused shadcn theme
setup from the styles file by deleting the generic `:root` and `.dark` palette
variables and the `@custom-variant dark` declaration, since the app only relies
on the `fs-*` tokens. Update the stylesheet so only the active `fs-*` custom
properties remain, and make sure no `bg-primary`, `bg-card`, `bg-popover`, or
`dark:` references depend on the removed `dark` variant. Refer to the existing
stylesheet theme block and `@custom-variant dark` definition when cleaning this
up.
finsight/api/services/pipeline.py (1)

27-33: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Duplicate host-parsing logic vs. source_ranker._host.

host_from_url and source_ranker.py's _host (Lines 51-55) both parse hostnames and strip "www.", but with slightly different fallback semantics (returning the original url here vs. empty string there). Consider extracting a single shared helper to avoid divergence.

🤖 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 `@finsight/api/services/pipeline.py` around lines 27 - 33, The host parsing
logic in host_from_url duplicates source_ranker._host and has different fallback
behavior, so consolidate them into a single shared helper and make both call it.
Update host_from_url to reuse the same hostname extraction and www-stripping
semantics as _host, including a consistent fallback path, and reference both
host_from_url and _host when wiring the shared implementation.
finsight/api/services/tinyfish_client.py (1)

35-41: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Consider reusing a persistent httpx.AsyncClient instead of creating one per call.

TinyFishClient is instantiated once as a module-level singleton (Line 92), but each search/fetch call opens and tears down its own AsyncClient, losing connection pooling/keep-alive across the many concurrent calls the workflow makes.

♻️ Proposed refactor
 class TinyFishClient:
     def __init__(self):
         self.api_key = settings.tinyfish_api_key
         self.headers = {...}
         self.timeout = settings.timeout_seconds
+        self._client = httpx.AsyncClient()
+
+    async def aclose(self):
+        await self._client.aclose()

Then replace async with httpx.AsyncClient() as client: ... with client = self._client in both methods, and close it during app shutdown.

Also applies to: 66-72

🤖 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 `@finsight/api/services/tinyfish_client.py` around lines 35 - 41, The
TinyFishClient search/fetch methods are creating a new httpx.AsyncClient on
every call, which prevents connection reuse across the singleton instance.
Update TinyFishClient to hold a persistent client (for example as a _client
field on the class), change the search and fetch methods to use self._client
instead of creating a new client in each call, and make sure the shared client
is closed during application shutdown.
finsight/api/services/source_ranker.py (1)

107-108: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Duplicate low-signal-host check — reuse is_low_signal_host.

♻️ Proposed simplification
-    if any(host == domain or host.endswith(f".{domain}") for domain in LOW_SIGNAL_DOMAINS):
-        score -= 4.0
+    if is_low_signal_host(url):
+        score -= 4.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 `@finsight/api/services/source_ranker.py` around lines 107 - 108, The
low-signal host logic is duplicated in the source ranking path; replace the
inline domain check in the scoring flow with the existing is_low_signal_host
helper used by source_ranker so the same matching rules are reused consistently.
Update the relevant logic around the score adjustment in source_ranker to call
is_low_signal_host(host) and keep only the score decrement there, avoiding the
repeated LOW_SIGNAL_DOMAINS comparison.
finsight/frontend/src/components/landing/VietnamMap.tsx (1)

59-98: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

City markers are mouse-only; no keyboard equivalent.

onMouseEnter is the sole interaction to change activeCity (Lines 64, 84); there's no onFocus/tabIndex/keyboard handler, so keyboard users can't explore per-city stats.

♿ Proposed fix (sketch)
             <g
               key={city.name}
               transform={`translate(${city.x}, ${city.y})`}
-              className="cursor-pointer"
+              className="cursor-pointer"
+              tabIndex={0}
+              role="button"
+              aria-label={`Show ${city.name} stats`}
               onMouseEnter={() => setActiveCity(city)}
+              onFocus={() => setActiveCity(city)}
             >
🤖 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 `@finsight/frontend/src/components/landing/VietnamMap.tsx` around lines 59 -
98, The city markers in VietnamMap are only interactive via mouse hover, so
keyboard users cannot change activeCity or access the city details. Update the
marker groups in VietnamMap to be focusable and keyboard-operable by adding
tabIndex, an appropriate accessibility role/label, and an onFocus handler that
mirrors onMouseEnter. Also add keyboard handling in the same city marker
rendering blocks so users can navigate and activate cities without a mouse.
finsight/frontend/src/components/landing/SkyHero.tsx (1)

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

Duplicate nav config with ClosingCTA.tsx.

NAV_ITEMS here is identical to FOOTER_NAV in ClosingCTA.tsx. Consider extracting a shared constant to avoid drift between header/footer nav.

🤖 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 `@finsight/frontend/src/components/landing/SkyHero.tsx` around lines 5 - 9,
NAV_ITEMS in SkyHero.tsx is duplicated from FOOTER_NAV in ClosingCTA.tsx, so
extract the shared nav configuration into a single reusable constant and import
it in both places to prevent drift. Update the SkyHero component and the
ClosingCTA component to reference the shared symbol instead of maintaining
separate arrays, keeping the existing labels and ids unchanged.
finsight/frontend/src/lib/api.ts (2)

109-132: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Duplicate error-parsing logic.

The error-body parsing/message-extraction block is duplicated verbatim between runIntelligenceQuery and runIntelligenceQueryStream. Extract a shared helper to avoid drift when error handling needs to change.

♻️ Proposed refactor
+async function extractErrorMessage(res: Response): Promise<string> {
+  const body = (await res.json().catch(() => null)) as
+    | ApiError
+    | { detail?: string }
+    | null;
+  return (
+    (body && 'error' in body && body.error?.message) ||
+    (body && 'detail' in body && typeof body.detail === 'string' ? body.detail : null) ||
+    `Request failed with status ${res.status}`
+  );
+}

Then replace both if (!res.ok) { ... } blocks with:

-  if (!res.ok) {
-    const body = (await res.json().catch(() => null)) as
-      | ApiError
-      | { detail?: string }
-      | null;
-    const message =
-      (body && 'error' in body && body.error?.message) ||
-      (body && 'detail' in body && typeof body.detail === 'string' ? body.detail : null) ||
-      `Request failed with status ${res.status}`;
-    throw new Error(message);
-  }
+  if (!res.ok) {
+    throw new Error(await extractErrorMessage(res));
+  }

Also applies to: 173-183

🤖 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 `@finsight/frontend/src/lib/api.ts` around lines 109 - 132, The error parsing
and message extraction logic in runIntelligenceQuery is duplicated with
runIntelligenceQueryStream, so extract it into a shared helper and reuse it in
both response handlers. Move the non-OK response body parsing and fallback
message construction into a single utility near these functions, then have both
runIntelligenceQuery and runIntelligenceQueryStream call that helper before
throwing the error.

159-212: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

No cancellation support for in-flight streaming request; reader not released on early exit.

runIntelligenceQueryStream accepts no AbortSignal, and IntelligenceContext.runQuery (finsight/frontend/src/context/IntelligenceContext.tsx:103-190) doesn't cancel a prior in-flight stream before starting a new one — it only guards state updates via runIdRef, letting the old fetch/backend workflow keep running to completion. The AnalysisConsole UI currently disables the run button while isRunning, which limits (but doesn't eliminate) reachability — e.g. the provider could be reused elsewhere, or unmounted mid-stream without cleanup. Also, on early return (line 206, result received) or throw (line 204, error received) the reader is never released/cancelled, leaving the stream's lock in an indeterminate state.

Each abandoned SSE stream corresponds to a full backend search/fetch/LLM synthesis run (per finsight/api/index.py:126-178), so uncancelled requests keep consuming backend compute/LLM quota even after the UI moves on.

🔧 Proposed fix — accept an AbortSignal and clean up the reader
 export async function runIntelligenceQueryStream(
   query: string,
   queryType: QueryType,
-  onPipelineEvent: (event: PipelineEvent) => void
+  onPipelineEvent: (event: PipelineEvent) => void,
+  signal?: AbortSignal
 ): Promise<QueryResponse> {
   const res = await fetch(apiUrl('/api/v1/intelligence/query/stream'), {
     method: 'POST',
     headers: {
       'Content-Type': 'application/json',
       Accept: 'text/event-stream',
     },
     body: JSON.stringify({ query, query_type: queryType }),
+    signal,
   });
   ...
   const reader = res.body.getReader();
   const decoder = new TextDecoder();
   let buffer = '';
-
-  while (true) {
-    const { done, value } = await reader.read();
-    if (done) break;
-    ...
-  }
-
-  throw new Error('Stream ended before a result was returned.');
+  try {
+    while (true) {
+      const { done, value } = await reader.read();
+      if (done) break;
+      ...
+    }
+    throw new Error('Stream ended before a result was returned.');
+  } finally {
+    reader.cancel().catch(() => {});
+  }
 }

Then in IntelligenceContext.runQuery, keep an AbortController ref, call .abort() on any previous run before starting a new one, and pass .signal through.

🤖 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 `@finsight/frontend/src/lib/api.ts` around lines 159 - 212, Add cancellation
and stream cleanup to runIntelligenceQueryStream: it currently has no
AbortSignal support and never releases the reader on early return or error.
Update runIntelligenceQueryStream to accept an AbortSignal and ensure the reader
is cancelled or released in a cleanup path when a result is returned, an error
is thrown, or the request is aborted. Then update IntelligenceContext.runQuery
to keep an AbortController ref, abort any previous in-flight query before
starting a new one, and pass the signal into runIntelligenceQueryStream.
finsight/frontend/src/components/BrandMark.tsx (2)

5-6: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Inline size style overrides className-based sizing.

style={{ width: '2.25rem', height: '2.25rem' }} unconditionally forces the SVG to a fixed size regardless of className. Today it happens to match the h-9 w-9 default, but any caller passing a different size class (e.g. h-6 w-6) would be silently ignored since inline styles win over Tailwind utility classes.

🔧 Proposed fix
     <svg
       viewBox="0 0 40 40"
       className={className ?? 'h-9 w-9'}
-      style={{ width: '2.25rem', height: '2.25rem' }}
       aria-hidden="true"
     >
🤖 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 `@finsight/frontend/src/components/BrandMark.tsx` around lines 5 - 6, The
BrandMark SVG size is being forced by the inline style in BrandMark, which
overrides any size passed through className. Remove the fixed width/height style
and let the className-driven Tailwind sizing control the element, keeping the
default size only through the className fallback in BrandMark.

9-13: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Scope the gradient id per BrandMark instance. BrandMark is a reusable SVG component with a static fs-grad id, so rendering it more than once in the same document can make the url(#fs-grad) fills collide. useId() avoids that.

🤖 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 `@finsight/frontend/src/components/BrandMark.tsx` around lines 9 - 13, The
gradient identifier in BrandMark is static, which can cause collisions when
multiple BrandMark instances render in the same document. Update the BrandMark
component to generate a per-instance id using useId, then use that generated id
consistently in the <linearGradient> definition and every url(#...) reference
that targets it. Keep the change localized to the BrandMark component so each
SVG instance has its own scoped gradient.
finsight/frontend/src/context/IntelligenceContext.tsx (1)

132-147: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Expose the metric count in synthesize event meta instead of parsing the message. The live update still depends on /extracted (\d+) verified metric/, while the backend synthesize event only emits message and status today. Add a structured count to meta and read that here so copy changes don’t stall the progress indicator.

🤖 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 `@finsight/frontend/src/context/IntelligenceContext.tsx` around lines 132 -
147, The verified metric progress update in IntelligenceContext currently parses
event.message text for the count, which is brittle. Update the synthesize event
flow so the backend includes the verified metric count in event.meta, then
change the setSourcePreflight handling in IntelligenceContext to read that
structured meta field instead of matching /extracted (\d+) verified metric/.
Keep the existing synthesize stage check and verifiedMetrics state update, but
source the value from the typed event payload rather than the message string.
finsight/api/services/agent_workflows.py (1)

74-97: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Brittle, hardcoded keyword heuristic for answer-status classification.

The English/Vietnamese substring matching only covers a handful of specific topics (delivery fee, foreign capital, storefront density, valuation); any other query silently falls through to "direct" regardless of actual coverage. Since this heuristic directly drives the data-quality gating shown in the pipeline UI, misclassification is invisible to users.

Consider moving these keyword sets to configuration/constants and/or logging when the default fallback path is hit, so blind spots are observable rather than silent.

🤖 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 `@finsight/api/services/agent_workflows.py` around lines 74 - 97, The
_infer_answer_status method uses brittle hardcoded substring checks and silently
falls back to "direct" for any query that does not match the small keyword list.
Refactor the keyword sets in _infer_answer_status into named constants or
configuration so they are easier to maintain, and add an observable signal when
the default fallback path is taken. Keep the existing query_text and gap_text
checks, but make the fallback explicit so blind spots in answer-status
classification are visible rather than silently treated as "direct".
🤖 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 `@finsight/.env.template`:
- Around line 2-3: Swap the order of the environment variable entries in the
.env.template so it matches dotenv-linter expectations; specifically, reorder
the two keys in the template so the OPENAI_API_KEY and TINYFISH_API_KEY entries
are in the lint-compliant sequence without changing their values.

In `@finsight/api/index.py`:
- Around line 184-191: The legacy get_intelligence_query endpoint is exposing
the expensive intelligence workflow over a public GET route with no safeguards.
Remove this GET route or gate it behind authentication and rate limiting before
it reaches _run_intelligence_query, and keep the logic behind the existing
/api/v1/intelligence/query handler protected so crawlers or prefetchers cannot
trigger the paid TinyFish/LLM flow.
- Around line 40-46: The global_exception_handler currently logs
request.url.path directly, which can contain attacker-controlled control
characters and lead to log forging. Update the logging in
global_exception_handler to sanitize the path before passing it to
logger.exception by stripping or escaping newline and other control characters,
while keeping the rest of the error handling and JSONResponse unchanged.
- Around line 193-210: The get_market_ticker endpoint is bypassing the same API
configuration guard and workflow timeout handling used by query_intelligence and
query_intelligence_stream. Update get_market_ticker to call
_ensure_api_configured() before any agent_workflows.process_query work, and wrap
each workflow call with asyncio.wait_for using settings.workflow_timeout_seconds
so failures return a clean 503-style error instead of hanging. Also run the HCMC
and Hanoi queries concurrently rather than awaiting them one after the other,
and keep the logic localized around get_market_ticker and
agent_workflows.process_query so it stays consistent with the other endpoints.
- Around line 137-178: The SSE workflow in run_workflow and event_generator is
leaking internal exception text and keeps running after the client disconnects.
Replace the raw str(exc) error payload with the same sanitized generic message
used elsewhere, and add proper client-disconnect handling by using a real
FastAPI Request object alongside the existing request parameter so
event_generator can stop or cancel the task when the connection is gone. Ensure
the cleanup path in event_generator no longer waits for the full task to finish
unconditionally.

In `@finsight/api/services/agent_workflows.py`:
- Around line 26-28: The in-memory `_async_cache` in `agent_workflows.py` is
unbounded and can retain stale entries forever, while concurrent identical
queries still run duplicate pipelines. Replace the raw dict cache with a bounded
TTL-based cache in the same area where `_async_cache`, `CACHE_TTL`, and
`CACHE_VERSION` are defined, and add an in-flight request map to coalesce
duplicate work in the query handling path around the cache lookup/insert logic
in `query_answered` and the downstream async flow. Ensure expired entries are
evicted automatically and that concurrent requests for the same cache key await
the same `asyncio.Future` instead of launching separate fetch/LLM calls.

In `@finsight/api/services/query_planner.py`:
- Around line 25-38: The MOBILITY keyword fallback in query_planner.py is taking
precedence over already classified non-MOBILITY queries, causing COMPETITOR
queries that mention grabfood/shopeefood to be misrouted. Update the branching
around the query_type check in the query_planner logic so the keyword-based
MOBILITY path only applies when the query is unclassified or GENERAL, and
preserve the existing COMPETITOR branch for query_type == QueryType.COMPETITOR
so competitor-specific angles are still appended.

In `@finsight/api/services/tinyfish_client.py`:
- Around line 20-25: The retry policy in TinyFishClient is too broad because it
retries all HTTPStatusError cases, including 4xx auth and bad-request failures.
Update the retry condition used by the retry decorator on TinyFishClient methods
like search and fetch so it only retries transient failures: keep
httpx.RequestError and restrict HTTPStatusError to 5xx responses. Adjust the
retry predicate/helper accordingly so non-transient client errors surface
immediately instead of burning the full backoff.

In `@finsight/frontend/src/components/landing/AnalysisConsole.tsx`:
- Around line 48-52: The Enter-key handler in AnalysisConsole.tsx bypasses the
same minimum query-length guard used by the Run Analysis button. Update
handleKeyDown to mirror the button’s disabled logic by checking the trimmed
query length before calling handleRun, in addition to !isRunning, so short
queries cannot trigger runQuery. Keep the validation consistent between
handleKeyDown and the button state derived from query.trim().length.

In `@finsight/frontend/src/main.ts`:
- Around line 1-60: The Vite starter entrypoint is unused scaffolding, so remove
the orphaned logic from main.ts by deleting the starter DOM template and the
setupCounter import/use, and align the app bootstrap with the actual entry path
used by index.html and the App component tree. Also remove the now-unused
counter.ts and style.css scaffold files, and make sure the real entrypoint
remains the one that renders App.tsx with src/styles.css.

In `@finsight/package.json`:
- Line 10: The install:all script in package.json only installs Python deps and
frontend packages, so the root npm devDependency concurrently is never installed
on a fresh checkout. Update install:all to also run npm install at the repo root
before or alongside the frontend install, and use the existing install script
entry point if available so npm run dev can resolve concurrently from the root
package.json.
- Line 8: The dev:api script in package.json is hardcoded to
.\venv\Scripts\uvicorn, but install:all does not guarantee that launcher exists.
Update the dev:api command to use the same launcher pattern as the docs,
preferably python -m uvicorn, or alternatively make install:all create the
expected venv consistently; use the dev:api script entry to locate the change.

---

Nitpick comments:
In `@finsight/api/core/config.py`:
- Around line 93-109: `model_post_init` is overriding the user-configured
`analysis_mode` on non-Vercel runs. Update `Config.model_post_init` so it only
forces the Vercel-specific fast settings inside the `is_vercel` branch, and
preserves the existing `analysis_mode` value otherwise instead of
unconditionally setting it to "deep". Keep `_apply_deep_pipeline` behavior
aligned with the effective mode so the `analysis_mode` field remains
configurable via env or constructor inputs.

In `@finsight/api/core/logger.py`:
- Line 9: The timestamp generation in the logger setup uses datetime.utcnow(),
which should be replaced with a timezone-aware UTC timestamp. Update the
timestamp assignment in the logger code to use datetime.now(timezone.utc) and
keep the existing ISO formatting so the output remains consistent while avoiding
the deprecated API.
- Around line 7-17: The formatter in the `format` method should not let
`json.dumps` failures break logging; wrap the JSON serialization in a defensive
fallback so `logging` still emits a usable message if non-serializable data or
message formatting errors occur. Update the `format` implementation in the
logger class to catch serialization issues around the `log_data` payload, and
return a safe plain-text fallback that still includes key fields like level,
message, and exception context.

In `@finsight/api/models/schemas.py`:
- Around line 15-18: The QueryRequest query_type field description is stale and
only mentions three values even though QueryType includes six members. Update
the Field description in QueryRequest to list every valid QueryType option,
keeping the OpenAPI docs aligned with the enum definition and its actual members
in QueryType.
- Around line 39-49: `PipelineEvent.stage`, `PipelineEvent.status`, and
`DataQuality.tier` are still unconstrained freeform strings; update the
`PipelineEvent` and `DataQuality` model fields to use explicit `Literal` values
or shared `Enum` types so validation rejects unsupported values. Make the
allowed sets match the documented options in the field descriptions, and ensure
any serialization/deserialization paths that consume these models continue to
use the same symbols (`PipelineEvent`, `DataQuality`) with the new closed value
definitions.

In `@finsight/api/services/agent_workflows.py`:
- Around line 74-97: The _infer_answer_status method uses brittle hardcoded
substring checks and silently falls back to "direct" for any query that does not
match the small keyword list. Refactor the keyword sets in _infer_answer_status
into named constants or configuration so they are easier to maintain, and add an
observable signal when the default fallback path is taken. Keep the existing
query_text and gap_text checks, but make the fallback explicit so blind spots in
answer-status classification are visible rather than silently treated as
"direct".

In `@finsight/api/services/pipeline.py`:
- Around line 27-33: The host parsing logic in host_from_url duplicates
source_ranker._host and has different fallback behavior, so consolidate them
into a single shared helper and make both call it. Update host_from_url to reuse
the same hostname extraction and www-stripping semantics as _host, including a
consistent fallback path, and reference both host_from_url and _host when wiring
the shared implementation.

In `@finsight/api/services/source_ranker.py`:
- Around line 107-108: The low-signal host logic is duplicated in the source
ranking path; replace the inline domain check in the scoring flow with the
existing is_low_signal_host helper used by source_ranker so the same matching
rules are reused consistently. Update the relevant logic around the score
adjustment in source_ranker to call is_low_signal_host(host) and keep only the
score decrement there, avoiding the repeated LOW_SIGNAL_DOMAINS comparison.

In `@finsight/api/services/tinyfish_client.py`:
- Around line 35-41: The TinyFishClient search/fetch methods are creating a new
httpx.AsyncClient on every call, which prevents connection reuse across the
singleton instance. Update TinyFishClient to hold a persistent client (for
example as a _client field on the class), change the search and fetch methods to
use self._client instead of creating a new client in each call, and make sure
the shared client is closed during application shutdown.

In `@finsight/frontend/src/components/BrandMark.tsx`:
- Around line 5-6: The BrandMark SVG size is being forced by the inline style in
BrandMark, which overrides any size passed through className. Remove the fixed
width/height style and let the className-driven Tailwind sizing control the
element, keeping the default size only through the className fallback in
BrandMark.
- Around line 9-13: The gradient identifier in BrandMark is static, which can
cause collisions when multiple BrandMark instances render in the same document.
Update the BrandMark component to generate a per-instance id using useId, then
use that generated id consistently in the <linearGradient> definition and every
url(#...) reference that targets it. Keep the change localized to the BrandMark
component so each SVG instance has its own scoped gradient.

In `@finsight/frontend/src/components/landing/SkyHero.tsx`:
- Around line 5-9: NAV_ITEMS in SkyHero.tsx is duplicated from FOOTER_NAV in
ClosingCTA.tsx, so extract the shared nav configuration into a single reusable
constant and import it in both places to prevent drift. Update the SkyHero
component and the ClosingCTA component to reference the shared symbol instead of
maintaining separate arrays, keeping the existing labels and ids unchanged.

In `@finsight/frontend/src/components/landing/VietnamMap.tsx`:
- Around line 59-98: The city markers in VietnamMap are only interactive via
mouse hover, so keyboard users cannot change activeCity or access the city
details. Update the marker groups in VietnamMap to be focusable and
keyboard-operable by adding tabIndex, an appropriate accessibility role/label,
and an onFocus handler that mirrors onMouseEnter. Also add keyboard handling in
the same city marker rendering blocks so users can navigate and activate cities
without a mouse.

In `@finsight/frontend/src/context/IntelligenceContext.tsx`:
- Around line 132-147: The verified metric progress update in
IntelligenceContext currently parses event.message text for the count, which is
brittle. Update the synthesize event flow so the backend includes the verified
metric count in event.meta, then change the setSourcePreflight handling in
IntelligenceContext to read that structured meta field instead of matching
/extracted (\d+) verified metric/. Keep the existing synthesize stage check and
verifiedMetrics state update, but source the value from the typed event payload
rather than the message string.

In `@finsight/frontend/src/lib/api.ts`:
- Around line 109-132: The error parsing and message extraction logic in
runIntelligenceQuery is duplicated with runIntelligenceQueryStream, so extract
it into a shared helper and reuse it in both response handlers. Move the non-OK
response body parsing and fallback message construction into a single utility
near these functions, then have both runIntelligenceQuery and
runIntelligenceQueryStream call that helper before throwing the error.
- Around line 159-212: Add cancellation and stream cleanup to
runIntelligenceQueryStream: it currently has no AbortSignal support and never
releases the reader on early return or error. Update runIntelligenceQueryStream
to accept an AbortSignal and ensure the reader is cancelled or released in a
cleanup path when a result is returned, an error is thrown, or the request is
aborted. Then update IntelligenceContext.runQuery to keep an AbortController
ref, abort any previous in-flight query before starting a new one, and pass the
signal into runIntelligenceQueryStream.

In `@finsight/frontend/src/styles.css`:
- Line 4: Remove the unused shadcn theme setup from the styles file by deleting
the generic `:root` and `.dark` palette variables and the `@custom-variant dark`
declaration, since the app only relies on the `fs-*` tokens. Update the
stylesheet so only the active `fs-*` custom properties remain, and make sure no
`bg-primary`, `bg-card`, `bg-popover`, or `dark:` references depend on the
removed `dark` variant. Refer to the existing stylesheet theme block and
`@custom-variant dark` definition when cleaning this up.
🪄 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: cf8bd6c9-a810-4179-bce8-971178a36395

📥 Commits

Reviewing files that changed from the base of the PR and between 6156fbd and 0f12dbc.

⛔ Files ignored due to path filters (5)
  • finsight/frontend/package-lock.json is excluded by !**/package-lock.json
  • finsight/frontend/src/assets/hero.png is excluded by !**/*.png
  • finsight/frontend/src/assets/typescript.svg is excluded by !**/*.svg
  • finsight/frontend/src/assets/vite.svg is excluded by !**/*.svg
  • finsight/package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (55)
  • README.md
  • finsight/.env.template
  • finsight/.gitignore
  • finsight/README.md
  • finsight/api/__init__.py
  • finsight/api/core/config.py
  • finsight/api/core/logger.py
  • finsight/api/index.py
  • finsight/api/models/__init__.py
  • finsight/api/models/schemas.py
  • finsight/api/services/__init__.py
  • finsight/api/services/agent_workflows.py
  • finsight/api/services/data_quality.py
  • finsight/api/services/llm_client.py
  • finsight/api/services/pipeline.py
  • finsight/api/services/prompts.py
  • finsight/api/services/query_classifier.py
  • finsight/api/services/query_planner.py
  • finsight/api/services/source_ranker.py
  • finsight/api/services/tinyfish_client.py
  • finsight/frontend/.gitignore
  • finsight/frontend/index.html
  • finsight/frontend/package.json
  • finsight/frontend/src/App.tsx
  • finsight/frontend/src/components/BrandMark.tsx
  • finsight/frontend/src/components/MarkdownContent.tsx
  • finsight/frontend/src/components/SkylineHero.tsx
  • finsight/frontend/src/components/SkylineSilhouette.tsx
  • finsight/frontend/src/components/landing/AnalysisConsole.tsx
  • finsight/frontend/src/components/landing/Architecture.tsx
  • finsight/frontend/src/components/landing/ClosingCTA.tsx
  • finsight/frontend/src/components/landing/IntelligenceReport.tsx
  • finsight/frontend/src/components/landing/LivePipelinePanel.tsx
  • finsight/frontend/src/components/landing/MarketPulse.tsx
  • finsight/frontend/src/components/landing/OpsProblem.tsx
  • finsight/frontend/src/components/landing/QueryMatrix.tsx
  • finsight/frontend/src/components/landing/SkyHero.tsx
  • finsight/frontend/src/components/landing/SourceQualityMeter.tsx
  • finsight/frontend/src/components/landing/TinyFishPipeline.tsx
  • finsight/frontend/src/components/landing/VerticalPlaybooks.tsx
  • finsight/frontend/src/components/landing/VietnamMap.tsx
  • finsight/frontend/src/context/IntelligenceContext.tsx
  • finsight/frontend/src/counter.ts
  • finsight/frontend/src/lib/api.ts
  • finsight/frontend/src/main.ts
  • finsight/frontend/src/main.tsx
  • finsight/frontend/src/style.css
  • finsight/frontend/src/styles.css
  • finsight/frontend/tsconfig.json
  • finsight/frontend/vite.config.ts
  • finsight/package.json
  • finsight/pyproject.toml
  • finsight/render.yaml
  • finsight/requirements.txt
  • finsight/vercel.json

Comment thread finsight/.env.template Outdated
Comment thread finsight/api/index.py
Comment thread finsight/api/index.py
Comment thread finsight/api/index.py Outdated
Comment thread finsight/api/index.py
Comment thread finsight/api/services/tinyfish_client.py
Comment thread finsight/frontend/src/components/landing/AnalysisConsole.tsx
Comment thread finsight/frontend/src/main.ts Outdated
Comment thread finsight/package.json Outdated
Comment thread finsight/package.json Outdated
@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.

🧹 Nitpick comments (1)
finsight/api/index.py (1)

218-231: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

On a non-timeout failure, asyncio.gather leaves the sibling query running.

The gating/timeout/concurrency rework correctly resolves the prior finding. One residual gap: with the default return_exceptions=False, if one _ticker_query raises a non-TimeoutError exception (e.g., an httpx/openai error propagated from process_query), gather re-raises immediately but does not cancel the still-pending sibling coroutine, so the other paid TinyFish/LLM workflow keeps running to completion in the background. Cancel outstanding tasks on failure to avoid the wasted spend.

♻️ Suggested cleanup
     try:
-        response_hcmc, response_hanoi = await asyncio.gather(
-            _ticker_query(
-                "Current average commercial real estate rent per square meter in Ho Chi Minh City District 1"
-            ),
-            _ticker_query(
-                "Current average commercial real estate rent per square meter in Hanoi Hoan Kiem District"
-            ),
-        )
+        tasks = [
+            asyncio.ensure_future(
+                _ticker_query(
+                    "Current average commercial real estate rent per square meter in Ho Chi Minh City District 1"
+                )
+            ),
+            asyncio.ensure_future(
+                _ticker_query(
+                    "Current average commercial real estate rent per square meter in Hanoi Hoan Kiem District"
+                )
+            ),
+        ]
+        try:
+            response_hcmc, response_hanoi = await asyncio.gather(*tasks)
+        except BaseException:
+            for t in tasks:
+                if not t.done():
+                    t.cancel()
+            await asyncio.gather(*tasks, return_exceptions=True)
+            raise
     except asyncio.TimeoutError as exc:
         raise HTTPException(
             status_code=504,
             detail="Market ticker data timed out.",
         ) from exc
🤖 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 `@finsight/api/index.py` around lines 218 - 231, The concurrent ticker lookup
in the `asyncio.gather` block can leave one `_ticker_query` running after the
other fails with a non-timeout exception. Update the `finsight.api.index` flow
around the `response_hcmc, response_hanoi = await asyncio.gather(...)` call so
that any pending sibling task is explicitly canceled when an exception escapes,
and make sure the `process_query`/TinyFish work is not left running in the
background. Keep the existing timeout handling, but add cleanup for
non-`TimeoutError` failures in the same `gather`-based code path.
🤖 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.

Nitpick comments:
In `@finsight/api/index.py`:
- Around line 218-231: The concurrent ticker lookup in the `asyncio.gather`
block can leave one `_ticker_query` running after the other fails with a
non-timeout exception. Update the `finsight.api.index` flow around the
`response_hcmc, response_hanoi = await asyncio.gather(...)` call so that any
pending sibling task is explicitly canceled when an exception escapes, and make
sure the `process_query`/TinyFish work is not left running in the background.
Keep the existing timeout handling, but add cleanup for non-`TimeoutError`
failures in the same `gather`-based code path.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 0e63b6d4-0206-4af2-aadc-e2a0e0336a26

📥 Commits

Reviewing files that changed from the base of the PR and between 0f12dbc and 9b4ac8f.

📒 Files selected for processing (5)
  • finsight/.env.template
  • finsight/api/index.py
  • finsight/api/services/tinyfish_client.py
  • finsight/frontend/src/components/landing/AnalysisConsole.tsx
  • finsight/package.json
✅ Files skipped from review due to trivial changes (1)
  • finsight/.env.template
🚧 Files skipped from review as they are similar to previous changes (3)
  • finsight/package.json
  • finsight/frontend/src/components/landing/AnalysisConsole.tsx
  • finsight/api/services/tinyfish_client.py

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