Skip to content

Commit 116b9cb

Browse files
authored
feat: add ai translation + performance improvements + fix python-bridge logic (#400)
* feat(translation): add AI-powered translation for cluster labels and summaries Implement automatic translation of Polis cluster labels and summaries using Google Cloud Translation API. Translations are generated proactively during math updates and on-demand as fallback. Translation strategy: - Proactive: Translate all cluster labels/summaries during math updates for configured languages - On-demand: Translate individual clusters at request time if translation not found in database - Frontend automatically sends Accept-Language header based on user's display language preference Key changes: - Add Google Cloud Translation API integration with authentication - Implement cluster translation service with parallel processing (p-limit) - Add translation logic to math-updater service for batch translation - Add fallback translation in API service for missing translations - Include cluster ID in metadata for translation mapping - Add database migration for cluster translation storage - Configure translation services across all backend services * perf(database): add composite index for moderation queries and enhance monitoring This commit implements database performance optimizations and infrastructure improvements based on load testing analysis. Database Schema Changes: - Added composite index on conversation_moderation(conversation_id, moderation_action) to optimize lock status checks (V0027 migration) - Renumbered V0024 migration to V0025 to maintain sequential ordering - Added V0026 migration for previous schema changes - The composite index reduces query plan cost for the 4,950 moderation checks observed during load testing (~14% of total queries) Math Updater Improvements - Queue Locking Strategy: - Fixed race condition in conversation update queue processing - Changed early locking from processedAt to lastMathUpdateAt - New logic: requestedAt > lastMathUpdateAt determines if processing needed - lastMathUpdateAt is only touched by math-updater (not API), preventing race conditions - Added detailed pg-boss job state logging when singleton jobs are rejected - Shows whether jobs are RUNNING, QUEUED, or COMPLETED with timing information - Sets processedAt only if requestedAt didn't change during processing Math Updater Improvements - Scanner Logic: - Updated scanner to check requestedAt > lastMathUpdateAt instead of processedAt IS NULL - Added comprehensive job state queries to pgboss.job table - Logs job age, running time, and completion time for better observability - Prevents duplicate job enqueueing when jobs are running or recently completed Math Updater - Batch Query Implementation (from main, leftover from rebase): - Opinion updates are batched (1000 opinions per batch) to avoid PostgreSQL parameter limits - Uses batchArray helper function to split large opinion lists - Cluster stats organized in lookup maps for efficient batch processing - Note: These changes were already merged to main but appear in diff due to rebase Monitoring & Infrastructure: - Enhanced PostgreSQL monitoring configuration in postgresql-monitoring.conf - Updated Prometheus configuration for better metrics collection - Improved Docker Compose setup for development environment - Added connection pooling and read replica routing configuration Load Testing: - Updated scenario to test moderation lock checks under load - Enhanced API client utilities for better test coverage - Improved user action simulation for realistic testing Documentation: - Added DATABASE.md with comprehensive database architecture documentation - Updated PERFORMANCE_ANALYSIS.md with October 2025 optimization details - Added git commit guidelines to CLAUDE.md following Conventional Commits standard - Includes guideline to verify staged changes and check for accidental commits - Explicitly instructs not to mention AI assistants in commit messages - Documented read replica strategy and query optimization approaches Python Bridge: - Minor updates to clustering service for better compatibility Shared Backend Syncing: - Synced shared-backend code to api and math-updater services - Updated schema.ts and db.ts across all consuming services - This is standard rsync distribution of shared database layer code Impact: - Prevents math-updater job duplication race conditions - Marginal query performance improvement (~1-2%) from composite index - Significantly improved observability through enhanced logging - Sets foundation for future caching layer implementation - Better code maintainability with type-safe patterns Technical Notes: - The composite index provides minimal gain due to UNIQUE constraint on conversation_id, but demonstrates query optimization best practices - The requestedAt/lastMathUpdateAt strategy is more reliable than processedAt because it's single-writer (only math-updater modifies lastMathUpdateAt) - See PERFORMANCE_ANALYSIS.md Known Limitations #3 for planned caching layer
1 parent 623b522 commit 116b9cb

63 files changed

Lines changed: 21930 additions & 1192 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

CLAUDE.md

Lines changed: 351 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,351 @@
1+
# CLAUDE.md
2+
3+
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4+
5+
## Project Overview
6+
7+
Agora Citizen Network is a privacy-preserving social platform using zero-knowledge proofs and bridging-based ranking algorithms. The monorepo contains a Vue.js/Quasar frontend, Fastify backend, background worker, and Python clustering service.
8+
9+
## Development Commands
10+
11+
### Running Services
12+
13+
```bash
14+
# Frontend (Vue/Quasar)
15+
make dev-app
16+
17+
# Backend API (Fastify)
18+
make dev-api
19+
20+
# Math updater worker (background jobs)
21+
make dev-math-updater
22+
23+
# Python bridge (clustering service)
24+
make dev-polis
25+
```
26+
27+
### Code Generation & Syncing
28+
29+
```bash
30+
# Generate frontend API client from OpenAPI spec
31+
make generate
32+
33+
# Sync shared code to all services
34+
make sync
35+
36+
# Watch and auto-sync shared code during development
37+
make dev-sync
38+
39+
# Watch and auto-generate API client on OpenAPI changes
40+
make dev-generate
41+
```
42+
43+
### Testing & Linting
44+
45+
```bash
46+
# Frontend
47+
cd services/agora && yarn lint && yarn test
48+
49+
# Backend API
50+
cd services/api && pnpm lint && pnpm test
51+
52+
# Math updater
53+
cd services/math-updater && pnpm lint && pnpm test
54+
```
55+
56+
### Git Commits
57+
58+
This project follows the [Conventional Commits](https://www.conventionalcommits.org/) standard.
59+
60+
**Commit message format:**
61+
```
62+
<type>(<optional scope>): <description>
63+
64+
[optional body - be exhaustive here]
65+
66+
[optional footer(s)]
67+
```
68+
69+
**Common types:**
70+
- `feat`: New feature
71+
- `fix`: Bug fix
72+
- `refactor`: Code change that neither fixes a bug nor adds a feature
73+
- `perf`: Performance improvement
74+
- `docs`: Documentation changes
75+
- `test`: Adding or updating tests
76+
- `chore`: Maintenance tasks, dependency updates, etc.
77+
78+
**Examples:**
79+
```bash
80+
# Simple commit
81+
git commit -m "feat(api): add AI-powered translation for cluster labels"
82+
83+
# Commit with exhaustive body
84+
git commit -m "fix(math-updater): prevent duplicate job processing via early queue locking
85+
86+
The math-updater was experiencing race conditions where multiple instances
87+
would process the same conversation simultaneously. This was caused by jobs
88+
being enqueued before the queue lock was established.
89+
90+
Changes:
91+
- Move queue initialization before job scheduling
92+
- Add singleton pattern with 60s deduplication window
93+
- Implement early lock acquisition in job handler
94+
- Add comprehensive logging for queue state transitions
95+
96+
This ensures only one worker processes a conversation at a time, preventing
97+
duplicate updates and database contention."
98+
```
99+
100+
**Guidelines:**
101+
- Keep the subject line concise (50-72 characters)
102+
- Use imperative mood ("add" not "added" or "adds")
103+
- Don't capitalize the first letter after the colon
104+
- No period at the end of the subject line
105+
- **Be exhaustive in the body**: explain what changed, why it changed, and any important implementation details
106+
- Use the body to provide context that reviewers and future maintainers will need
107+
- Reference issue numbers, design decisions, or related PRs in the body or footer
108+
- **Do NOT mention AI assistants or tools** (e.g., "Claude", "AI-generated") in commit messages
109+
110+
**Before committing:**
111+
1. **Review all staged changes** with `git status` and `git diff --staged`
112+
2. **Verify nothing is staged by mistake** (secrets, debug code, unrelated changes, etc.)
113+
3. **If suspicious files are found**, report them to the user and abort the commit
114+
4. **Wait for user confirmation** before proceeding with the commit
115+
116+
### Database Operations
117+
118+
```bash
119+
cd services/api
120+
121+
# Generate migration from Drizzle schema changes
122+
pnpm db:generate
123+
124+
# Run migrations (requires Docker + Flyway)
125+
pnpm db:migrate
126+
127+
# Undo last migration
128+
pnpm db:undo
129+
```
130+
131+
### Docker Images
132+
133+
```bash
134+
# Build images for each service
135+
cd services/agora && yarn image:build
136+
cd services/api && pnpm image:build
137+
cd services/math-updater && pnpm image:build
138+
```
139+
140+
## Architecture
141+
142+
### Service Communication
143+
144+
```
145+
Frontend (Vue/Quasar) → OpenAPI Client → API (Fastify)
146+
147+
PostgreSQL (primary + read replica)
148+
149+
Math-updater (pg-boss jobs) ←──────────────┘
150+
151+
Python-bridge (Flask/reddwarf clustering)
152+
```
153+
154+
### Services
155+
156+
- **agora** (`services/agora/`): Vue 3 + Quasar frontend with Pinia state management
157+
- **api** (`services/api/`): Fastify backend with Drizzle ORM, handles auth/conversations/voting
158+
- **math-updater** (`services/math-updater/`): Background worker using pg-boss for clustering updates and AI label generation
159+
- **python-bridge** (`services/python-bridge/`): Flask service wrapping reddwarf clustering algorithms
160+
- **shared**, **shared-app-api**, **shared-backend**: Shared TypeScript code synced via rsync
161+
162+
### Shared Code Strategy
163+
164+
Shared code is distributed via **rsync** (not npm linking) because Drizzle ORM requires direct file access:
165+
166+
- `services/shared/` → synced to all services (types, utilities)
167+
- `services/shared-app-api/` → synced to frontend + API (UCAN, auth)
168+
- `services/shared-backend/` → synced to API + math-updater (database schema, translations)
169+
170+
Files generated from shared directories have warning comments at the top. Always edit source files in `services/shared*/src/`, never the synced copies.
171+
172+
### Database Layer
173+
174+
- **ORM**: Drizzle with TypeScript schema in `services/shared-backend/src/schema.ts`
175+
- **Read replicas**: Automatic routing via `withReplicas()` - SELECTs use replica, writes use primary
176+
- **Migrations**: Flyway-based versioned migrations in `services/api/database/flyway/`
177+
- **Connection**: Supports both direct connection strings and AWS Secrets Manager
178+
179+
### OpenAPI-First API Development
180+
181+
1. Backend defines routes with Zod schemas + Swagger decorators
182+
2. Fastify generates `services/api/openapi-zkorum.json`
183+
3. OpenAPI generator creates TypeScript client in `services/agora/src/api/`
184+
4. Frontend imports typed client functions
185+
186+
When adding API endpoints:
187+
- Define Zod schema for request/response
188+
- Add Swagger decorator to route
189+
- Run `make generate` to update frontend client
190+
- Never manually edit generated API client code
191+
192+
### Background Jobs (pg-boss)
193+
194+
Math-updater uses PostgreSQL-based job queue:
195+
196+
- **Job types**: `scan-conversations`, `update-conversation-math`
197+
- **Singleton pattern**: Jobs deduplicated with `singletonSeconds`
198+
- **Known issue**: Early queue locking prevents duplicate job bug (see `services/math-updater/src/index.ts`)
199+
200+
### Authentication
201+
202+
- **UCAN (User Controlled Authorization Network)**: Capability-based auth with signed requests
203+
- **Rarimo integration**: Zero-knowledge proof for anonymous human verification
204+
- **Phone OTP**: Twilio-based phone verification fallback
205+
206+
Authorization headers built via `buildAuthorizationHeader(encodedUcan)` in frontend API wrappers.
207+
208+
## Key Files
209+
210+
- `services/api/src/index.ts`: Main backend entry point, route registration
211+
- `services/shared-backend/src/schema.ts`: Database schema (all tables)
212+
- `services/shared-backend/src/db.ts`: Database connection with read replica routing
213+
- `services/agora/src/stores/`: Pinia state management
214+
- `services/agora/src/utils/api/`: Frontend API wrapper layer
215+
- `services/math-updater/src/index.ts`: Background job worker
216+
- `Makefile`: Build orchestration and dev commands
217+
218+
## Code Quality Principles
219+
220+
### Favor Static Type Safety Over Defensive Programming
221+
222+
This codebase prioritizes **strong static type safety** using TypeScript to eliminate entire classes of bugs at compile time, rather than relying on runtime defensive checks.
223+
224+
**Preferred approach:**
225+
```typescript
226+
// GOOD: Use type-safe data structures that make invalid states unrepresentable
227+
const majorityOpinions: Array<{
228+
probability: SQL;
229+
type: SQL;
230+
}> = [];
231+
232+
// Both fields are guaranteed to exist together
233+
for (const opinion of majorityOpinions) {
234+
// TypeScript ensures probability and type are always in sync
235+
use(opinion.probability, opinion.type);
236+
}
237+
```
238+
239+
**Avoid:**
240+
```typescript
241+
// AVOID: Separate arrays that can get out of sync + runtime consistency check
242+
const probabilities: SQL[] = [];
243+
const types: SQL[] = [];
244+
245+
// Manual defensive check needed at runtime
246+
if (probabilities.length !== types.length) {
247+
throw new Error("Arrays out of sync!");
248+
}
249+
```
250+
251+
**Guidelines:**
252+
- **Design data structures** that enforce invariants at the type level
253+
- **Use discriminated unions** instead of boolean flags + null checks
254+
- **Leverage TypeScript's type system** (mapped types, conditional types, `as const`)
255+
- **Prefer readonly/immutable types** where possible to prevent accidental mutations
256+
- **Use Zod schemas** for runtime validation at system boundaries (API requests, external data)
257+
- **Avoid runtime assertions** for invariants that can be enforced by types
258+
259+
**Examples in this codebase:**
260+
- Math-updater uses type-safe `majorityOpinions` array instead of parallel arrays (see `services/math-updater/src/services/polisMathUpdater.ts:425`)
261+
- Drizzle ORM schema provides compile-time guarantees for database operations
262+
- Zod schemas validate external data at API boundaries while internal code uses strong types
263+
264+
**When defensive programming IS appropriate:**
265+
- External system boundaries (user input, third-party APIs)
266+
- Data from untyped sources (raw SQL, environment variables)
267+
- Legacy code integration where types cannot be guaranteed
268+
269+
### Logging Guidelines
270+
271+
**Important:** Do NOT use `log.debug()` in this codebase. Always use `log.info()`, `log.warn()`, or `log.error()` instead.
272+
273+
**Rationale:**
274+
- Debug logs are rarely checked in production and add noise
275+
- Info-level logs are always visible and provide better traceability
276+
- Simpler to grep/filter logs when there are fewer log levels in use
277+
278+
**Examples:**
279+
```typescript
280+
// GOOD
281+
log.info(`[Math Updater] Processing conversation ${conversationSlugId}`);
282+
log.warn(`[Scanner] Skipped conversation due to rate limiting`);
283+
log.error(error, `[API] Failed to create opinion`);
284+
285+
// BAD - Never use log.debug
286+
log.debug(`Processing started`); // ❌ Don't use this
287+
```
288+
289+
## Important Patterns
290+
291+
### Adding a New API Endpoint
292+
293+
1. Define Zod schema in `services/api/src/` (or reuse from shared)
294+
2. Add route with schema in `services/api/src/index.ts`
295+
3. Implement handler calling service layer function
296+
4. Run `make generate` to update frontend client
297+
5. Use generated client in frontend: `const { fetchData } = useBackendXApi()`
298+
299+
### Adding a Database Table
300+
301+
1. Edit `services/shared-backend/src/schema.ts` to add table definition
302+
2. Run `cd services/shared-backend && pnpm run sync` to distribute changes
303+
3. Run `cd services/api && pnpm db:generate` to create migration
304+
4. Review generated SQL in `services/api/drizzle/`
305+
5. Run `pnpm db:migrate` to apply migration
306+
6. Import new table in service code: `import { newTable } from '@/shared-backend/schema'`
307+
308+
### Working with Shared Code
309+
310+
1. Edit source files in `services/shared*/src/`
311+
2. Run `make sync` (or `make dev-sync` for auto-watch)
312+
3. Generated files include `/** WARNING: GENERATED FROM ... **/` comments
313+
4. Never directly edit synced files - changes will be overwritten
314+
315+
### Running Tests for a Specific Module
316+
317+
```bash
318+
# Frontend component tests
319+
cd services/agora && yarn test
320+
321+
# Backend service tests
322+
cd services/api && pnpm test
323+
324+
# Math updater tests
325+
cd services/math-updater && pnpm test
326+
```
327+
328+
## Environment Configuration
329+
330+
Each service uses environment variables:
331+
332+
- `.env` - Local development defaults
333+
- `.env.staging` - Staging environment
334+
- `.env.production` - Production environment
335+
336+
Key variables:
337+
- `CONNECTION_STRING` / `CONNECTION_STRING_READ` - Database connections
338+
- `AWS_SECRET_ID_*` - AWS Secrets Manager credentials
339+
- `POLIS_BASE_URL` - Python-bridge service URL
340+
- `MATH_UPDATER_BATCH_SIZE` - Concurrent conversation processing
341+
- Translation API keys for AI label generation
342+
343+
## Prerequisites
344+
345+
- Node.js 20+ (frontend uses 22/24)
346+
- pnpm (backend services)
347+
- yarn (frontend)
348+
- Python 3.11+ (python-bridge)
349+
- Docker (for Flyway migrations and production builds)
350+
- watchman (for file watching during development)
351+
- rsync, make, jq, sed (build tools)

0 commit comments

Comments
 (0)