"""
Trusty Sign - Document Analysis Service
Analyzes contracts and documents using LLM
"""
import logging
from typing import Dict, Any, List, Optional
from dataclasses import dataclass
from src.llm_core.client import IRISClient
logger = logging.getLogger(__name__)
@dataclass
class CriticalClause:
"""A critical clause found in contract"""
type: str # PENALE, SCADENZA, LIMITAZIONE_RESPONSABILITA, VESSATORIA, RINNOVO_TACITO
risk_level: str # HIGH, MEDIUM, LOW
text: str
location: str # e.g. "Punto 7.3, Pagina 8"
suggestion: str
@dataclass
class ContractAnalysis:
"""Full contract analysis result"""
document_type: str
summary: str
parties: List[Dict[str, str]]
value: Optional[str]
duration: Optional[str]
deadlines: List[Dict[str, str]]
obligations: Dict[str, List[str]]
critical_clauses: List[CriticalClause]
class DocumentService:
"""Service for document analysis using LLM"""
def __init__(self, iris_client: IRISClient):
"""
Initialize document service
Args:
iris_client: IRISClient instance for LLM calls
"""
self.iris = iris_client
async def analyze_contract(
self,
pdf_text: str,
session_id: str
) -> Dict[str, Any]:
"""
Perform full contract analysis with LLM
Args:
pdf_text: Extracted text from PDF
session_id: Session ID for conversation tracking
Returns:
Analysis dictionary with contract details
"""
try:
logger.info(f"Analyzing contract (session={session_id})")
# Truncate text if too long (max ~8000 chars for context)
truncated_text = pdf_text[:8000] if len(pdf_text) > 8000 else pdf_text
prompt = self._create_analysis_prompt(truncated_text)
analysis = await self.iris.chat(
session_id=session_id,
message=prompt,
max_tokens=1500
)
logger.info(f"Contract analysis completed (session={session_id})")
return {"summary": analysis}
except Exception as e:
logger.error(f"Error analyzing contract: {e}")
return {
"summary": f"⚠️ Errore durante l'analisi: {str(e)}",
"error": str(e)
}
async def extract_critical_clauses(
self,
pdf_text: str,
session_id: str
) -> str:
"""
Extract and analyze critical clauses from contract
Args:
pdf_text: Extracted text from PDF
session_id: Session ID for conversation tracking
Returns:
Formatted string with critical clauses analysis
"""
try:
logger.info(f"Extracting critical clauses (session={session_id})")
# Truncate text if too long
truncated_text = pdf_text[:8000] if len(pdf_text) > 8000 else pdf_text
prompt = self._create_clauses_prompt(truncated_text)
clauses = await self.iris.chat(
session_id=session_id,
message=prompt,
max_tokens=2000
)
logger.info(f"Critical clauses extracted (session={session_id})")
return clauses
except Exception as e:
logger.error(f"Error extracting clauses: {e}")
return f"⚠️ Errore durante l'estrazione delle clausole: {str(e)}"
async def analyze_document_full(
self,
pdf_text: str,
session_id: str
) -> str:
"""
Perform complete document analysis (summary + critical clauses)
Args:
pdf_text: Extracted text from PDF
session_id: Session ID
Returns:
Complete analysis as formatted string
"""
try:
# Get contract analysis
analysis = await self.analyze_contract(pdf_text, session_id)
# Get critical clauses
clauses = await self.extract_critical_clauses(pdf_text, session_id)
# Combine results
full_analysis = f"""
📄 **ANALISI COMPLETATA**
{analysis.get('summary', 'Analisi non disponibile')}
---
🔍 **CLAUSOLE CRITICHE**
{clauses}
---
💬 Vuoi procedere con la firma o preferisci approfondire qualcosa?
"""
return full_analysis
except Exception as e:
logger.error(f"Error in full analysis: {e}")
return f"⚠️ Errore durante l'analisi completa: {str(e)}"
def _create_analysis_prompt(self, pdf_text: str) -> str:
"""Create prompt for contract analysis"""
return f"""
Sei un esperto legale. Analizza questo contratto e fornisci un'analisi strutturata.
**ISTRUZIONI**:
1. Leggi attentamente il testo
2. Identifica il tipo di contratto
3. Estrai informazioni chiave
4. Fornisci summary esecutivo
**FORMATO RISPOSTA**:
📊 **TIPO CONTRATTO**: [tipo contratto]
📝 **SUMMARY** (max 100 parole):
[descrizione breve di cosa prevede il contratto]
💼 **PARTI COINVOLTE**:
- Cliente: [nome]
- Fornitore/Controparte: [nome]
💰 **VALORE**: [importo se presente]
📅 **DURATA**: [periodo di validità]
⚠️ **SCADENZE IMPORTANTI**:
- [data]: [cosa scade/avviene]
✅ **OBBLIGHI PRINCIPALI**:
1. Cliente deve: [obblighi]
2. Fornitore deve: [obblighi]
**TESTO CONTRATTO**:
{pdf_text}
"""
def _create_clauses_prompt(self, pdf_text: str) -> str:
"""Create prompt for critical clauses extraction"""
return f"""
Analizza il seguente contratto e identifica CLAUSOLE CRITICHE che richiedono particolare attenzione.
**CERCA QUESTE TIPOLOGIE**:
1. 💰 **PENALI**: Clausole con sanzioni economiche (es: "penale", "multa", "risarcimento")
2. ⏰ **SCADENZE PERENTORIE**: Date improrogabili (es: "termine perentorio", "entro e non oltre")
3. 🚫 **LIMITAZIONI RESPONSABILITÀ**: Cap su responsabilità (es: "limitata a", "massimo", "non superiore")
4. ⚖️ **CLAUSOLE VESSATORIE**: Sbilanciamento contrattuale (es: "a insindacabile giudizio", "senza preavviso")
5. 📜 **RINNOVO TACITO**: Rinnovi automatici (es: "tacito rinnovo", "automaticamente rinnovato")
**FORMATO RISPOSTA**:
Per ogni clausola trovata, usa questo formato:
⚠️ **[TIPO CLAUSOLA]** - Punto [X.Y]
📍 Testo: "[citazione esatta dalla clausola]"
🎯 Rischio: [🔴 ALTO / 🟡 MEDIO / 🟢 BASSO]
💡 Suggerimento: [cosa fare - es: "Valuta negoziazione", "Imposta reminder", etc.]
---
**ESEMPI**:
⚠️ **PENALE** - Punto 7.3
📍 Testo: "In caso di ritardo superiore a 30 giorni, il Fornitore riconoscerà una penale pari al 15% del valore contrattuale"
🎯 Rischio: 🔴 ALTO
💡 Suggerimento: Penale del 15% è superiore alla media di mercato (8-10%). Valuta negoziazione per ridurre al 10% o inserire cap massimo.
⚠️ **SCADENZA PERENTORIA** - Punto 12.1
📍 Testo: "Il preavviso di recesso deve essere comunicato con almeno 90 giorni di anticipo rispetto alla scadenza"
🎯 Rischio: 🟡 MEDIO
💡 Suggerimento: Imposta reminder 120 giorni prima della scadenza contratto se intendi recedere.
---
**ATTENZIONE**:
- Rispondi SOLO con le clausole trovate nel formato indicato
- Se non trovi clausole critiche, scrivi "✅ Nessuna clausola particolarmente critica rilevata"
- Non inventare clausole che non ci sono
- Sii preciso nelle citazioni
**TESTO CONTRATTO**:
{pdf_text}
"""