mtornani Claude Sonnet 4.6 commited on
Commit
fd70dfd
Β·
1 Parent(s): 3e460dc

fix: executive report roadmap truncation and markdown leaking

Browse files

- executive_report.py: _extract_timeline_from_plan now strips **markdown**
via _clean_text() on title+desc, title capture expanded 80β†’200 chars,
added table-format detection for pipe-separated AI output
- agents.py: STW Strutturali prompt explicitly forbids MACRO 3/4+
- config.py: promote meta-llama/Llama-3.3-70B-Instruct to primary in
HF_MODEL_CHAIN (confirmed working on together provider)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

Files changed (3) hide show
  1. agents.py +3 -1
  2. config.py +2 -2
  3. executive_report.py +14 -4
agents.py CHANGED
@@ -644,7 +644,9 @@ Sei l'ANALISTA AREA STRUTTURALE STW. Redigi la sezione OBIETTIVI STRUTTURALI sec
644
  - Budget: [stima]
645
  - Beneficiari: [target]
646
 
647
- **REGOLE:**
 
 
648
  - Costi SEMPRE come range con fonte (es. "€50.000-80.000 - benchmark club simili")
649
  - Dati mancanti: `(dato da acquisire)`
650
  - Voce istituzionale
 
644
  - Budget: [stima]
645
  - Beneficiari: [target]
646
 
647
+ **REGOLE ASSOLUTE:**
648
+ - Questa sezione ha ESATTAMENTE 2 MACRO (MACRO 1 e MACRO 2). NON aggiungere MACRO 3, MACRO 4 o altri obiettivi macro.
649
+ - Finanziario e marketing NON appartengono a questa sezione.
650
  - Costi SEMPRE come range con fonte (es. "€50.000-80.000 - benchmark club simili")
651
  - Dati mancanti: `(dato da acquisire)`
652
  - Voce istituzionale
config.py CHANGED
@@ -136,8 +136,8 @@ OPENROUTER_API_KEY = os.environ.get("OPENROUTER_API_KEY", "")
136
  HF_TOKEN = os.environ.get("HF_TOKEN", "")
137
  HF_MODEL = os.environ.get("HF_MODEL", "Qwen/Qwen2.5-72B-Instruct")
138
  HF_MODEL_CHAIN = [
139
- "Qwen/Qwen2.5-72B-Instruct", # primary: 72B, multilingual, high quality
140
- "meta-llama/Llama-3.3-70B-Instruct", # 70B fallback, multilingual
141
  "Qwen/Qwen2.5-32B-Instruct", # 32B fallback, fast
142
  "mistralai/Mistral-Nemo-Instruct-2407", # 12B, lightweight fallback
143
  "Qwen/Qwen2.5-7B-Instruct", # 7B, last resort
 
136
  HF_TOKEN = os.environ.get("HF_TOKEN", "")
137
  HF_MODEL = os.environ.get("HF_MODEL", "Qwen/Qwen2.5-72B-Instruct")
138
  HF_MODEL_CHAIN = [
139
+ "meta-llama/Llama-3.3-70B-Instruct", # primary: confirmed working on together
140
+ "Qwen/Qwen2.5-72B-Instruct", # 72B fallback, multilingual
141
  "Qwen/Qwen2.5-32B-Instruct", # 32B fallback, fast
142
  "mistralai/Mistral-Nemo-Instruct-2407", # 12B, lightweight fallback
143
  "Qwen/Qwen2.5-7B-Instruct", # 7B, last resort
executive_report.py CHANGED
@@ -377,21 +377,31 @@ def _extract_timeline_from_plan(plan_data: Dict) -> list:
377
  for key in ['roadmap', 'piano_triennale', 'strategic_roadmap', 'timeline']:
378
  content = plan_data.get(key, '')
379
  if content and len(content) > 100:
 
 
 
 
 
 
 
 
 
380
  blocks = re.findall(
381
- r'(?:Anno|Year)\s*(\d)[:\s\-–—]*([^\n]{5,80})(?:\n([^\n]{20,250}))?',
382
  content, re.IGNORECASE
383
  )
384
  if len(blocks) >= 2:
385
- return [(f'Y{b[0]}', b[1].strip(), (b[2] or '').strip()[:200]) for b in blocks[:3]]
 
386
  # Fallback: cerca nell'executive summary
387
  exec_s = plan_data.get('executive_summary', '') or plan_data.get('coordinator_summary', '')
388
  if exec_s:
389
  blocks = re.findall(
390
- r'(?:Anno|Triennio|Year)\s*(\d)[:\s\-–—]*([^\n.]{10,80})',
391
  exec_s, re.IGNORECASE
392
  )
393
  if len(blocks) >= 2:
394
- return [(f'Y{b[0]}', b[1].strip(), '') for b in blocks[:3]]
395
  return []
396
 
397
 
 
377
  for key in ['roadmap', 'piano_triennale', 'strategic_roadmap', 'timeline']:
378
  content = plan_data.get(key, '')
379
  if content and len(content) > 100:
380
+ # Formato markdown table: | Anno 1 | Titolo | Desc |
381
+ table_blocks = re.findall(
382
+ r'\|\s*(?:Anno|Year)\s*(\d)[^|]*\|\s*([^|\n]{5,120})\|(?:\s*([^|\n]{5,250})\|)?',
383
+ content, re.IGNORECASE
384
+ )
385
+ if len(table_blocks) >= 2:
386
+ return [(f'Y{b[0]}', _clean_text(b[1]), _clean_text(b[2] or '')) for b in table_blocks[:3]]
387
+
388
+ # Formato standard: Anno 1: Titolo\nDescrizione
389
  blocks = re.findall(
390
+ r'(?:Anno|Year)\s*(\d)[:\s\-–—]*([^\n]{5,200})(?:\n([^\n]{20,250}))?',
391
  content, re.IGNORECASE
392
  )
393
  if len(blocks) >= 2:
394
+ return [(f'Y{b[0]}', _clean_text(b[1]), _clean_text((b[2] or '').strip())) for b in blocks[:3]]
395
+
396
  # Fallback: cerca nell'executive summary
397
  exec_s = plan_data.get('executive_summary', '') or plan_data.get('coordinator_summary', '')
398
  if exec_s:
399
  blocks = re.findall(
400
+ r'(?:Anno|Triennio|Year)\s*(\d)[:\s\-–—]*([^\n.]{10,200})',
401
  exec_s, re.IGNORECASE
402
  )
403
  if len(blocks) >= 2:
404
+ return [(f'Y{b[0]}', _clean_text(b[1]), '') for b in blocks[:3]]
405
  return []
406
 
407