File size: 16,099 Bytes
38f9c15
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
# Rooting Future - Architecture & Patterns

## πŸ—οΈ System Architecture

### High-Level Overview

```
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                        Flask App (app.py)                    β”‚
β”‚  Routes | Auth | Webhooks | SSE Streaming | Admin Panel     β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                        β”‚
        β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
        β”‚                               β”‚
        β–Ό                               β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”          β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  Multi-Agent     β”‚          β”‚  Structured      β”‚
β”‚  Orchestrator    β”‚          β”‚  Orchestrator    β”‚
β”‚  (agents.py)     β”‚          β”‚  (structured_    β”‚
β”‚                  β”‚          β”‚   agent.py)      β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜          β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
         β”‚                             β”‚
         β”‚ spawns 6+1 agents           β”‚ generates tables/data
         β”‚                             β”‚
         β–Ό                             β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚            Gemini 2.0 Flash API                β”‚
β”‚         (google-generativeai SDK)              β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
         β”‚                             β”‚
         β”‚ stores/retrieves            β”‚
         β–Ό                             β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  Knowledge Store    β”‚    β”‚   Export Pipeline    β”‚
β”‚  (SQLite + RAG)     β”‚    β”‚   (PDF/DOCX/HTML)    β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
```

## πŸ“¦ Core Components

### 1. Multi-Agent Orchestrator (`agents.py`)

**Purpose**: Coordinates 6 specialized agents + 1 coordinator to generate strategic plans.

**Architecture**:
```python
class MultiAgentOrchestrator:
    def __init__(self, knowledge_store, file_search_store_name):
        self.agents = {
            AgentRole.COORDINATOR: StrategicAgent(...)
            AgentRole.STW_SPORTIVI: StrategicAgent(...)
            AgentRole.STW_STRUTTURALI: StrategicAgent(...)
            AgentRole.STW_MARKETING: StrategicAgent(...)
            AgentRole.STW_SOCIALI: StrategicAgent(...)
            AgentRole.FINANCIAL: StrategicAgent(...)
        }

    def generate_strategic_plan(self, club_data, parallel=True):
        # Execute agents
        # Synthesize with coordinator
        # Return complete plan
```

**Key Methods**:
- `generate_strategic_plan()` - Main entry point
- `_generate_sequential()` - Execute agents one by one
- `_generate_parallel()` - Execute agents concurrently (⚠️ currently fake async)
- `generate_single_section()` - Regenerate specific section

**Agent Roles**:
| Role | Responsibility | Output Section |
|------|---------------|----------------|
| COORDINATOR | Executive summary synthesis | `executive_summary` |
| STW_SPORTIVI | 8 sports objectives (MACRO 1-8) | `stw_sportivi` |
| STW_STRUTTURALI | 2 infrastructure objectives | `stw_strutturali` |
| STW_MARKETING | 4 marketing objectives | `stw_marketing` |
| STW_SOCIALI | 7 social objectives | `stw_sociali` |
| FINANCIAL | Economic-financial plan | `financial_plan` |

### 2. Strategic Agent (`agents.py:StrategicAgent`)

**Purpose**: Individual agent that generates content for one domain.

**Pattern**:
```python
class StrategicAgent:
    def __init__(self, spec: AgentSpec, file_search_store_name: str):
        self.spec = spec  # System prompt, expertise
        self.model = genai.GenerativeModel(MODEL_CONFIG.name)
        self.cache = AICache()  # File-based cache
        self.sourcer = SourcedContentGenerator()

    def generate(self, club_data, research_data, rag_context):
        # 1. Build prompt with RAG context
        prompt = self._build_simple_prompt(club_data, rag_context)

        # 2. Check AI cache
        cached = self.cache.get(prompt)
        if cached:
            return cached

        # 3. Call Gemini
        response = self.model.generate_content(prompt)

        # 4. Post-process and cache
        content = self._post_process(response.text)
        self.cache.set(prompt, content)

        return {'content': content, 'sources': [...]}
```

**Critical Details**:
- ⚠️ **Gemini calls are synchronous** - blocking I/O
- βœ… **AI Cache** reduces redundant calls (~30% hit rate)
- βœ… **Fallback mechanism** if tools fail (retry without tools)
- ⚠️ **No rate limiting** - can hit API limits

### 3. RAG System (Knowledge Store + File Search)

**Purpose**: Learn from previous successful plans to improve quality.

**Flow**:
```
Previous Plans β†’ SQLite β†’ Embeddings β†’ File Search Store (Google)
                                              ↓
New Plan Generation ← RAG Context Fetch β†β”€β”€β”€β”€β”€β”˜
```

**Implementation**:
```python
# knowledge_store.py
class SQLiteKnowledgeStore:
    def save_plan(self, plan_id, club_data, plan_content):
        # Save to SQLite
        # Upload to Gemini File Search Store for RAG

    def get_context_for_generation(self, club_category, section_type):
        # Query similar plans by category
        # Return 1-2 best examples
```

**⚠️ Performance Issue**:
- No indices on `category`, `section_type` β†’ linear scan
- Queried for EVERY agent (6x per plan) β†’ bottleneck

### 4. Export Pipeline

**Current State** (6 separate modules):

```
export_pdf_server.py    β†’ WeasyPrint (HTML β†’ PDF)
export_html.py          β†’ Jinja2 templates
export_docx.py          β†’ python-docx
export_paged.py         β†’ Paged CSS media (print-optimized HTML)
export_onepager.py      β†’ Infographic summary
export_package.py       β†’ ZIP bundler (all formats)
```

**Common Pattern** (duplicated 6 times):
```python
def export(club_name, plan_data, metadata):
    # 1. Apply branding (colors, logo)
    # 2. Render template
    # 3. Add methodology section
    # 4. Generate output file
    # 5. Return filepath
```

**⚠️ Problem**: 60% code duplication across modules.

**Solution Needed**: `BaseExporter` abstract class.

## πŸ”„ Data Flow

### Plan Generation Flow

```
1. User Input (Web Form)
   ↓
2. app.py: /api/generate endpoint
   ↓
3. Background task: generate_strategic_plan_background()
   ↓
4. MultiAgentOrchestrator.generate_strategic_plan()
   β”œβ”€ RAG context fetch (6x parallel)
   β”œβ”€ Agent 1: STW Sportivi β†’ Gemini API
   β”œβ”€ Agent 2: STW Strutturali β†’ Gemini API
   β”œβ”€ Agent 3: STW Marketing β†’ Gemini API
   β”œβ”€ Agent 4: STW Sociali β†’ Gemini API
   β”œβ”€ Agent 5: Financial β†’ Gemini API
   └─ Agent 6: Coordinator β†’ Gemini API (synthesizes)
   ↓
5. Post-Production Editor (cleanup, validation)
   ↓
6. Knowledge Store: save plan + upload to File Search
   ↓
7. Export Pipeline: Generate PDF, DOCX, OnePager
   ↓
8. Response: URLs to download files
```

**Timing** (current):
- Steps 1-3: ~2s
- Step 4 (Generation): ~60s ⚠️ (should be 15-20s)
- Steps 5-7: ~15s
- **Total**: ~77s

## 🧩 Key Design Patterns

### 1. Multi-Agent Coordination

**Pattern**: Specialized agents with single responsibility.

**Benefits**:
- βœ… Modular: Each agent can be improved independently
- βœ… Testable: Can test individual agents
- βœ… Scalable: Add new agents without touching existing

**Trade-offs**:
- ⚠️ Sequential execution slow (60s)
- ⚠️ Coordinator depends on all agents completing

### 2. RAG Learning Loop

**Pattern**: Every generated plan becomes training data.

```python
# After generation
knowledge_store.save_plan(plan_id, club_data, plan_content)
knowledge_store.upload_to_file_search(plan_id)

# Before next generation
rag_context = knowledge_store.get_context_for_generation(category, section)
# Agent sees examples from similar clubs
```

**Benefits**:
- βœ… Quality improves over time
- βœ… Consistent tone and structure
- βœ… Learns domain-specific patterns

### 3. AI Cache Layer

**Pattern**: File-based cache keyed by prompt MD5 hash.

```python
class AICache:
    def get(self, prompt: str) -> Optional[str]:
        cache_path = cache_dir / f"{md5(prompt)}.txt"
        if cache_path.exists():
            return cache_path.read_text()
        return None

    def set(self, prompt: str, response: str):
        cache_path = cache_dir / f"{md5(prompt)}.txt"
        cache_path.write_text(response)
```

**Benefits**:
- βœ… Reduces API calls (~30% hit rate)
- βœ… Faster iteration during development
- βœ… Cost savings

**Trade-offs**:
- ⚠️ Cache invalidation manual
- ⚠️ No TTL (time to live)
- ⚠️ Not distributed (single machine only)

### 4. Data Sourcing (3-Tier System)

**Pattern**: Transparent data provenance.

```python
# Tier 1: Real data (highest trust)
if club_data.get(f"{field}_source") == "questionnaire":
    value = club_data[field]
    source = "(fonte: questionario)"

# Tier 2: Web research
elif research_data.get(field):
    value = research_data[field]
    source = "(fonte: ricerca web)"

# Tier 3: AI estimation (lowest trust)
else:
    value = estimate_from_benchmarks(field, category)
    source = "(fonte: stima AI)"
```

**Output**: Every data point tagged with source badge.

## 🚨 Known Gotchas

### 1. Gemini API Quirks

**Issue**: Tool declarations must match exact API schema.

```python
# ❌ WRONG (causes "Unknown field" error)
tool = genai.protos.Tool(google_search={})

# βœ… CORRECT (no tools, or use supported tools only)
model = genai.GenerativeModel(MODEL_CONFIG.name)
```

**Fix Applied**: Removed `google_search` tool in v5.5.

### 2. WeasyPrint Performance

**Issue**: PDF generation slow (~10-30s per document).

```python
# Slow operation
weasyprint.HTML(string=html_content).write_pdf(filepath)
```

**Workarounds**:
- Use simpler CSS (avoid heavy calc())
- Optimize images (compress before embedding)
- Consider alternative: `wkhtmltopdf` or `puppeteer`

### 3. SQLite Locking

**Issue**: Concurrent writes block.

```python
# Multiple agents writing logs simultaneously β†’ lock
logger.info("Agent started")  # Writes to DB-backed log
```

**Solution**: Enable WAL mode (OPT-001).

```sql
PRAGMA journal_mode=WAL;
```

### 4. Context Window Limits

**Issue**: Prompts can exceed Gemini's 200k token limit.

**Current Mitigation**:
- Truncate RAG context to 2 examples max
- Summarize research data before injection

**Better Solution**: Stratified prompts (OPT-004).

### 5. Async/Await Confusion

**Issue**: `asyncio.gather()` used but calls are synchronous.

```python
# ❌ FAKE ASYNC (no actual parallelism)
async def run_agent(role, agent):
    output = agent.generate(club_data)  # Synchronous blocking call
    return role.value, output

results = await asyncio.gather(*tasks)  # Doesn't parallelize
```

**Why it doesn't work**: `agent.generate()` calls `model.generate_content()` which is synchronous I/O.

**Fix (OPT-002)**:
```python
executor = ThreadPoolExecutor(max_workers=6)
futures = [executor.submit(agent.generate, club_data) for agent in agents]
results = [future.result() for future in as_completed(futures)]
```

## πŸ“Š Performance Characteristics

### Current Bottlenecks

| Operation | Time | Bottleneck |
|-----------|------|-----------|
| Agent generation (6 agents) | ~60s | Fake async |
| RAG context fetch (6x) | ~3-5s | No indices |
| PDF export | ~10-30s | WeasyPrint CPU-bound |
| Knowledge store save | ~2s | SQLite write locks |

### Target Performance (v6.0)

| Operation | Current | Target | Improvement |
|-----------|---------|--------|-------------|
| Agent generation | 60s | 15-20s | 66% faster |
| RAG context fetch | 5s | 1-2s | 60% faster |
| Database queries | 500ms | 50ms | 90% faster |

## πŸ” Security Patterns

### 1. License Enforcement

**Pattern**: Hardware-bound licensing.

```python
# auth_manager.py
def check_license():
    hwid = get_machine_id()
    license_data = load_license_key()

    if not validate_license(hwid, license_data):
        abort(403, "Invalid license")
```

**Trigger**: Middleware on every request.

### 2. Multi-Tenancy Isolation

**Pattern**: User-scoped data access.

```python
@login_required
def get_plan(plan_id):
    plan = db.get_plan(plan_id)

    if plan.owner_id != current_user.id:
        abort(403)  # Cannot access other users' plans

    return plan
```

### 3. Webhook Signature Validation

**Pattern**: HMAC verification for n8n webhooks.

```python
@app.route("/webhook/n8n", methods=["POST"])
def n8n_webhook():
    signature = request.headers.get("X-API-Key")

    if signature != WEBHOOK_SECRET:
        abort(401)

    # Process webhook
```

## πŸ› οΈ Development Patterns

### 1. Configuration Management

**Pattern**: Centralized config with environment overrides.

```python
# config.py
GEMINI_API_KEY = os.environ.get("GEMINI_API_KEY", "")
MODEL_CONFIG = ModelConfig(
    name="gemini-2.0-flash",
    temperature=0.7,
    max_tokens=8192
)

# Usage
from config import GEMINI_API_KEY, MODEL_CONFIG
```

### 2. Logging Standards

**Pattern**: Structured logging with context.

```python
import logging
logger = logging.getLogger(__name__)

# βœ… Good
logger.info(f"Agent {agent.spec.name} completed in {elapsed:.2f}s")
logger.error(f"Generation failed: {e}", exc_info=True)

# ❌ Bad
print("Done")  # Not logged
raise Exception(f"Error: {e}")  # No context
```

### 3. Error Handling Strategy

**Pattern**: Fail gracefully with fallbacks.

```python
try:
    response = model.generate_content(prompt)
except InvalidArgument as e:
    logger.warning(f"Tool error, retrying without tools: {e}")
    fallback_model = genai.GenerativeModel(MODEL_CONFIG.name)
    response = fallback_model.generate_content(prompt)
except Exception as e:
    logger.error(f"Generation failed: {e}")
    return {'content': 'Error generating content', 'sources': []}
```

## πŸ“š Module Dependencies

### Core Dependencies

```
agents.py
β”œβ”€ config.py (MODEL_CONFIG, GEMINI_API_KEY)
β”œβ”€ data_sourcing.py (SourcedContentGenerator)
β”œβ”€ data_estimator.py (estimate_missing_financials)
└─ knowledge_store.py (SQLiteKnowledgeStore)

app.py
β”œβ”€ agents.py (MultiAgentOrchestrator)
β”œβ”€ structured_agent.py (StructuredOrchestrator)
β”œβ”€ export_*.py (all exporters)
β”œβ”€ knowledge_store.py
β”œβ”€ auth_manager.py (login_required decorator)
└─ license_manager.py (check_license)
```

### External Dependencies (Critical)

- `google-generativeai==0.8.5` - Gemini API client
- `Flask==3.1.2` - Web framework
- `weasyprint==67.0` - PDF generation
- `python-docx==1.2.0` - DOCX generation
- `pytest==9.0.2` - Testing

## πŸ”„ Refactoring Opportunities

### High Priority

1. **Real Async** (OPT-002)
   - Replace `asyncio` with `ThreadPoolExecutor`
   - Impact: 66% generation time reduction

2. **SQLite Indexing** (OPT-001)
   - Add indices, enable WAL
   - Impact: 70% query speed improvement

3. **Export Unification** (OPT-003)
   - Create `BaseExporter` class
   - Impact: -500 LOC, easier maintenance

### Medium Priority

4. **Stratified Prompts** (OPT-004)
5. **Circuit Breaker** (OPT-005)
6. **Storage Layer Split** (OPT-006)

### Low Priority (Post-v6.0)

7. FastAPI migration
8. PostgreSQL + Redis
9. Celery task queue

---

**This document is living documentation. Update when patterns change.**

Last Updated: 2026-01-23