- STACK.md - Technologies and dependencies - ARCHITECTURE.md - System design and patterns - STRUCTURE.md - Directory layout - CONVENTIONS.md - Code style and patterns - TESTING.md - Test structure - INTEGRATIONS.md - External services - CONCERNS.md - Technical debt and issues
11 KiB
11 KiB
Coding Conventions
Analysis Date: 2026-02-04
Naming Patterns
Files:
- Python:
snake_case.py-ynab_service.py,mealie_service.py,oidc_service.py - TypeScript/React:
PascalCase.tsxfor components,camelCase.tsfor services- Components:
ChatScreen.tsx,AnswerBubble.tsx,QuestionBubble.tsx - Services:
conversationService.ts,userService.ts,oidcService.ts
- Components:
- Config files:
snake_case.py-aerich_config.py,oidc_config.py
Functions:
- Python:
snake_case-get_budget_summary(),parse_relative_date(),consult_simba_oracle() - TypeScript:
camelCase-handleQuestionSubmit(),sendQueryStream(),fetchWithRefreshToken()
Variables:
- Python:
snake_case-budget_id,access_token,llama_url,current_user_uuid - TypeScript:
camelCase-conversationId,streamingContent,isLoading
Types:
- Python classes:
PascalCase-YNABService,MealieService,LLMClient,User,Conversation - Python enums:
PascalCasewith SCREAMING_SNAKE_CASE values -Speaker.USER,Speaker.SIMBA - TypeScript interfaces:
PascalCase-Message,Conversation,QueryResponse,StreamEvent - TypeScript types:
PascalCase-ChatScreenProps,QuestionAnswer
Constants:
- Python:
SCREAMING_SNAKE_CASE-DATABASE_URL,TORTOISE_CONFIG,PROVIDER - TypeScript:
camelCase-baseUrl,conversationBaseUrl
Code Style
Formatting:
- Python: No explicit formatter configured (no Black, autopep8, or yapf config detected)
- Manual formatting observed: 4-space indentation, line length ~88-100 chars
- TypeScript: Biome 2.3.10 configured in
raggr-frontend/package.json- No explicit biome.json found, using defaults
Linting:
- Python: No linter config detected (no pylint, flake8, ruff config)
- TypeScript: Biome handles linting via
@biomejs/biomepackage
Imports:
- Python: Standard library first, then third-party, then local imports
import os import logging from datetime import datetime from dotenv import load_dotenv from quart import Blueprint from .models import User from .logic import get_conversation - TypeScript: React imports, then third-party, then local (relative)
import { useEffect, useState } from "react"; import { conversationService } from "../api/conversationService"; import { QuestionBubble } from "./QuestionBubble";
Import Organization
Order:
- Standard library imports
- Third-party framework imports (Flask/Quart/React/etc)
- Local application imports (blueprints, utils, models)
Path Aliases:
- None detected in TypeScript - uses relative imports (
../api/,./components/) - Python uses absolute imports for blueprints and utils modules
Absolute vs Relative:
- Python: Absolute imports for cross-module (
from utils.ynab_service import YNABService) - TypeScript: Relative imports (
../api/conversationService)
Error Handling
Patterns:
- Python: Try/except with detailed logging
try: # operation logger.info("[SERVICE] Operation details") except httpx.HTTPStatusError as e: logger.error(f"[SERVICE] HTTP error: {e.response.status_code}") raise except Exception as e: logger.error(f"[SERVICE] Error: {type(e).__name__}: {str(e)}") logger.exception("[SERVICE] Full traceback:") raise - TypeScript: Try/catch with console.error, re-throw or handle gracefully
try { const response = await fetch(); } catch (error) { console.error("Failed to fetch:", error); if (error.message.includes("Session expired")) { setAuthenticated(false); } }
Async Error Handling:
- Python:
async deffunctions use try/except blocks - TypeScript:
asyncfunctions use try/catch blocks - Both propagate errors upward with
raise(Python) orthrow(TypeScript)
HTTP Errors:
- Python Quart: Return
jsonify({"error": "message"}), status_code - Python httpx: Raise HTTPStatusError, log response text
- TypeScript: Throw Error with descriptive message
Logging
Framework:
- Python: Standard
loggingmodule - TypeScript:
console.log(),console.error()
Patterns:
- Python: Structured logging with prefixes
logger = logging.getLogger(__name__) logger.info("[SERVICE] Operation started") logger.error(f"[SERVICE] Error: {details}") logger.exception("[SERVICE] Full traceback:") # After except - Logging levels: INFO for operations, ERROR for failures, DEBUG for detailed data
- Service-specific prefixes:
[YNAB],[MEALIE],[YNAB TOOLS]
When to Log:
- Entry/exit of major operations (API calls, database queries)
- Error conditions with full context
- Configuration/initialization status
- Performance metrics (timing critical operations)
Examples from codebase:
logger.info(f"[YNAB] get_budget_summary() called for budget_id: {self.budget_id}")
logger.info(f"[YNAB] Total budgeted: ${total_budgeted:.2f}")
logger.error(f"[YNAB] Error in get_budget_summary(): {type(e).__name__}: {str(e)}")
Comments
When to Comment:
- Complex business logic (date parsing, budget calculations)
- Non-obvious workarounds or API quirks
- Important configuration decisions
- Docstrings for all public functions/methods
JSDoc/TSDoc:
- Python: Docstrings with Args/Returns sections
def get_transactions(self, start_date: Optional[str] = None) -> dict[str, Any]: """Get transactions filtered by date range. Args: start_date: Start date in YYYY-MM-DD or relative ('this_month') Returns: Dictionary containing matching transactions and summary. """ - TypeScript: Inline comments, no formal JSDoc detected
// Stream events back to client as they happen async function generate() { // ... }
Comment Style:
- Python:
# Single lineor"""Docstring""" - TypeScript:
// Single lineor/* Multi-line */ - No TODOs/FIXMEs in project code (only in node_modules)
Function Design
Size:
- Python: 20-100 lines typical, some reach 150+ (service methods with error handling)
- TypeScript: 10-50 lines for React components, 20-80 for service methods
- Large functions acceptable when handling complex workflows (streaming, API interactions)
Parameters:
- Python: Explicit typing with
Optional[type], defaults for optional paramsdef get_transactions( self, start_date: Optional[str] = None, end_date: Optional[str] = None, limit: int = 50, ) -> dict[str, Any]: - TypeScript: Interfaces for complex parameter objects
async sendQueryStream( query: string, conversation_id: string, callbacks: StreamCallbacks, signal?: AbortSignal, ): Promise<void>
Return Values:
- Python: Explicit return type hints -
-> dict[str, Any],-> str,-> bool - TypeScript: Explicit types -
: Promise<Conversation>,: void - Dictionary/object returns for complex data (not tuples in Python)
Async/Await:
- Python Quart: All route handlers are
async def - Python services: Database queries and external API calls are
async - TypeScript: All API calls use
async/awaitpattern
Module Design
Exports:
- Python: No explicit
__all__, classes/functions imported directly - TypeScript: Named exports for classes/functions, default export for singleton services
export const conversationService = new ConversationService();
Barrel Files:
- Python:
blueprints/__init__.pydefines blueprints, re-exported - TypeScript: No barrel files, direct imports
Structure:
- Python blueprints:
__init__.pycontains routes,models.pyfor ORM,logic.pyfor business logic - Services in separate modules:
utils/ynab_service.py,utils/mealie_service.py - Separation of concerns: routes, models, business logic, utilities
Decorators
Authentication:
@jwt_refresh_token_required- Standard auth requirement@admin_required- Custom decorator for admin-only routes (wraps@jwt_refresh_token_required)
Route Decorators:
@app.route()or@blueprint.route()with HTTP method- Async routes:
async deffunction signature
Tool Decorators (LangChain):
@tool- Mark functions as LangChain tools@tool(response_format="content_and_artifact")- Specialized tool responses
Pattern:
@conversation_blueprint.post("/query")
@jwt_refresh_token_required
async def query():
current_user_uuid = get_jwt_identity()
# ...
Type Hints
Python:
- Modern type hints throughout:
dict[str, Any],Optional[str],list[str] - Tortoise ORM types:
fields.ForeignKeyRelation - No legacy typing module usage (using built-in generics)
TypeScript:
- Strict typing with interfaces
- Union types for variants:
"user" | "simba",'status' | 'content' | 'done' | 'error' - Generic types:
Promise<T>,React.ChangeEvent<HTMLTextAreaElement>
State Management
Python (Backend):
- Database: Tortoise ORM async models
- In-memory: Module-level variables for services (
ynab_service,mealie_service) - Session: JWT tokens, in-memory dict for OIDC sessions (
_oidc_sessions)
TypeScript (Frontend):
- React hooks:
useState,useEffect,useRef - localStorage for JWT tokens (via
userService) - No global state management library (no Redux/Zustand)
Pattern:
const [isLoading, setIsLoading] = useState<boolean>(false);
const abortControllerRef = useRef<AbortController | null>(null);
Database Conventions
ORM:
- Tortoise ORM with Aerich for migrations
- Models inherit from
Modelbase class - Field definitions:
fields.UUIDField,fields.CharField,fields.ForeignKeyField
Naming:
- Table names: Lowercase plural (
users,conversations,conversation_messages) - Foreign keys: Singular model name (
user,conversation) - Related names: Plural (
conversations,messages)
Pattern:
class Conversation(Model):
id = fields.UUIDField(primary_key=True)
name = fields.CharField(max_length=255)
user: fields.ForeignKeyRelation = fields.ForeignKeyField(
"models.User", related_name="conversations", null=True
)
class Meta:
table = "conversations"
API Conventions
REST Endpoints:
- Prefix:
/api/{resource} - Blueprints:
/api/user,/api/conversation,/api/rag - CRUD patterns: GET for fetch, POST for create/actions, PUT for update, DELETE for remove
Request/Response:
- JSON payloads:
await request.get_json() - Responses:
jsonify({...})with optional status code - Streaming: Server-Sent Events (SSE) with
text/event-streammimetype
Authentication:
- JWT in Authorization header (managed by
quart-jwt-extended) - Refresh tokens for long-lived sessions
- OIDC flow for external authentication
Convention analysis: 2026-02-04