Skip to content

Refactor: Improve modularity and readability of backend services.#69

Merged
MasumRab merged 2 commits intomainfrom
refactor/modularize-backend
Jun 16, 2025
Merged

Refactor: Improve modularity and readability of backend services.#69
MasumRab merged 2 commits intomainfrom
refactor/modularize-backend

Conversation

@MasumRab
Copy link
Copy Markdown
Owner

@MasumRab MasumRab commented Jun 16, 2025

This commit introduces several changes to enhance the structure and maintainability of the Node.js and Python backend services.

Key changes include:

  • Refactored server/routes.ts: Split into multiple, more focused route files (e.g., emailRoutes.ts, aiRoutes.ts).
  • Refactored server/python_backend/main.py: Divided FastAPI endpoints into separate route modules (e.g., email_routes.py, ai_routes.py).
  • Refactored server/python_nlp/nlp_engine.py: Renamed analyzer classes to model classes (e.g., SentimentModel) and ensured they encapsulate model loading and prediction logic.
  • Consolidated Logic: Centralized the AI-driven category name to category ID matching logic within the Python AdvancedAIEngine. This removes redundancy previously present in the Node.js AI routes.
  • Organized Python Models: Consolidated Pydantic models used in the Python backend into server/python_backend/models.py.
  • Added Unit Tests: Introduced new unit tests for the refactored components in both Node.js (Jest) and Python (FastAPI TestClient, pytest) to ensure correctness and prevent regressions.

These changes aim to make the codebase easier to understand, navigate, and maintain, while also reducing redundancy and improving the separation of concerns.

Summary by Sourcery

Refactor backend codebases to improve modularity, readability, and maintainability by splitting monolithic route files into focused modules, centralizing AI logic, consolidating shared models, and renaming components for clarity.

Enhancements:

  • Split Node.js routes into dedicated modules (emailRoutes, aiRoutes, gmailRoutes, categoryRoutes, activityRoutes, dashboardRoutes, performanceRoutes) and register them via Express routers
  • Modularize Python FastAPI service by moving endpoints from main.py into route-specific files and centralize Pydantic models in a single models.py
  • Rename Python NLP analyzer classes to model classes (SentimentModel, IntentModel, TopicModel, UrgencyModel) for clearer semantics
  • Centralize AI category‐matching logic in Python AdvancedAIEngine and expose category_id through PythonNLPBridge, removing redundancy in JavaScript routes
  • Update ai-engine.ts and python-bridge to include categoryId in AI analysis schemas

Tests:

  • Add Jest unit tests for Node.js route handlers across email, AI, Gmail, category, activity, and performance modules
  • Add FastAPI TestClient and pytest tests for Python route modules (email, category, dashboard, Gmail sync, filter, action extraction)
  • Add pytest tests for Python AdvancedAIEngine and NLP model components (sentiment, intent, topic, urgency) to validate analysis and fallback logic

Summary by CodeRabbit

  • New Features

    • Introduced modular API endpoints for emails, categories, activities, AI analysis, Gmail integration, filters, dashboard stats, and performance monitoring across both Node.js (Express) and Python (FastAPI) backends.
    • Added endpoints for extracting action items from email content and for intelligent filter management.
    • Enhanced AI analysis to include category identification and action extraction.
  • Bug Fixes

    • Improved error handling and validation for all API endpoints, including detailed error messages and status codes.
  • Refactor

    • Migrated all route and model definitions into dedicated modules for improved maintainability and scalability.
    • Updated class and method names for consistency across AI and NLP components.
  • Tests

    • Added comprehensive unit and integration tests for all new and refactored API routes and AI/NLP components.
  • Documentation

    • Improved inline documentation and logging for API endpoints and error handling.

End-users will benefit from a more robust, modular, and feature-rich API with enhanced AI capabilities and improved reliability.

This commit introduces several changes to enhance the structure and maintainability of the Node.js and Python backend services.

Key changes include:

- Refactored `server/routes.ts`: Split into multiple, more focused route files (e.g., `emailRoutes.ts`, `aiRoutes.ts`).
- Refactored `server/python_backend/main.py`: Divided FastAPI endpoints into separate route modules (e.g., `email_routes.py`, `ai_routes.py`).
- Refactored `server/python_nlp/nlp_engine.py`: Renamed analyzer classes to model classes (e.g., `SentimentModel`) and ensured they encapsulate model loading and prediction logic.
- Consolidated Logic: Centralized the AI-driven category name to category ID matching logic within the Python `AdvancedAIEngine`. This removes redundancy previously present in the Node.js AI routes.
- Organized Python Models: Consolidated Pydantic models used in the Python backend into `server/python_backend/models.py`.
- Added Unit Tests: Introduced new unit tests for the refactored components in both Node.js (Jest) and Python (FastAPI TestClient, pytest) to ensure correctness and prevent regressions.

These changes aim to make the codebase easier to understand, navigate, and maintain, while also reducing redundancy and improving the separation of concerns.
@sourcery-ai
Copy link
Copy Markdown
Contributor

sourcery-ai bot commented Jun 16, 2025

Reviewer's Guide

This PR refactors both the Node.js and Python backends for better modularity and readability by splitting monolithic route files into focused modules, centralizing Pydantic schemas, consolidating AI/NLP logic into dedicated engine classes with category-matching support, updating the Python–TypeScript bridge to carry category IDs, and expanding unit test coverage across both stacks.

Entity relationship diagram for AI category matching and email assignment

erDiagram
    EMAILS {
        int id
        string subject
        string content
        int category_id
        int confidence
    }
    CATEGORIES {
        int id
        string name
    }
    EMAILS }o--|| CATEGORIES : "assigned to"
    AI_ANALYSIS {
        int email_id
        int category_id
        float confidence
    }
    AI_ANALYSIS ||--|| CATEGORIES : "matches"
    AI_ANALYSIS ||--|| EMAILS : "analyzes"
Loading

Class diagram for refactored Python NLP model components

classDiagram
    class NLPEngine {
        - SentimentModel sentiment_analyzer
        - TopicModel topic_analyzer
        - IntentModel intent_analyzer
        - UrgencyModel urgency_analyzer
        + analyze_email(subject, content)
    }
    class SentimentModel {
        - model
        - has_nltk
        + analyze(text)
    }
    class TopicModel {
        - model
        + analyze(text)
    }
    class IntentModel {
        - model
        + analyze(text)
    }
    class UrgencyModel {
        - model
        + analyze(text)
    }
    NLPEngine --> SentimentModel
    NLPEngine --> TopicModel
    NLPEngine --> IntentModel
    NLPEngine --> UrgencyModel
Loading

Class diagram for AdvancedAIEngine and category matching

classDiagram
    class AdvancedAIEngine {
        - NLPEngine nlp_engine
        + initialize()
        + analyze_email(subject, content, db)
        - _match_category_id(ai_categories, db)
    }
    class DatabaseManager {
        + get_all_categories()
    }
    AdvancedAIEngine --> NLPEngine
    AdvancedAIEngine --> DatabaseManager
Loading

Class diagram for refactored GmailAIService with dependency injection

classDiagram
    class GmailAIService {
        - GmailDataCollector collector
        - GmailMetadataExtractor metadata_extractor
        - DataCollectionStrategy data_strategy
        - AdvancedAIEngine advanced_ai_engine
        - DatabaseManager db_manager
        + sync_gmail_emails(...)
        + execute_smart_retrieval(...)
        + get_performance_metrics(...)
    }
    GmailAIService --> AdvancedAIEngine
    GmailAIService --> DatabaseManager
Loading

Class diagram for centralized Pydantic models in models.py

classDiagram
    class ActionExtractionRequest {
        subject: Optional[str]
        content: str
    }
    class ActionItem {
        action_phrase: str
        verb: Optional[str]
        object: Optional[str]
        raw_due_date_text: Optional[str]
        context: str
    }
    class GmailSyncRequest {
        maxEmails: int
        queryFilter: str
        includeAIAnalysis: bool
        strategies: List[str]
        timeBudgetMinutes: int
    }
    class SmartRetrievalRequest {
        strategies: List[str]
        maxApiCalls: int
        timeBudgetMinutes: int
    }
Loading

Class diagram for TypeScript AIAnalysis and PythonNLPBridge update

classDiagram
    class AIAnalysis {
        topic: string
        sentiment: string
        intent: string
        urgency: string
        confidence: number
        categories: string[]
        keywords: string[]
        reasoning: string
        suggestedLabels: string[]
        riskFlags: string[]
        categoryId: number
    }
    class PythonNLPBridge {
        + analyzeEmail(subject, content): Promise<AIAnalysis>
    }
    PythonNLPBridge --> AIAnalysis
Loading

File-Level Changes

Change Details Files
Extracted and mounted Express routers for feature-specific API modules
  • Split server/routes.ts into dedicated routers (emailRoutes, categoryRoutes, aiRoutes, gmailRoutes, performanceRoutes, activityRoutes, dashboardRoutes)
  • Replaced inline app.get/app.post handlers with app.use to mount each router
server/routes.ts
server/emailRoutes.ts
server/categoryRoutes.ts
server/aiRoutes.ts
server/gmailRoutes.ts
server/performanceRoutes.ts
server/activityRoutes.ts
server/dashboardRoutes.ts
Decoupled Python FastAPI endpoints into separate route modules
  • Moved endpoints out of main.py into email_routes, category_routes, gmail_routes, filter_routes, action_routes, dashboard_routes
  • Updated main.py to import and include each APIRouter
server/python_backend/main.py
server/python_backend/email_routes.py
server/python_backend/category_routes.py
server/python_backend/gmail_routes.py
server/python_backend/filter_routes.py
server/python_backend/action_routes.py
server/python_backend/dashboard_routes.py
Centralized Pydantic models in a single models.py file
  • Moved ActionExtractionRequest, ActionItem, GmailSyncRequest, SmartRetrievalRequest (and related schemas) into server/python_backend/models.py
  • Imported those models from .models in route modules instead of defining inline
server/python_backend/models.py
server/python_backend/main.py
server/python_backend/email_routes.py
server/python_backend/gmail_routes.py
server/python_backend/filter_routes.py
server/python_backend/action_routes.py
server/python_backend/dashboard_routes.py
Consolidated AI/NLP logic with category-ID matching support
  • Refactored AdvancedAIEngine.analyze_email to be async, accept a DatabaseManager, and match AI categories to DB IDs
  • Injected AdvancedAIEngine and DatabaseManager into GmailAIService and passed db through analysis
  • Adjusted PythonNLPBridge and ai-engine.ts schema to include categoryId from Python output
server/python_backend/ai_engine.py
server/python_nlp/gmail_service.py
server/python-bridge.ts
server/ai-engine.ts
Renamed NLP analyzer classes to model classes and updated initialization
  • Renamed SentimentAnalyzer → SentimentModel, TopicAnalyzer → TopicModel, IntentAnalyzer → IntentModel, UrgencyAnalyzer → UrgencyModel
  • Refactored model loading flags and fallback logic in nlp_engine
server/python_nlp/analysis_components/sentiment_model.py
server/python_nlp/analysis_components/topic_model.py
server/python_nlp/analysis_components/intent_model.py
server/python_nlp/analysis_components/urgency_model.py
Expanded unit and integration tests across Node.js and Python services
  • Added Jest tests for all Node.js routers (email, categories, AI, Gmail, activities, performance, dashboard)
  • Added pytest/FastAPI TestClient tests for Python routes and AsyncMock tests for AdvancedAIEngine and NLP models
  • Included unittest suites for sentiment, intent, topic, and urgency model fallbacks
server/*Routes.test.ts
server/python_backend/tests/*.py
server/python_nlp/tests/analysis_components/*.py

Tips and commands

Interacting with Sourcery

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

Customizing Your Experience

Access your dashboard to:

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

Getting Help

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Jun 16, 2025

Walkthrough

The codebase was refactored to modularize route definitions for both Node.js (Express) and Python (FastAPI) backends. All route logic, models, and service initializations were moved from monolithic files into dedicated modules. New test suites were introduced for each route module, and AI analysis components were updated to support category ID integration and improved dependency injection.

Changes

Files / Modules Change Summary
server/routes.ts, server/emailRoutes.ts, server/categoryRoutes.ts, server/aiRoutes.ts,
server/gmailRoutes.ts, server/performanceRoutes.ts, server/activityRoutes.ts, server/dashboardRoutes.ts
Refactored to modularize route handlers: removed monolithic route registration and mounted dedicated routers for emails, categories, AI, Gmail, activities, dashboard, and performance monitoring. Each router implements its own endpoints, error handling, and timing logs.
server/emailRoutes.test.ts, server/categoryRoutes.test.ts, server/aiRoutes.test.ts,
server/activityRoutes.test.ts, server/dashboardRoutes.test.ts, server/gmailRoutes.test.ts
Added comprehensive Jest test suites for each route module, mocking dependencies and verifying both success and error scenarios for all endpoints.
server/ai-engine.ts, server/python-bridge.ts Extended AI analysis schema and Python bridge to support an optional categoryId field in results, updating type definitions and mapping logic.
server/python_nlp/analysis_components/intent_model.py,
server/python_nlp/analysis_components/sentiment_model.py,
server/python_nlp/analysis_components/topic_model.py,
server/python_nlp/analysis_components/urgency_model.py
Renamed analyzer classes to model classes (e.g., IntentAnalyzerIntentModel) for consistency and clarity.
server/python_nlp/nlp_engine.py Updated imports and initialization to use new model class names and encapsulate loaded models within analyzer/model instances.
server/python_nlp/gmail_service.py Enhanced GmailAIService to accept injected AdvancedAIEngine and DatabaseManager, pass the DB manager to AI analysis for category matching, and unify category ID handling. Removed deprecated topic-to-category mapping.
server/python_backend/main.py Removed all route and model definitions, instead importing and including routers from new route modules. Service instances remain for use by routers.
server/python_backend/email_routes.py, server/python_backend/category_routes.py,
server/python_backend/gmail_routes.py, server/python_backend/filter_routes.py,
server/python_backend/action_routes.py, server/python_backend/dashboard_routes.py
Introduced modular FastAPI routers for emails, categories, Gmail, filters, actions, and dashboard, each with dedicated endpoints, error handling, and performance monitoring.
server/python_backend/models.py Added/moved Pydantic models for requests and responses (e.g., ActionExtractionRequest, ActionItem) from main.py.
server/python_backend/ai_engine.py Made analyze_email async, added optional DB parameter for category matching, and implemented async category ID matching logic. Updated error handling and fallback logic.
server/python_backend/tests/test_email_routes.py, test_category_routes.py, test_dashboard_routes.py,
test_action_routes.py, test_ai_engine.py, test_filter_routes.py, test_gmail_routes.py
Added pytest-based test modules for each FastAPI route, mocking dependencies and verifying endpoint logic, error handling, and integration with AI and DB layers.
server/python_nlp/tests/analysis_components/test_intent_model.py,
test_sentiment_model.py, test_topic_model.py, test_urgency_model.py
Added unittests for each AI model class, covering model-based and fallback logic, and validating output structure and confidence calculations.

Sequence Diagram(s)

Example: Node.js Email Creation Flow

sequenceDiagram
    participant Client
    participant ExpressApp
    participant EmailRouter
    participant Storage
    Client->>ExpressApp: POST /api/emails (email data)
    ExpressApp->>EmailRouter: handle POST /
    EmailRouter->>Storage: createEmail(validatedData)
    Storage-->>EmailRouter: createdEmail
    EmailRouter-->>ExpressApp: 201 Created (createdEmail)
    ExpressApp-->>Client: Response
Loading

Example: FastAPI Gmail Sync Flow

sequenceDiagram
    participant Client
    participant FastAPIApp
    participant GmailRouter
    participant GmailAIService
    participant DatabaseManager
    participant AdvancedAIEngine
    Client->>FastAPIApp: POST /api/gmail/sync (request)
    FastAPIApp->>GmailRouter: handle sync_gmail
    GmailRouter->>GmailAIService: sync_gmail_emails(request)
    GmailAIService->>DatabaseManager: (as needed)
    GmailAIService->>AdvancedAIEngine: analyze_email(subject, content, db)
    AdvancedAIEngine-->>GmailAIService: AIAnalysisResult
    GmailAIService-->>GmailRouter: SyncResult
    GmailRouter-->>FastAPIApp: Response
    FastAPIApp-->>Client: JSON Response
Loading

Suggested labels

enhancement

Poem

(\(\
( -.-)
o_(")(")

Modular routes now hop and play,
Each in their own cozy warren, hooray!
Tests abound, both swift and bright,
AI learns to match categories right.
Backend and frontend, neat and spry—
A burrow of code, reaching for the sky!

✨ Finishing Touches
  • 📝 Generate Docstrings

🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

"""Get real-time performance overview"""
try:
overview = await performance_monitor.get_real_time_dashboard()
return overview

Check warning

Code scanning / CodeQL

Information exposure through an exception Medium

Stack trace information
flows to this location and may be exposed to an external user.

Copilot Autofix

AI 10 months ago

To fix the issue, we need to ensure that no sensitive information, such as stack traces or raw exception messages, is included in the response sent to the client. Instead, we should return a generic error message while logging the detailed error information on the server for debugging purposes. Specifically:

  1. Modify the get_real_time_dashboard method in PerformanceMonitor to return a generic error message in the overallStatus.error field instead of the raw exception message.
  2. Update the get_performance_overview endpoint to ensure that the response does not include sensitive error details.

Suggested changeset 2
server/python_backend/dashboard_routes.py

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/server/python_backend/dashboard_routes.py b/server/python_backend/dashboard_routes.py
--- a/server/python_backend/dashboard_routes.py
+++ b/server/python_backend/dashboard_routes.py
@@ -53,2 +53,4 @@
         overview = await performance_monitor.get_real_time_dashboard()
+        if overview.get("overallStatus", {}).get("status") == "unhealthy":
+            overview["overallStatus"]["error"] = "An internal error occurred."
         return overview
EOF
@@ -53,2 +53,4 @@
overview = await performance_monitor.get_real_time_dashboard()
if overview.get("overallStatus", {}).get("status") == "unhealthy":
overview["overallStatus"]["error"] = "An internal error occurred."
return overview
server/python_backend/performance_monitor.py
Outside changed files

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/server/python_backend/performance_monitor.py b/server/python_backend/performance_monitor.py
--- a/server/python_backend/performance_monitor.py
+++ b/server/python_backend/performance_monitor.py
@@ -242,3 +242,3 @@
                 "timestamp": datetime.now().isoformat(),
-                "overallStatus": {"status": "unhealthy", "error": str(e)},
+                "overallStatus": {"status": "unhealthy", "error": "An internal error occurred."},
                 "quotaStatus": {"dailyUsage": {"percentage": 0}},
EOF
@@ -242,3 +242,3 @@
"timestamp": datetime.now().isoformat(),
"overallStatus": {"status": "unhealthy", "error": str(e)},
"overallStatus": {"status": "unhealthy", "error": "An internal error occurred."},
"quotaStatus": {"dailyUsage": {"percentage": 0}},
Copilot is powered by AI and may make mistakes. Always verify output.
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 34

🔭 Outside diff range comments (2)
server/python_nlp/nlp_engine.py (1)

85-100: Loaded models are discarded – breaks _analyze_topic_model & friends

_sentiment_model_obj, _topic_model_obj, … are correctly loaded, but never assigned to the public attributes (self.sentiment_model, self.topic_model, …).
Any downstream helper (e.g. _analyze_topic_model(), _analyze_sentiment_model()) that still checks these attributes will see None and fall back every time.

         # Load models if dependencies are available
@@
         _urgency_model_obj = None
@@
             _urgency_model_obj = self._load_model(self.urgency_model_path)
 
-        # Initialize SentimentModel …
+        # Persist raw model objects for the legacy helpers
+        self.sentiment_model = _sentiment_model_obj
+        self.topic_model     = _topic_model_obj
+        self.intent_model    = _intent_model_obj
+        self.urgency_model   = _urgency_model_obj
+
+        # Initialize SentimentModel …
server/aiRoutes.test.ts (1)

215-218: Remove sentinel lines – TypeScript parser error

The closing marker made it into the source file and breaks compilation:

-});
-[end of server/aiRoutes.test.ts]
+});
🧹 Nitpick comments (24)
server/python_nlp/analysis_components/sentiment_model.py (1)

17-20: Consider defaulting has_nltk_installed parameter
Requiring callers to pass has_nltk_installed can lead to inconsistencies. You might refactor the constructor to default to the module-level HAS_NLTK flag:

-from typing import Optional, Dict, Any
-from .sentiment_model import HAS_NLTK
+from typing import Optional, Dict, Any
+from .sentiment_model import HAS_NLTK

 class SentimentModel:
-    def __init__(self, sentiment_model: Optional[Any], has_nltk_installed: bool):
+    def __init__(self, sentiment_model: Optional[Any], has_nltk_installed: bool = HAS_NLTK):
         self.model = sentiment_model
         self.has_nltk = has_nltk_installed
         self.logger = logging.getLogger(__name__)
server/python_backend/models.py (1)

135-140: Avoid using object as a field name

The field name object shadows the built-in object type, which can confuse static analysis tools and readers. Consider a more descriptive name such as target or item_object.

-class ActionItem(BaseModel):
-    action_phrase: str
-    verb: Optional[str] = None
-    object: Optional[str] = None
+class ActionItem(BaseModel):
+    action_phrase: str
+    verb: Optional[str] = None
+    target: Optional[str] = Field(None, alias="object")

Add alias="object" if the original payloads already use object.

server/categoryRoutes.ts (1)

20-34: POST / – surface validation details consistently

ZodError details are exposed, but the success branch omits a location header. Consider adding Location: /api/categories/{id} for REST semantics.

server/performanceRoutes.ts (1)

35-40: Consistent default response shape

overview || { … } may drift from the documented schema. Create a typed factory (e.g., emptyPerformanceOverview()) to guarantee field presence and keep the shape in sync between routes.

server/activityRoutes.ts (1)

23-37: Surface Zod validation details for any failure path

Only the happy‐path of the ZodError branch exposes error.errors; if other validation errors surface inside storage.createActivity, callers will only get a generic 500. Consider normalising error responses and logging the original error for observability.

Also note that returning Zod’s errors verbatim may leak internal schema details; you may want to map them to a more compact shape before sending them to clients.

server/python_nlp/tests/analysis_components/test_urgency_model.py (1)

45-63: Avoid coupling tests to private helpers

The suite calls the internal method _analyze_regex(). While useful, this couples the test to implementation details and can hinder refactors. Prefer validating public behaviour only and, if needed, expose a small helper through the public API for unit-level testing.

server/python_backend/tests/test_dashboard_routes.py (1)

52-74: Mock shape mismatch for weekly_growth

The comment notes that weekly_growth must conform to the Pydantic sub-model, then immediately overrides the mock dict. Prefer constructing one canonical fixture that matches the API contract and avoid the mid-test mutation.

Refactor for clarity and maintainability.

server/python_backend/tests/test_category_routes.py (1)

61-66: Consider returning 201 for resource creation

POST /api/categories currently returns 200, but REST conventions reserve 201 for successful creation. Aligning with the convention simplifies client-side expectations and allows the Location header in the future.

If you keep 200 intentionally, add a comment in the route explaining the choice to avoid confusion.

server/python_backend/category_routes.py (2)

53-55: pydantic v2 compatibility

CategoryCreate is a V2 model – favour model_dump() over dict() to respect alias & exclude rules:

-        created_category_dict = await db.create_category(category.dict())
+        created_category_dict = await db.create_category(category.model_dump())

22-33: Re-raise with context for clearer logs

Same B904 pattern as elsewhere:

-        raise HTTPException(status_code=503, detail="Database service unavailable.")
+        raise HTTPException(status_code=503, detail="Database service unavailable.") from db_err

Repeat for the other except blocks.

server/python_backend/dashboard_routes.py (1)

37-47: Chain underlying exceptions

To keep stack-traces intact:

-        raise HTTPException(status_code=503, detail="Database service unavailable.")
+        raise HTTPException(status_code=503, detail="Database service unavailable.") from db_err

Apply similarly in the generic except.

server/python_backend/tests/test_action_routes.py (1)

6-6: Remove unused import ActionItem.

ActionItem is imported but never referenced in this test module, producing an F401/unused-import warning and unnecessary coupling to model code.

-from server.python_backend.models import ActionItem # For response type hint if needed
+# (import not required)
server/python_backend/tests/test_email_routes.py (2)

5-8: Commented guidance can be dropped; it leaks internal context and clutters tests.

These inline remarks about import paths/dependency timing are useful while refactoring but should not remain in committed test code. They don’t add value to future readers and may become outdated.


63-66: dependency_overrides not cleaned up – risk of leakage to other test modules.

FastAPI keeps app.dependency_overrides after the test finishes.
Add a try/finally (or yield) in the client fixture to restore the original state:

@pytest.fixture
def client():
    from server.python_backend.database import get_db
-    app.dependency_overrides[get_db] = lambda: mock_db_manager
-    return TestClient(app)
+    app.dependency_overrides[get_db] = lambda: mock_db_manager
+    try:
+        yield TestClient(app)
+    finally:
+        app.dependency_overrides.clear()
server/python_nlp/tests/analysis_components/test_sentiment_model.py (1)

97-99: if __name__ == "__main__": unittest.main() is redundant under pytest.

Pytest discovers and runs unittest.TestCase classes automatically.
Keeping the manual runner is harmless but unnecessary noise.

server/gmailRoutes.ts (1)

8-37: Repeated try/catch + console.* blocks – extract reusable error handler.

Every route repeats identical error handling. Consider a helper:

const wrap =
  (fn: express.RequestHandler): express.RequestHandler =>
  async (req, res) => {
    try {
      await fn(req, res);
    } catch (err) {
      console.error(err);
      res.status(500).json({
        success: false,
        error: err instanceof Error ? err.message : "Unknown error",
      });
    }
  };

and then router.post("/sync", wrap(async (req,res)=>{ … })).

This removes duplication and guarantees consistent behaviour across future routes.

server/categoryRoutes.test.ts (1)

78-114: Zod-error branch is currently un-testable

The note in-code is correct: mocking storage.createCategory does not exercise the Zod-parsing branch because parsing happens before hitting storage.
Either (a) switch these tests to supertest so the request flows through the real validator or (b) export the raw handler from categoryRoutes.ts and invoke it directly with a crafted ZodError.

Until then this test is a false positive.

server/python_backend/tests/test_ai_engine.py (1)

16-36: Redundant reassignment of analyze_email

patch.object(NLPEngine, 'analyze_email') already replaces the method for every instance.
engine.nlp_engine.analyze_email = mock_nlp_analyze is redundant and may hide leaks if the patch is reverted.
Safe to delete the reassignment line.

server/python_backend/ai_engine.py (1)

70-96: Import DatabaseManager under TYPE_CHECKING to silence F821

Ruff flags DatabaseManager as undefined even though it only appears in type hints.
Add a guarded import to keep runtime deps unchanged and satisfy static analysis:

+from typing import TYPE_CHECKING
+
+if TYPE_CHECKING:           # for type-checkers / linters only
+    from server.python_backend.database import DatabaseManager
server/python_backend/filter_routes.py (2)

2-3: Drop unused typing.* imports

List, Dict, Any are never referenced – they only add noise.

-from typing import List, Dict, Any
+from typing import Any  # if really needed, otherwise remove entirely

90-100: Re-raise DB errors with context

Inside the except psycopg2.Error as db_err: block, re-raise with from db_err to preserve traceback:

-raise HTTPException(status_code=503, detail="Database service unavailable.")
+raise HTTPException(status_code=503,
+                    detail="Database service unavailable.") from db_err
server/python_backend/gmail_routes.py (2)

2-2: Remove unused typing imports

List, Dict, Any are not used; keep the file lean.


21-24: Heavy services created at import time

Instantiating DatabaseManager() and AdvancedAIEngine() at module import can slow startup, hamper unit-tests, and make configuration via env-vars impossible. Wire them via FastAPI Depends or a factory in startup events.

server/aiRoutes.ts (1)

46-55: Leverage optional chaining for cleaner null-checks.

The pattern if (analysis && analysis.categoryId) { ... } is repeated several times.
Using analysis?.categoryId (and similar) makes the intent clearer and satisfies the linter warning (useOptionalChain).

-if (analysis && analysis.categoryId) {
+if (analysis?.categoryId) {
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between dac0f2e and 5a2161a.

📒 Files selected for processing (42)
  • server/activityRoutes.test.ts (1 hunks)
  • server/activityRoutes.ts (1 hunks)
  • server/ai-engine.ts (1 hunks)
  • server/aiRoutes.test.ts (1 hunks)
  • server/aiRoutes.ts (1 hunks)
  • server/categoryRoutes.test.ts (1 hunks)
  • server/categoryRoutes.ts (1 hunks)
  • server/dashboardRoutes.test.ts (1 hunks)
  • server/dashboardRoutes.ts (1 hunks)
  • server/emailRoutes.test.ts (1 hunks)
  • server/emailRoutes.ts (1 hunks)
  • server/gmailRoutes.test.ts (1 hunks)
  • server/gmailRoutes.ts (1 hunks)
  • server/performanceRoutes.ts (1 hunks)
  • server/python-bridge.ts (2 hunks)
  • server/python_backend/action_routes.py (1 hunks)
  • server/python_backend/ai_engine.py (2 hunks)
  • server/python_backend/category_routes.py (1 hunks)
  • server/python_backend/dashboard_routes.py (1 hunks)
  • server/python_backend/email_routes.py (1 hunks)
  • server/python_backend/filter_routes.py (1 hunks)
  • server/python_backend/gmail_routes.py (1 hunks)
  • server/python_backend/main.py (1 hunks)
  • server/python_backend/models.py (1 hunks)
  • server/python_backend/tests/test_action_routes.py (1 hunks)
  • server/python_backend/tests/test_ai_engine.py (1 hunks)
  • server/python_backend/tests/test_category_routes.py (1 hunks)
  • server/python_backend/tests/test_dashboard_routes.py (1 hunks)
  • server/python_backend/tests/test_email_routes.py (1 hunks)
  • server/python_backend/tests/test_filter_routes.py (1 hunks)
  • server/python_backend/tests/test_gmail_routes.py (1 hunks)
  • server/python_nlp/analysis_components/intent_model.py (1 hunks)
  • server/python_nlp/analysis_components/sentiment_model.py (1 hunks)
  • server/python_nlp/analysis_components/topic_model.py (1 hunks)
  • server/python_nlp/analysis_components/urgency_model.py (1 hunks)
  • server/python_nlp/gmail_service.py (6 hunks)
  • server/python_nlp/nlp_engine.py (2 hunks)
  • server/python_nlp/tests/analysis_components/test_intent_model.py (1 hunks)
  • server/python_nlp/tests/analysis_components/test_sentiment_model.py (1 hunks)
  • server/python_nlp/tests/analysis_components/test_topic_model.py (1 hunks)
  • server/python_nlp/tests/analysis_components/test_urgency_model.py (1 hunks)
  • server/routes.ts (2 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (23)
server/dashboardRoutes.ts (1)
server/storage.ts (1)
  • storage (304-304)
server/categoryRoutes.ts (2)
shared/schema.ts (2)
  • categories (11-17)
  • insertCategorySchema (111-113)
server/storage.ts (1)
  • storage (304-304)
server/python_nlp/tests/analysis_components/test_topic_model.py (1)
server/python_nlp/analysis_components/topic_model.py (3)
  • TopicModel (7-94)
  • analyze (82-94)
  • _analyze_keyword (33-80)
server/dashboardRoutes.test.ts (2)
server/python_backend/models.py (1)
  • DashboardStats (275-283)
server/storage.ts (1)
  • storage (304-304)
server/python_backend/category_routes.py (3)
server/python_backend/database.py (2)
  • DatabaseManager (18-662)
  • get_db (665-672)
server/python_backend/models.py (2)
  • CategoryCreate (91-92)
  • CategoryResponse (94-96)
server/python_backend/performance_monitor.py (2)
  • PerformanceMonitor (50-566)
  • track (482-487)
server/emailRoutes.ts (2)
shared/schema.ts (2)
  • emails (19-94)
  • insertEmailSchema (115-117)
server/storage.ts (1)
  • storage (304-304)
server/python-bridge.ts (1)
server/ai-engine.ts (2)
  • AIAnalysis (18-18)
  • AccuracyValidation (28-28)
server/python_backend/tests/test_category_routes.py (3)
server/python_backend/category_routes.py (1)
  • create_category (46-75)
server/python_backend/database.py (1)
  • get_db (665-672)
server/python_backend/tests/test_email_routes.py (1)
  • client (62-66)
server/python_backend/tests/test_dashboard_routes.py (3)
server/python_backend/dashboard_routes.py (1)
  • get_dashboard_stats (16-47)
server/python_backend/database.py (1)
  • get_db (665-672)
server/python_backend/tests/test_email_routes.py (1)
  • client (62-66)
server/python_backend/action_routes.py (3)
server/python_backend/ai_engine.py (2)
  • AdvancedAIEngine (51-270)
  • analyze_email (97-119)
server/python_backend/performance_monitor.py (2)
  • PerformanceMonitor (50-566)
  • track (482-487)
server/python_backend/models.py (2)
  • ActionExtractionRequest (131-133)
  • ActionItem (135-140)
server/python_backend/tests/test_ai_engine.py (2)
server/python_backend/ai_engine.py (3)
  • AdvancedAIEngine (51-270)
  • AIAnalysisResult (18-49)
  • analyze_email (97-119)
server/python_nlp/nlp_engine.py (2)
  • NLPEngine (56-798)
  • analyze_email (681-748)
server/python_backend/dashboard_routes.py (3)
server/python_backend/database.py (2)
  • DatabaseManager (18-662)
  • get_db (665-672)
server/python_backend/performance_monitor.py (2)
  • PerformanceMonitor (50-566)
  • track (482-487)
server/python_backend/models.py (1)
  • DashboardStats (275-283)
server/python_nlp/tests/analysis_components/test_sentiment_model.py (1)
server/python_nlp/analysis_components/sentiment_model.py (3)
  • SentimentModel (16-142)
  • analyze (124-142)
  • _analyze_keyword (85-122)
server/emailRoutes.test.ts (1)
server/storage.ts (1)
  • storage (304-304)
server/gmailRoutes.ts (2)
server/gmail-ai-service.ts (1)
  • gmailAIService (403-403)
server/python_backend/metrics.py (1)
  • metrics (106-111)
server/python_backend/tests/test_gmail_routes.py (2)
server/python_nlp/gmail_service.py (4)
  • sync_gmail_emails (121-159)
  • execute_smart_retrieval (523-558)
  • get_retrieval_strategies (560-576)
  • get_performance_metrics (578-608)
server/python_backend/gmail_routes.py (1)
  • get_retrieval_strategies (175-189)
server/python_backend/tests/test_email_routes.py (4)
server/python_backend/database.py (5)
  • search_emails (569-587)
  • get_emails_by_category (565-567)
  • get_all_emails (561-563)
  • get_email_by_id (248-259)
  • get_db (665-672)
server/python_backend/email_routes.py (2)
  • create_email (95-149)
  • update_email (153-187)
server/python_backend/ai_engine.py (2)
  • analyze_email (97-119)
  • to_dict (35-49)
server/python_nlp/smart_filters.py (1)
  • apply_filters_to_email_data (1253-1293)
server/python_backend/tests/test_filter_routes.py (3)
server/python_nlp/smart_filters.py (4)
  • EmailFilter (16-29)
  • add_custom_filter (579-600)
  • create_intelligent_filters (312-328)
  • prune_ineffective_filters (602-684)
server/python_backend/database.py (2)
  • get_recent_emails (590-593)
  • get_db (665-672)
server/python_backend/tests/test_email_routes.py (1)
  • client (62-66)
server/python_backend/tests/test_action_routes.py (2)
server/python_backend/models.py (1)
  • ActionItem (135-140)
server/python_backend/ai_engine.py (1)
  • analyze_email (97-119)
server/aiRoutes.test.ts (2)
server/python-bridge.ts (2)
  • MappedNLPResult (30-30)
  • pythonNLP (303-303)
server/storage.ts (1)
  • storage (304-304)
server/categoryRoutes.test.ts (1)
server/storage.ts (1)
  • storage (304-304)
server/gmailRoutes.test.ts (1)
server/gmail-ai-service.ts (1)
  • gmailAIService (403-403)
server/aiRoutes.ts (3)
server/python-bridge.ts (2)
  • pythonNLP (303-303)
  • MappedNLPResult (30-30)
server/storage.ts (1)
  • storage (304-304)
shared/schema.ts (1)
  • categories (11-17)
🪛 Pylint (3.3.7)
server/python_nlp/analysis_components/urgency_model.py

[refactor] 7-7: Too few public methods (1/2)

(R0903)

server/python_nlp/analysis_components/sentiment_model.py

[refactor] 16-16: Too few public methods (1/2)

(R0903)

server/python_nlp/analysis_components/topic_model.py

[refactor] 7-7: Too few public methods (1/2)

(R0903)

server/python_nlp/analysis_components/intent_model.py

[refactor] 7-7: Too few public methods (1/2)

(R0903)

server/python_nlp/tests/analysis_components/test_intent_model.py

[error] 57-57: Parsing failed: 'invalid syntax. Perhaps you forgot a comma? (test_intent_model, line 57)'

(E0001)

server/python_nlp/tests/analysis_components/test_topic_model.py

[error] 57-57: Parsing failed: 'invalid syntax. Perhaps you forgot a comma? (test_topic_model, line 57)'

(E0001)

server/python_backend/models.py

[refactor] 131-131: Too few public methods (0/2)

(R0903)


[refactor] 135-135: Too few public methods (0/2)

(R0903)

server/python_backend/tests/test_category_routes.py

[error] 78-78: Parsing failed: 'invalid syntax. Perhaps you forgot a comma? (test_category_routes, line 78)'

(E0001)

server/python_backend/tests/test_dashboard_routes.py

[error] 115-115: Parsing failed: 'invalid syntax. Perhaps you forgot a comma? (test_dashboard_routes, line 115)'

(E0001)

server/python_backend/tests/test_ai_engine.py

[error] 133-133: Parsing failed: 'invalid syntax. Perhaps you forgot a comma? (test_ai_engine, line 133)'

(E0001)

server/python_nlp/tests/analysis_components/test_sentiment_model.py

[error] 100-100: Parsing failed: 'invalid syntax. Perhaps you forgot a comma? (test_sentiment_model, line 100)'

(E0001)

server/python_nlp/tests/analysis_components/test_urgency_model.py

[error] 67-67: Parsing failed: 'invalid syntax. Perhaps you forgot a comma? (test_urgency_model, line 67)'

(E0001)

server/python_backend/tests/test_gmail_routes.py

[error] 111-111: Parsing failed: 'invalid syntax. Perhaps you forgot a comma? (test_gmail_routes, line 111)'

(E0001)

server/python_backend/tests/test_email_routes.py

[error] 169-169: Parsing failed: 'invalid syntax. Perhaps you forgot a comma? (test_email_routes, line 169)'

(E0001)

server/python_backend/tests/test_filter_routes.py

[error] 118-118: Parsing failed: 'invalid syntax. Perhaps you forgot a comma? (test_filter_routes, line 118)'

(E0001)

server/python_backend/tests/test_action_routes.py

[error] 85-85: Parsing failed: 'invalid syntax. Perhaps you forgot a comma? (test_action_routes, line 85)'

(E0001)

server/python_nlp/gmail_service.py

[error] 377-377: Parsing failed: ''{' was never closed (gmail_service, line 377)'

(E0001)

🪛 Ruff (0.11.9)
server/python_nlp/tests/analysis_components/test_intent_model.py

57-57: SyntaxError: Expected ',', found name


57-57: SyntaxError: Expected ',', found name

server/python_nlp/tests/analysis_components/test_topic_model.py

57-57: SyntaxError: Expected ',', found name


57-57: SyntaxError: Expected ',', found name

server/python_backend/category_routes.py

17-17: Do not perform function call Depends in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable

(B008)


32-32: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


42-42: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


49-49: Do not perform function call Depends in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable

(B008)


65-65: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


75-75: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)

server/python_backend/tests/test_category_routes.py

78-78: SyntaxError: Expected ',', found name


78-78: SyntaxError: Expected ',', found name

server/python_backend/tests/test_dashboard_routes.py

115-115: SyntaxError: Expected ',', found name


115-115: SyntaxError: Expected ',', found name

server/python_backend/action_routes.py

57-57: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)

server/python_backend/tests/test_ai_engine.py

133-133: SyntaxError: Expected ',', found name


133-133: SyntaxError: Expected ',', found name

server/python_backend/dashboard_routes.py

16-16: Do not perform function call Depends in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable

(B008)


37-37: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


47-47: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


64-64: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)

server/python_nlp/tests/analysis_components/test_sentiment_model.py

100-100: SyntaxError: Expected ',', found name


100-100: SyntaxError: Expected ',', found name

server/python_nlp/tests/analysis_components/test_urgency_model.py

67-67: SyntaxError: Expected ',', found name


67-67: SyntaxError: Expected ',', found name

server/python_backend/tests/test_gmail_routes.py

111-111: SyntaxError: Expected ',', found name


111-111: SyntaxError: Expected ',', found name

server/python_backend/tests/test_email_routes.py

169-169: SyntaxError: Expected ',', found name


169-169: SyntaxError: Expected ',', found name

server/python_backend/tests/test_filter_routes.py

118-118: SyntaxError: Expected ',', found name


118-118: SyntaxError: Expected ',', found name

server/python_backend/tests/test_action_routes.py

85-85: SyntaxError: Expected ',', found name


85-85: SyntaxError: Expected ',', found name

server/python_backend/email_routes.py

26-26: Do not perform function call Depends in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable

(B008)


48-48: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


58-58: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


62-62: Do not perform function call Depends in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable

(B008)


81-81: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


91-91: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


99-99: Do not perform function call Depends in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable

(B008)


139-139: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


149-149: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


157-157: Do not perform function call Depends in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable

(B008)


177-177: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


187-187: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)

server/python_backend/ai_engine.py

70-70: Undefined name DatabaseManager

(F821)


97-97: Undefined name DatabaseManager

(F821)

server/python_backend/filter_routes.py

2-2: typing.List imported but unused

Remove unused import

(F401)


2-2: typing.Dict imported but unused

Remove unused import

(F401)


2-2: typing.Any imported but unused

Remove unused import

(F401)


38-38: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


65-65: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


69-69: Do not perform function call Depends in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable

(B008)


99-99: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


109-109: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


129-129: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)

server/python_backend/gmail_routes.py

2-2: typing.List imported but unused

Remove unused import

(F401)


2-2: typing.Dict imported but unused

Remove unused import

(F401)


2-2: typing.Any imported but unused

Remove unused import

(F401)


84-84: Do not use bare except

(E722)


109-109: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


119-119: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


136-136: Do not use bare except

(E722)


161-161: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


171-171: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


189-189: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


207-207: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)

🪛 Biome (1.9.4)
server/dashboardRoutes.test.ts

[error] 73-73: expected , but instead found of

Remove of

(parse)


[error] 73-73: expected , but instead found server

Remove server

(parse)

server/emailRoutes.test.ts

[error] 260-260: expected , but instead found of

Remove of

(parse)


[error] 260-260: expected , but instead found server

Remove server

(parse)

server/activityRoutes.test.ts

[error] 103-103: expected , but instead found of

Remove of

(parse)


[error] 103-103: expected , but instead found server

Remove server

(parse)

server/aiRoutes.test.ts

[error] 217-217: expected , but instead found of

Remove of

(parse)


[error] 217-217: expected , but instead found server

Remove server

(parse)

server/categoryRoutes.test.ts

[error] 149-149: expected , but instead found of

Remove of

(parse)


[error] 149-149: expected , but instead found server

Remove server

(parse)

server/gmailRoutes.test.ts

[error] 168-168: expected , but instead found of

Remove of

(parse)


[error] 168-168: expected , but instead found server

Remove server

(parse)

server/aiRoutes.ts

[error] 46-46: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)

🪛 GitHub Check: CodeQL
server/python_backend/dashboard_routes.py

[warning] 54-54: Information exposure through an exception
Stack trace information flows to this location and may be exposed to an external user.

🔇 Additional comments (11)
server/python_nlp/analysis_components/topic_model.py (1)

7-7: Rename aligns with naming conventions
The class has been correctly renamed from TopicAnalyzer to TopicModel, matching other components. Ensure all imports and references (e.g., in nlp_engine.py) are updated accordingly.

server/python_nlp/analysis_components/intent_model.py (1)

7-7: Consistent renaming to IntentModel
The class rename is in line with the updated analyzer-to-model convention. Verify that NLPEngine instantiation and all imports are updated to use IntentModel.

server/python_nlp/analysis_components/sentiment_model.py (1)

16-16: Class rename to SentimentModel is correct
The rename maintains consistency with other model components. Confirm that tests and nlp_engine.py import SentimentModel instead of the old name.

server/python_nlp/analysis_components/urgency_model.py (1)

7-7: Analyzer-to-Model rename applied correctly
UrgencyAnalyzerUrgencyModel is consistent with the other components. Ensure downstream code uses UrgencyModel.

server/ai-engine.ts (1)

15-15: Added categoryId to schema—verify usage
You added categoryId as optional in aiAnalysisSchema, but neither LocalNLPModel nor HuggingFaceModel populate this field. Confirm that your Python bridge or other data mappers supply categoryId, or update the TS models to propagate it.

Run:

rg "categoryId" -n server

to locate where this property must be mapped in your codebase.

server/python-bridge.ts (2)

27-27: LGTM! Proper implementation of category ID mapping.

The optional category_id field and its mapping to categoryId correctly follow the snake_case to camelCase conversion pattern used throughout this file.

Also applies to: 57-57


30-30: Verify that the AIAnalysis type includes categoryId.

The comment suggests that AIAnalysis now includes categoryId, but this should be verified against the actual type definition.

#!/bin/bash
# Description: Verify that AIAnalysis type includes categoryId field

# Search for the aiAnalysisSchema definition to confirm categoryId is included
ast-grep --pattern 'const aiAnalysisSchema = z.object({
  $$$
})'

# Also search for any direct references to categoryId in the schema
rg -A 5 -B 5 "aiAnalysisSchema.*categoryId|categoryId.*aiAnalysisSchema" 
server/routes.ts (1)

3-9: Excellent modularization of routes!

The refactoring successfully separates route logic into dedicated modules, improving code organization and maintainability. The mounting pattern follows Express best practices.

Also applies to: 22-28

server/python_nlp/tests/analysis_components/test_topic_model.py (1)

1-56: Well-structured test suite with comprehensive coverage!

The tests properly mock the sklearn model and cover all major scenarios including model success, failure with fallback, and keyword-based analysis. Good use of assertions to verify the expected behavior.

server/python_nlp/tests/analysis_components/test_urgency_model.py (1)

1-44: 👍 Comprehensive coverage

The tests exercise both the ML and fallback paths and correctly mock model behaviour. Nice use of logging.disable to keep the output clean.

server/python_backend/action_routes.py (1)

47-57: Preserve original traceback when re-raising

When you wrap an exception you lose the causal chain. Pass the original exception with from e:

-        raise HTTPException(status_code=500, detail=f"Failed to extract action items: {str(e)}")
+        raise HTTPException(
+            status_code=500,
+            detail=f"Failed to extract action items: {e}"
+        ) from e

[ suggest_nitpick ]

Comment on lines +7 to +16
router.get("/stats", async (_req, res) => {
try {
console.time("storage.getDashboardStats");
const stats = await storage.getDashboardStats();
console.timeEnd("storage.getDashboardStats");
res.json(stats);
} catch (error) {
res.status(500).json({ message: "Failed to fetch dashboard stats" });
}
});
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Ensure console.time() is always balanced with console.timeEnd()

If an exception is raised after console.time() but before console.timeEnd(), the timer label will remain open, polluting stdout and making later timing logs unreadable.
Wrap the timing calls in a finally block so the timer is stopped even on failure.

router.get("/stats", async (_req, res) => {
-  try {
-    console.time("storage.getDashboardStats");
-    const stats = await storage.getDashboardStats();
-    console.timeEnd("storage.getDashboardStats");
-    res.json(stats);
-  } catch (error) {
-    res.status(500).json({ message: "Failed to fetch dashboard stats" });
-  }
+  console.time("storage.getDashboardStats");
+  try {
+    const stats = await storage.getDashboardStats();
+    res.json(stats);
+  } catch (error) {
+    res.status(500).json({ message: "Failed to fetch dashboard stats" });
+  } finally {
+    console.timeEnd("storage.getDashboardStats");
+  }
});
📝 Committable suggestion

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

Suggested change
router.get("/stats", async (_req, res) => {
try {
console.time("storage.getDashboardStats");
const stats = await storage.getDashboardStats();
console.timeEnd("storage.getDashboardStats");
res.json(stats);
} catch (error) {
res.status(500).json({ message: "Failed to fetch dashboard stats" });
}
});
router.get("/stats", async (_req, res) => {
console.time("storage.getDashboardStats");
try {
const stats = await storage.getDashboardStats();
res.json(stats);
} catch (error) {
res.status(500).json({ message: "Failed to fetch dashboard stats" });
} finally {
console.timeEnd("storage.getDashboardStats");
}
});
🤖 Prompt for AI Agents
In server/dashboardRoutes.ts around lines 7 to 16, the console.time() call is
not guaranteed to be followed by console.timeEnd() if an exception occurs, which
leaves the timer label open and pollutes stdout. To fix this, move
console.time() before the try block and place console.timeEnd() inside a finally
block after the try-catch, ensuring the timer is always stopped regardless of
success or failure.

Comment on lines +11 to +17
console.time("storage.getAllCategories");
const categories = await storage.getAllCategories();
console.timeEnd("storage.getAllCategories");
res.json(categories);
} catch (error) {
res.status(500).json({ message: "Failed to fetch categories" });
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Balance console.time() / console.timeEnd() in the error path

Same timing-leak concern as in dashboardRoutes.ts. Move console.timeEnd() to a finally clause.

🤖 Prompt for AI Agents
In server/categoryRoutes.ts around lines 11 to 17, the console.timeEnd call is
missing in the error path, causing an unbalanced timing measurement. To fix
this, move the console.timeEnd("storage.getAllCategories") call into a finally
block so it executes regardless of success or failure, ensuring balanced timing
calls.

Comment on lines +36 to +52
router.put("/:id", async (req, res) => {
try {
const id = parseInt(req.params.id);
const updateData = req.body;
console.time("storage.updateCategory");
const category = await storage.updateCategory(id, updateData);
console.timeEnd("storage.updateCategory");

if (!category) {
return res.status(404).json({ message: "Category not found" });
}

res.json(category);
} catch (error) {
res.status(500).json({ message: "Failed to update category" });
}
});
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Validate id and request body when updating a category

parseInt(req.params.id) may return NaN, which will reach the storage layer unchecked. There is also no schema validation for updateData, risking silent acceptance of invalid fields.

+const id = Number(req.params.id);
+if (!Number.isInteger(id)) {
+  return res.status(400).json({ message: "Invalid category id" });
+}
+
+// Whitelist permitted fields
+const updateSchema = insertCategorySchema.partial();
+const updateData = updateSchema.parse(req.body);

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In server/categoryRoutes.ts around lines 36 to 52, the code does not validate
the parsed id or the updateData request body before calling
storage.updateCategory. Add a check after parsing the id to verify it is a valid
number and return a 400 error if not. Also, implement schema validation for
updateData using a validation library or manual checks to ensure only valid
fields are accepted, returning a 400 error if validation fails. This prevents
invalid data from reaching the storage layer.

Comment on lines +10 to +13
console.time("gmailAIService.getPerformanceMetrics");
const metrics = await gmailAIService.getPerformanceMetrics();
console.timeEnd("gmailAIService.getPerformanceMetrics");
if (metrics) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Move timer cleanup to finally

Same issue: if gmailAIService.getPerformanceMetrics() throws, the console.timeEnd never executes.

🤖 Prompt for AI Agents
In server/performanceRoutes.ts around lines 10 to 13, the console.timeEnd call
is not guaranteed to run if gmailAIService.getPerformanceMetrics() throws an
error. To fix this, wrap the timing code in a try-finally block where
console.time starts before the try, the await call is inside the try, and
console.timeEnd is called in the finally block to ensure it always executes
regardless of errors.

Comment on lines +55 to +57
if __name__ == '__main__':
unittest.main()
[end of server/python_nlp/tests/analysis_components/test_intent_model.py]
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Remove stray sentinel line causing SyntaxError

The literal line
[end of server/python_nlp/tests/analysis_components/test_intent_model.py]
is not a comment and breaks the test suite.

- [end of server/python_nlp/tests/analysis_components/test_intent_model.py]
+# (file end marker removed)
📝 Committable suggestion

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

Suggested change
if __name__ == '__main__':
unittest.main()
[end of server/python_nlp/tests/analysis_components/test_intent_model.py]
if __name__ == '__main__':
unittest.main()
# (file end marker removed)
🧰 Tools
🪛 Ruff (0.11.9)

57-57: SyntaxError: Expected ',', found name


57-57: SyntaxError: Expected ',', found name

🪛 Pylint (3.3.7)

[error] 57-57: Parsing failed: 'invalid syntax. Perhaps you forgot a comma? (test_intent_model, line 57)'

(E0001)

🤖 Prompt for AI Agents
In server/python_nlp/tests/analysis_components/test_intent_model.py at lines 55
to 57, remove the stray line '[end of
server/python_nlp/tests/analysis_components/test_intent_model.py]' as it is not
a comment and causes a SyntaxError, breaking the test suite.

assert response.json() == mock_performance_data
mock_gmail_service_instance.get_performance_metrics.assert_called_once()

[end of server/python_backend/tests/test_gmail_routes.py]
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Remove non-Python sentinel line

[end of server_python_backend/tests/test_gmail_routes.py] is included in the file body, producing a SyntaxError.
Delete this marker so the test module is valid Python.

🧰 Tools
🪛 Ruff (0.11.9)

111-111: SyntaxError: Expected ',', found name


111-111: SyntaxError: Expected ',', found name

🪛 Pylint (3.3.7)

[error] 111-111: Parsing failed: 'invalid syntax. Perhaps you forgot a comma? (test_gmail_routes, line 111)'

(E0001)

🤖 Prompt for AI Agents
In server/python_backend/tests/test_gmail_routes.py at line 111, remove the
non-Python sentinel line '[end of
server_python_backend/tests/test_gmail_routes.py]' as it causes a SyntaxError.
Delete this line entirely to ensure the test module contains only valid Python
code.

Comment on lines +84 to +86
except: # Broad except for decoding issues
error_details_dict = {"message": "Failed to decode Gmail error content."}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Replace bare except: with explicit exception

Catching everything hides programmer errors and breaks Ctrl-C.
Use except Exception as err: instead and log err.

🧰 Tools
🪛 Ruff (0.11.9)

84-84: Do not use bare except

(E722)

🤖 Prompt for AI Agents
In server/python_backend/gmail_routes.py around lines 84 to 86, replace the bare
except clause with an explicit exception handler by changing `except:` to
`except Exception as err:` and log the caught exception `err` to provide error
details and avoid hiding programmer errors or interfering with keyboard
interrupts.

Comment on lines +60 to +69
# Services are now initialized within their respective route files
# or kept here if they are used by multiple route files or for general app setup.
gmail_service = GmailAIService() # Used by gmail_routes
filter_manager = SmartFilterManager() # Used by filter_routes
ai_engine = AdvancedAIEngine() # Used by email_routes, action_routes
performance_monitor = PerformanceMonitor() # Used by all routes via @performance_monitor.track

# Import routers
from . import email_routes, category_routes, gmail_routes, filter_routes, action_routes, dashboard_routes

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Break the implicit circular import – move service singletons to a dedicated dependency module.

main.py instantiates gmail_service, filter_manager, ai_engine, and performance_monitor, then immediately imports router modules that are likely to import these very symbols back from main.
This tight coupling works only while the import order stays untouched; a small refactor can easily trigger a ImportError: partially initialised module.

Create a deps.py (or similar) that instantiates the singletons and have both main.py and each router import from it:

# deps.py
gmail_service = GmailAIService()
filter_manager = SmartFilterManager()
ai_engine = AdvancedAIEngine()
performance_monitor = PerformanceMonitor()
# main.py
from . import deps
...
app.include_router(email_routes.router)

This removes the bidirectional dependency and clarifies ownership of shared resources.

🤖 Prompt for AI Agents
In server/python_backend/main.py around lines 60 to 69, the service singletons
are instantiated directly in main.py and then router modules are imported,
causing a circular import risk. To fix this, create a new module named deps.py
where you instantiate gmail_service, filter_manager, ai_engine, and
performance_monitor. Then, in main.py and all router modules, import these
singletons from deps.py instead of main.py. This breaks the circular import
dependency and clarifies ownership of shared resources.

Comment on lines +186 to +200
details: `${successCount}/${emailIds.length} emails successfully analyzed and categorized`,
timestamp: new Date().toISOString(),
icon: "fas fa-layer-group",
iconBg: "bg-purple-50 text-purple-600",
});
console.timeEnd("storage.createActivity_batch_analyze");

res.json({
success: true,
results,
summary: {
total: emailIds.length,
processed: results.length,
successful: successCount,
failed: results.length - successCount
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

success/total mismatch when more than 10 IDs are supplied.

emailIds.length is used in the summary and activity details, but only the first 10 IDs are processed (slice(0, 10)).
If 30 IDs are submitted the summary will read “10/30 processed”, which is misleading.

Track the actual processed count:

-const results = [];
+const results: typeof results = [];
 const processedIds = emailIds.slice(0, 10);
 ...
-const successCount = results.filter(r => r.success).length;
+const successCount = results.filter(r => r.success).length;
 ...
-details: `${successCount}/${emailIds.length} emails successfully analyzed and categorized`,
+details: `${successCount}/${processedIds.length} emails successfully analyzed and categorized`,
 ...
-        total: emailIds.length,
-        processed: results.length,
+        total: processedIds.length,
+        processed: results.length,

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In server/aiRoutes.ts around lines 186 to 200, the summary and activity details
use emailIds.length as the total count, but only the first 10 IDs are processed
due to slicing. This causes a mismatch in the success/total counts when more
than 10 IDs are supplied. To fix this, replace emailIds.length with the actual
number of processed IDs (e.g., the length of the sliced array) in both the
summary and activity details to accurately reflect the processed count.

Comment on lines +88 to +98
// Manual categorization (existing logic)
// analysis remains undefined here
const { categoryId, confidence } = req.body;

console.time("storage.updateEmail_categorize_manual"); // Unique label
const updatedEmail = await storage.updateEmail(emailId, {
categoryId: categoryId,
confidence: confidence || 95,
});
console.timeEnd("storage.updateEmail_categorize_manual");

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Validate categoryId when performing manual categorisation.

In the manual branch you call storage.updateEmail with the possibly undefined categoryId.
If the client forgets to send it, the API silently sets categoryId to null, leading to orphaned emails.

Add an explicit guard:

-const { categoryId, confidence } = req.body;
+const { categoryId, confidence } = req.body;
+if (!categoryId) {
+  return res.status(400).json({ message: "categoryId is required for manual categorisation" });
+}
📝 Committable suggestion

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

Suggested change
// Manual categorization (existing logic)
// analysis remains undefined here
const { categoryId, confidence } = req.body;
console.time("storage.updateEmail_categorize_manual"); // Unique label
const updatedEmail = await storage.updateEmail(emailId, {
categoryId: categoryId,
confidence: confidence || 95,
});
console.timeEnd("storage.updateEmail_categorize_manual");
// Manual categorization (existing logic)
// analysis remains undefined here
const { categoryId, confidence } = req.body;
if (!categoryId) {
return res.status(400).json({
message: "categoryId is required for manual categorisation",
});
}
console.time("storage.updateEmail_categorize_manual"); // Unique label
const updatedEmail = await storage.updateEmail(emailId, {
categoryId: categoryId,
confidence: confidence || 95,
});
console.timeEnd("storage.updateEmail_categorize_manual");
🤖 Prompt for AI Agents
In server/aiRoutes.ts around lines 88 to 98, the manual categorization code
calls storage.updateEmail with a potentially undefined categoryId from req.body,
which can cause orphaned emails if missing. Add an explicit check to validate
that categoryId is present and valid before calling updateEmail, and return an
error response if it is missing or invalid to prevent silent failures.

Copy link
Copy Markdown
Contributor

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

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

Hey @MasumRab - I've reviewed your changes and they look great!

Prompt for AI Agents
Please address the comments from this code review:
## Individual Comments

### Comment 1
<location> `server/python_nlp/gmail_service.py:34` </location>
<code_context>
+    def __init__(self,
</code_context>

<issue_to_address>
Default instantiation of DatabaseManager may cause issues in some environments.

If configuration is needed, make db_manager a required argument or raise a clear error on failed instantiation.
</issue_to_address>

### Comment 2
<location> `server/python_backend/ai_engine.py:70` </location>
<code_context>
-        """Analyze email content with AI by calling the NLPEngine directly."""
-        logger.info(f"Initiating AI analysis for email with subject: '{subject[:50]}...' using direct NLPEngine call.")
+
+    async def _match_category_id(self, ai_categories: List[str], db: 'DatabaseManager') -> Optional[int]:
+        """Matches AI suggested category strings to database categories."""
+        if not ai_categories:
+            return None
+
+        # This import is problematic for circular deps if DatabaseManager imports AIEngine.
</code_context>

<issue_to_address>
Category matching is case-insensitive but may not handle synonyms or partial matches.

Since the logic only checks for substrings, it may fail when synonyms or different naming conventions are used. Consider implementing a more robust matching method or maintaining a mapping table to improve accuracy.

Suggested implementation:

```python
    async def _match_category_id(self, ai_categories: List[str], db: 'DatabaseManager') -> Optional[int]:
        """Matches AI suggested category strings to database categories, using synonyms and robust matching."""
        if not ai_categories:
            return None

        # Synonym mapping: keys are normalized AI category names, values are normalized DB category names
        CATEGORY_SYNONYMS = {
            "billing": "invoices",
            "invoice": "invoices",
            "payment": "payments",
            "support": "customer support",
            "help": "customer support",
            "bug": "issue",
            "problem": "issue",
            "purchase": "orders",
            "order": "orders",
            # Add more as needed
        }

        def normalize(s: str) -> str:
            return s.strip().lower()

        try:
            all_db_categories = await db.get_all_categories() # db.get_all_categories() was adapted from get_categories
            if not all_db_categories:
                return None

```

```python
            all_db_categories = await db.get_all_categories() # db.get_all_categories() was adapted from get_categories
            if not all_db_categories:
                return None

            # Build a normalized lookup for DB categories: {normalized_name: (id, original_name)}
            db_cat_lookup = {normalize(cat['name']): (cat['id'], cat['name']) for cat in all_db_categories}

            for ai_cat in ai_categories:
                norm_ai_cat = normalize(ai_cat)
                # 1. Try direct match
                if norm_ai_cat in db_cat_lookup:
                    return db_cat_lookup[norm_ai_cat][0]
                # 2. Try synonym mapping
                if norm_ai_cat in CATEGORY_SYNONYMS:
                    mapped = CATEGORY_SYNONYMS[norm_ai_cat]
                    if mapped in db_cat_lookup:
                        return db_cat_lookup[mapped][0]
                # 3. Try substring/partial match
                for db_norm, (db_id, db_orig) in db_cat_lookup.items():
                    if norm_ai_cat in db_norm or db_norm in norm_ai_cat:
                        return db_id
            return None

```
</issue_to_address>

### Comment 3
<location> `server/python_backend/email_routes.py:93` </location>
<code_context>
+        )
+        raise HTTPException(status_code=500, detail="Failed to fetch email")
+
+@router.post("/api/emails", response_model=EmailResponse) # Changed to EmailResponse
+@performance_monitor.track
+async def create_email(
</code_context>

<issue_to_address>
Return 201 status code for created emails

Add `status_code=201` to the decorator or set it in the response to indicate resource creation.
</issue_to_address>

<suggested_fix>
<<<<<<< SEARCH
@router.post("/api/emails", response_model=EmailResponse) # Changed to EmailResponse
=======
@router.post("/api/emails", response_model=EmailResponse, status_code=201) # Changed to EmailResponse, added status_code=201
>>>>>>> REPLACE

</suggested_fix>

### Comment 4
<location> `server/python_backend/category_routes.py:44` </location>
<code_context>
+        )
+        raise HTTPException(status_code=500, detail="Failed to fetch categories")
+
+@router.post("/api/categories", response_model=CategoryResponse) # Changed to CategoryResponse
+@performance_monitor.track
+async def create_category(
</code_context>

<issue_to_address>
Return 201 status code for created categories

Add `status_code=201` to the POST decorator to return the standard HTTP 201 Created response for new categories.
</issue_to_address>

<suggested_fix>
<<<<<<< SEARCH
@router.post("/api/categories", response_model=CategoryResponse) # Changed to CategoryResponse
=======
@router.post("/api/categories", response_model=CategoryResponse, status_code=201) # Changed to CategoryResponse and returns 201 Created
>>>>>>> REPLACE

</suggested_fix>

### Comment 5
<location> `server/python_backend/filter_routes.py:47` </location>
<code_context>
+    try:
+        description = filter_request_model.criteria.get("description", "")
+
+        new_filter_object = filter_manager.add_custom_filter(
+            name=filter_request_model.name,
+            description=description,
</code_context>

<issue_to_address>
Missing await on add_custom_filter call

Please add 'await' before 'filter_manager.add_custom_filter(...)' to ensure the coroutine is executed properly.
</issue_to_address>

### Comment 6
<location> `server/python_backend/filter_routes.py:18` </location>
<code_context>
+filter_manager = SmartFilterManager() # Initialize filter manager
+performance_monitor = PerformanceMonitor() # Initialize performance monitor
+
+@router.get("/api/filters")
+@performance_monitor.track
+async def get_filters(request: Request):
</code_context>

<issue_to_address>
Define response_model for GET filters route

This enhances both API documentation and response validation.

Suggested implementation:

```python
from .models import FilterRequest, FilterListResponse # Changed from .main to .models; Import response model

```

```python
@router.get("/api/filters", response_model=FilterListResponse)
@performance_monitor.track
async def get_filters(request: Request):

```

If `FilterListResponse` is not yet defined in `.models`, you should define it as a Pydantic model, for example:
```python
from pydantic import BaseModel
from typing import List, Dict, Any

class FilterListResponse(BaseModel):
    filters: List[Dict[str, Any]]
```
Or, if you have an `EmailFilter` Pydantic model:
```python
class FilterListResponse(BaseModel):
    filters: List[EmailFilter]
```
Make sure to adjust the import and model definition as needed to match your codebase.
</issue_to_address>

### Comment 7
<location> `server/python_backend/filter_routes.py:111` </location>
<code_context>
+        )
+        raise HTTPException(status_code=500, detail="Failed to generate filters")
+
+@router.post("/api/filters/prune")
+@performance_monitor.track
+async def prune_filters(request: Request):
</code_context>

<issue_to_address>
Add response_model to prune endpoint

This helps ensure the endpoint returns data in the correct format and improves API documentation.

Suggested implementation:

```python
from pydantic import BaseModel

class PruneFiltersResponse(BaseModel):
    # Adjust fields as appropriate for your actual return structure
    pruned_count: int
    details: dict = {}

@router.post("/api/filters/prune", response_model=PruneFiltersResponse)
@performance_monitor.track
async def prune_filters(request: Request):

```

- You may need to adjust the fields of `PruneFiltersResponse` to match the actual structure of the data returned by `filter_manager.prune_ineffective_filters()`.
- Ensure that the return value from `prune_filters` is a dict compatible with the response model, e.g., `{"pruned_count": ..., "details": ...}`.
- If a response model already exists elsewhere in your codebase, import and use it instead of redefining it.
</issue_to_address>

### Comment 8
<location> `server/python_backend/dashboard_routes.py:49` </location>
<code_context>
+        )
+        raise HTTPException(status_code=500, detail="Failed to fetch dashboard stats")
+
+@router.get("/api/performance/overview")
+async def get_performance_overview(request: Request):
+    """Get real-time performance overview"""
</code_context>

<issue_to_address>
Add performance tracking to overview endpoint

For consistency with other dashboard routes, consider adding the `@performance_monitor.track` decorator here.
</issue_to_address>

<suggested_fix>
<<<<<<< SEARCH
@router.get("/api/performance/overview")
async def get_performance_overview(request: Request):
=======
@router.get("/api/performance/overview")
@performance_monitor.track
async def get_performance_overview(request: Request):
>>>>>>> REPLACE

</suggested_fix>

### Comment 9
<location> `server/python_backend/email_routes.py:30` </location>
<code_context>
+):
+    """Get emails with optional filtering"""
+    try:
+        if search:
+            emails = await db.search_emails(search)
+        elif category_id:
</code_context>

<issue_to_address>
Search filters override category filter

If you want to support filtering by both search and category, update the query logic to combine these filters.

Suggested implementation:

```python
    try:
        if search and category_id:
            emails = await db.search_emails_by_category(search, category_id)
        elif search:
            emails = await db.search_emails(search)
        elif category_id:
            emails = await db.get_emails_by_category(category_id)
        else:
            emails = await db.get_all_emails()

```

You will need to implement the `search_emails_by_category(search: str, category_id: int)` method in your `DatabaseManager` class to support searching emails by both search term and category. If such a method already exists, ensure it is used here. If not, you will need to add it to your database access layer.
</issue_to_address>

### Comment 10
<location> `server/emailRoutes.ts:55` </location>
<code_context>
+router.post("/", async (req, res) => {
+  try {
+    const emailData = insertEmailSchema.parse(req.body);
+    console.time("storage.createEmail");
+    const email = await storage.createEmail(emailData);
+    console.timeEnd("storage.createEmail");
</code_context>

<issue_to_address>
Use structured monitoring instead of console.time

A centralized monitoring tool will provide more robust and production-ready performance metrics than console-based timing.

Suggested implementation:

```typescript
    const transaction = Sentry.startTransaction({ name: "storage.createEmail" });
    let email;
    try {
      email = await storage.createEmail(emailData);
      transaction.finish();
    } catch (err) {
      transaction.finish();
      throw err;
    }

```

1. Ensure that `import * as Sentry from "@sentry/node";` (or your monitoring tool's import) is present at the top of the file.
2. If you use a different monitoring tool, adjust the transaction/span API accordingly.
3. If you want more granular timing, you can use Sentry's child spans or similar constructs in your monitoring tool.
</issue_to_address>

### Comment 11
<location> `server/emailRoutes.ts:68` </location>
<code_context>
+  }
+});
+
+router.put("/:id", async (req, res) => {
+  try {
+    const id = parseInt(req.params.id);
</code_context>

<issue_to_address>
Validate `id` parameter before use

Return a 400 error if `id` is not a valid integer to prevent passing `NaN` to the storage layer.
</issue_to_address>

### Comment 12
<location> `server/performanceRoutes.ts:13` </location>
<code_context>
+    console.time("gmailAIService.getPerformanceMetrics");
+    const metrics = await gmailAIService.getPerformanceMetrics();
+    console.timeEnd("gmailAIService.getPerformanceMetrics");
+    if (metrics) {
+      res.json(metrics);
+    } else {
</code_context>

<issue_to_address>
Explicitly check for null/undefined metrics

Since an empty object is truthy, the else block won't execute. Use `metrics != null` or check for a required property to properly detect missing data.
</issue_to_address>

### Comment 13
<location> `server/activityRoutes.ts:13` </location>
<code_context>
+  try {
+    const { limit } = req.query;
+    console.time("storage.getRecentActivities");
+    const activities = await storage.getRecentActivities(
+      limit ? parseInt(limit as string) : undefined
+    );
</code_context>

<issue_to_address>
Handle invalid `limit` query parameter

Validate or default `limit` if `parseInt` returns `NaN` to prevent issues when calling storage.
</issue_to_address>

### Comment 14
<location> `server/categoryRoutes.ts:36` </location>
<code_context>
+  }
+});
+
+router.put("/:id", async (req, res) => {
+  try {
+    const id = parseInt(req.params.id);
</code_context>

<issue_to_address>
Validate update payload with schema

Consider validating the PUT request body with a partial schema to restrict updates to allowed fields.

Suggested implementation:

```typescript
import { categorySchema } from "../schemas/categorySchema";

router.put("/:id", async (req, res) => {
  try {
    const id = parseInt(req.params.id);
    // Validate update payload with partial schema
    const parseResult = categorySchema.partial().safeParse(req.body);
    if (!parseResult.success) {
      return res.status(400).json({ message: "Invalid update data", errors: parseResult.error.errors });
    }
    const updateData = parseResult.data;
    console.time("storage.updateCategory");
    const category = await storage.updateCategory(id, updateData);
    console.timeEnd("storage.updateCategory");

    if (!category) {
      return res.status(404).json({ message: "Category not found" });
    }

    res.json(category);

```

- Make sure you have a `categorySchema` defined in `../schemas/categorySchema` and that it is a Zod schema.
- Adjust the import path if your schema is located elsewhere.
</issue_to_address>

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

Comment on lines +34 to +43
def __init__(self,
rate_config: Optional[RateLimitConfig] = None,
advanced_ai_engine: Optional[AdvancedAIEngine] = None, # Typed hint
db_manager: Optional[DatabaseManager] = None): # Added db_manager
self.collector = GmailDataCollector(rate_config)
self.metadata_extractor = GmailMetadataExtractor()
self.data_strategy = DataCollectionStrategy()
# self.model_trainer = ModelTrainer() # Potentially remove if not used directly
# self.prompt_engineer = PromptEngineer() # Potentially remove
self.logger = logging.getLogger(f"{__name__}.{self.__class__.__name__}")

# This is key for the refactoring: GmailAIService will use AdvancedAIEngine
# If not passed, a placeholder or default might be instantiated, or an error raised.
# For now, let's assume it's required or a default is handled by the caller.
self.advanced_ai_engine = advanced_ai_engine
if advanced_ai_engine:
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

issue (bug_risk): Default instantiation of DatabaseManager may cause issues in some environments.

If configuration is needed, make db_manager a required argument or raise a clear error on failed instantiation.

Comment on lines +70 to +73
async def _match_category_id(self, ai_categories: List[str], db: 'DatabaseManager') -> Optional[int]:
"""Matches AI suggested category strings to database categories."""
if not ai_categories:
return None
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

suggestion: Category matching is case-insensitive but may not handle synonyms or partial matches.

Since the logic only checks for substrings, it may fail when synonyms or different naming conventions are used. Consider implementing a more robust matching method or maintaining a mapping table to improve accuracy.

Suggested implementation:

    async def _match_category_id(self, ai_categories: List[str], db: 'DatabaseManager') -> Optional[int]:
        """Matches AI suggested category strings to database categories, using synonyms and robust matching."""
        if not ai_categories:
            return None

        # Synonym mapping: keys are normalized AI category names, values are normalized DB category names
        CATEGORY_SYNONYMS = {
            "billing": "invoices",
            "invoice": "invoices",
            "payment": "payments",
            "support": "customer support",
            "help": "customer support",
            "bug": "issue",
            "problem": "issue",
            "purchase": "orders",
            "order": "orders",
            # Add more as needed
        }

        def normalize(s: str) -> str:
            return s.strip().lower()

        try:
            all_db_categories = await db.get_all_categories() # db.get_all_categories() was adapted from get_categories
            if not all_db_categories:
                return None
            all_db_categories = await db.get_all_categories() # db.get_all_categories() was adapted from get_categories
            if not all_db_categories:
                return None

            # Build a normalized lookup for DB categories: {normalized_name: (id, original_name)}
            db_cat_lookup = {normalize(cat['name']): (cat['id'], cat['name']) for cat in all_db_categories}

            for ai_cat in ai_categories:
                norm_ai_cat = normalize(ai_cat)
                # 1. Try direct match
                if norm_ai_cat in db_cat_lookup:
                    return db_cat_lookup[norm_ai_cat][0]
                # 2. Try synonym mapping
                if norm_ai_cat in CATEGORY_SYNONYMS:
                    mapped = CATEGORY_SYNONYMS[norm_ai_cat]
                    if mapped in db_cat_lookup:
                        return db_cat_lookup[mapped][0]
                # 3. Try substring/partial match
                for db_norm, (db_id, db_orig) in db_cat_lookup.items():
                    if norm_ai_cat in db_norm or db_norm in norm_ai_cat:
                        return db_id
            return None

)
raise HTTPException(status_code=500, detail="Failed to fetch email")

@router.post("/api/emails", response_model=EmailResponse) # Changed to EmailResponse
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

suggestion: Return 201 status code for created emails

Add status_code=201 to the decorator or set it in the response to indicate resource creation.

Suggested change
@router.post("/api/emails", response_model=EmailResponse) # Changed to EmailResponse
@router.post("/api/emails", response_model=EmailResponse, status_code=201) # Changed to EmailResponse, added status_code=201

)
raise HTTPException(status_code=500, detail="Failed to fetch categories")

@router.post("/api/categories", response_model=CategoryResponse) # Changed to CategoryResponse
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

suggestion: Return 201 status code for created categories

Add status_code=201 to the POST decorator to return the standard HTTP 201 Created response for new categories.

Suggested change
@router.post("/api/categories", response_model=CategoryResponse) # Changed to CategoryResponse
@router.post("/api/categories", response_model=CategoryResponse, status_code=201) # Changed to CategoryResponse and returns 201 Created

try:
description = filter_request_model.criteria.get("description", "")

new_filter_object = filter_manager.add_custom_filter(
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

issue (bug_risk): Missing await on add_custom_filter call

Please add 'await' before 'filter_manager.add_custom_filter(...)' to ensure the coroutine is executed properly.

@performance_monitor.track
async def generate_intelligent_filters(request: Request, db: DatabaseManager = Depends(get_db)):
"""Generate intelligent filters based on email patterns"""
try:
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

issue (code-quality): Explicitly raise from a previous error [×2] (raise-from-previous-error)

@performance_monitor.track
async def prune_filters(request: Request):
"""Prune ineffective filters"""
try:
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

issue (code-quality): We've found these issues:

background_tasks: BackgroundTasks
):
"""Sync emails from Gmail with AI analysis"""
try:
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

issue (code-quality): We've found these issues:

@performance_monitor.track
async def smart_retrieval(req: Request, request_model: SmartRetrievalRequest): # Renamed params
"""Execute smart Gmail retrieval with multiple strategies"""
try:
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

issue (code-quality): We've found these issues:

"error_detail": str(e),
})
)
raise HTTPException(status_code=500, detail="Failed to fetch strategies")
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

suggestion (code-quality): Explicitly raise from a previous error (raise-from-previous-error)

Suggested change
raise HTTPException(status_code=500, detail="Failed to fetch strategies")
raise HTTPException(
status_code=500, detail="Failed to fetch strategies"
) from e

This commit introduces several changes to enhance the structure and maintainability of the Node.js and Python backend services.

Key changes include:

- Refactored `server/routes.ts`: Split into multiple, more focused route files (e.g., `emailRoutes.ts`, `aiRoutes.ts`).
- Refactored `server/python_backend/main.py`: Divided FastAPI endpoints into separate route modules (e.g., `email_routes.py`, `ai_routes.py`).
- Refactored `server/python_nlp/nlp_engine.py`: Renamed analyzer classes to model classes (e.g., `SentimentModel`) and ensured they encapsulate model loading and prediction logic.
- Consolidated Logic: Centralized the AI-driven category name to category ID matching logic within the Python `AdvancedAIEngine`. This removes redundancy previously present in the Node.js AI routes.
- Organized Python Models: Consolidated Pydantic models used in the Python backend into `server/python_backend/models.py`.
- Added Unit Tests: Introduced new unit tests for the refactored components in both Node.js (Jest) and Python (FastAPI TestClient, pytest) to ensure correctness and prevent regressions.

These changes aim to make the codebase easier to understand, navigate, and maintain, while also reducing redundancy and improving the separation of concerns.
@MasumRab MasumRab merged commit 7c77ff0 into main Jun 16, 2025
4 checks passed
@MasumRab MasumRab deleted the refactor/modularize-backend branch June 18, 2025 05:47
MasumRab added a commit that referenced this pull request Oct 29, 2025
Refactor: Improve modularity and readability of backend services.
MasumRab added a commit that referenced this pull request Nov 6, 2025
Refactor: Improve modularity and readability of backend services.
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.

2 participants