docs: map existing codebase

- 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
This commit is contained in:
2026-02-04 16:53:27 -05:00
parent 6ae36b51a0
commit b0b02d24f4
7 changed files with 1598 additions and 0 deletions

View File

@@ -0,0 +1,333 @@
# 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.tsx` for components, `camelCase.ts` for services
- Components: `ChatScreen.tsx`, `AnswerBubble.tsx`, `QuestionBubble.tsx`
- Services: `conversationService.ts`, `userService.ts`, `oidcService.ts`
- 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: `PascalCase` with 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/biome` package
**Imports:**
- Python: Standard library first, then third-party, then local imports
```python
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)
```typescript
import { useEffect, useState } from "react";
import { conversationService } from "../api/conversationService";
import { QuestionBubble } from "./QuestionBubble";
```
## Import Organization
**Order:**
1. Standard library imports
2. Third-party framework imports (Flask/Quart/React/etc)
3. 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
```python
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
```typescript
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 def` functions use try/except blocks
- TypeScript: `async` functions use try/catch blocks
- Both propagate errors upward with `raise` (Python) or `throw` (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 `logging` module
- TypeScript: `console.log()`, `console.error()`
**Patterns:**
- Python: Structured logging with prefixes
```python
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:**
```python
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
```python
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
```typescript
// Stream events back to client as they happen
async function generate() {
// ...
}
```
**Comment Style:**
- Python: `# Single line` or `"""Docstring"""`
- TypeScript: `// Single line` or `/* 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 params
```python
def 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
```typescript
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/await` pattern
## Module Design
**Exports:**
- Python: No explicit `__all__`, classes/functions imported directly
- TypeScript: Named exports for classes/functions, default export for singleton services
```typescript
export const conversationService = new ConversationService();
```
**Barrel Files:**
- Python: `blueprints/__init__.py` defines blueprints, re-exported
- TypeScript: No barrel files, direct imports
**Structure:**
- Python blueprints: `__init__.py` contains routes, `models.py` for ORM, `logic.py` for 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 def` function signature
**Tool Decorators (LangChain):**
- `@tool` - Mark functions as LangChain tools
- `@tool(response_format="content_and_artifact")` - Specialized tool responses
**Pattern:**
```python
@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:**
```typescript
const [isLoading, setIsLoading] = useState<boolean>(false);
const abortControllerRef = useRef<AbortController | null>(null);
```
## Database Conventions
**ORM:**
- Tortoise ORM with Aerich for migrations
- Models inherit from `Model` base 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:**
```python
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-stream` mimetype
**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*