WhizTenderBot1.0 / offer_analyzer.py
Marek4321's picture
Update offer_analyzer.py
c1f471b verified
from typing import List, Dict, Optional
from dataclasses import dataclass
import logging
import asyncio
from deepseek_client import DeepSeekClient
from criteria_analyzer import EvaluationCriterion
@dataclass
class OfferEvaluation:
"""Reprezentuje ocen臋 oferty dla pojedynczego kryterium"""
criterion_name: str
score: float # 0-100
justification: str
key_points: List[str]
evidence: List[str] # cytaty/referencje z oferty
@dataclass
class OfferAnalysis:
"""Pe艂na analiza oferty"""
offer_id: str
evaluations: List[OfferEvaluation]
strengths: List[str]
weaknesses: List[str]
total_score: float
summary: str
recommendations: List[str]
class OfferAnalyzer:
"""
Analizuje pojedyncz膮 ofert臋 wzgl臋dem zdefiniowanych kryteri贸w.
"""
def __init__(self, api_key: str, base_url: str):
self.logger = logging.getLogger(__name__)
self.client = DeepSeekClient(api_key=api_key, base_url=base_url)
async def close(self):
"""Zamyka klienta HTTP"""
await self.client.close()
async def __aenter__(self):
"""Context manager entry"""
return self
async def __aexit__(self, exc_type, exc_val, exc_tb):
"""Context manager exit"""
await self.close()
async def analyze_offer(
self,
offer_content: str,
criteria: List[EvaluationCriterion],
brief_content: str
) -> OfferAnalysis:
"""
Przeprowadza pe艂n膮 analiz臋 oferty.
Args:
offer_content (str): Tre艣膰 oferty
criteria (List[EvaluationCriterion]): Lista kryteri贸w oceny
brief_content (str): Tre艣膰 briefu/SIWZ
Returns:
OfferAnalysis: Pe艂na analiza oferty
"""
try:
# Konwertuj kryteria na s艂owniki tylko do komunikacji z API
criteria_dicts = self._convert_criteria_to_dict(criteria)
# Analizuj ofert臋 u偶ywaj膮c DeepSeek
analysis = await self.client.analyze_offer(
offer_content,
criteria_dicts, # Przekazuj s艂owniki do API
brief_content
)
# Konwertuj wyniki na obiekty OfferEvaluation
evaluations = [
OfferEvaluation(
criterion_name=eval['criterion_name'],
score=float(eval['score']),
justification=eval['justification'],
key_points=eval['key_points'],
evidence=eval['evidence']
)
for eval in analysis['evaluations']
]
# Oblicz ca艂o艣ciowy wynik
total_score = self._calculate_total_score(evaluations, criteria) # Przekazuj obiekty EvaluationCriterion
# Utw贸rz pe艂n膮 analiz臋
return OfferAnalysis(
offer_id=str(hash(offer_content))[:8],
evaluations=evaluations,
strengths=analysis['strengths'],
weaknesses=analysis['weaknesses'],
total_score=total_score,
summary=analysis['summary'],
recommendations=analysis['recommendations']
)
except Exception as e:
self.logger.error(f"B艂膮d podczas analizy oferty: {str(e)}")
raise
async def analyze_multiple_offers(
self,
offers: Dict[str, str],
criteria: List[EvaluationCriterion],
brief_content: str
) -> Dict[str, OfferAnalysis]:
"""Analizuje wiele ofert r贸wnolegle"""
try:
# Usu艅 async with, aby klient pozosta艂 otwarty
tasks = {
offer_id: self.analyze_offer(content, criteria, brief_content)
for offer_id, content in offers.items()
}
results = await asyncio.gather(*tasks.values(), return_exceptions=True)
return {
offer_id: result
for offer_id, result in zip(tasks.keys(), results)
if not isinstance(result, Exception)
}
except Exception as e:
self.logger.error(f"B艂膮d podczas analizy wielu ofert: {str(e)}")
raise
def _calculate_total_score(
self,
evaluations: List[OfferEvaluation],
criteria: List[EvaluationCriterion]
) -> float:
"""
Oblicza ca艂o艣ciowy wynik wa偶ony.
Args:
evaluations (List[OfferEvaluation]): Lista ocen
criteria (List[EvaluationCriterion]): Lista kryteri贸w
Returns:
float: Ca艂o艣ciowy wynik (0-100)
"""
total_score = 0.0
for eval in evaluations:
# Znajd藕 odpowiednie kryterium
criterion = next(
(c for c in criteria if c.name == eval.criterion_name),
None
)
if criterion:
# Dodaj wa偶ony wynik
total_score += eval.score * (criterion.weight / 100)
return round(total_score, 2)
def _convert_criteria_to_dict(
self,
criteria: List[EvaluationCriterion]
) -> List[Dict]:
"""
Konwertuje obiekty EvaluationCriterion na format dla API.
"""
return [
{
'name': c.name,
'weight': c.weight,
'description': c.description,
'scoring_guide': c.scoring_guide
}
for c in criteria
]
async def compare_offers(
self,
analyses: Dict[str, OfferAnalysis]
) -> Dict:
"""
Por贸wnuje analizy ofert i generuje rekomendacje.
Args:
analyses (Dict[str, OfferAnalysis]): S艂ownik analiz ofert
Returns:
Dict: Por贸wnanie ofert i rekomendacje
"""
if not analyses:
return {
'error': 'Brak ofert do por贸wnania'
}
# Przygotuj dane do por贸wnania
comparison_data = {
offer_id: {
'total_score': analysis.total_score,
'strengths': analysis.strengths,
'weaknesses': analysis.weaknesses
}
for offer_id, analysis in analyses.items()
}
prompt = f"""
Por贸wnaj nast臋puj膮ce oferty i przedstaw rekomendacje:
{comparison_data}
Uwzgl臋dnij:
- R贸偶nica w ocenie
- Najlepsza oferta
- Mocne strony"
- Rekomendacje
- Uzasadnienie
- Uwagi
Zwr贸膰 analiz臋 w formacie JSON:
"""
try:
response = await self.client.analyze_text(prompt, "")
# Debugowanie: zaloguj pe艂n膮 odpowied藕
self.logger.debug(f"Odpowied藕 z API: {response}")
# Sprawd藕, czy odpowied藕 zawiera oczekiwane dane
if not isinstance(response, dict):
error_msg = "Nieprawid艂owa struktura odpowiedzi: oczekiwano s艂ownika JSON"
self.logger.error(error_msg)
return {
'error': error_msg,
'raw_response': response # Zwr贸膰 surow膮 odpowied藕 do debugowania
}
# Sprawd藕, czy odpowied藕 zawiera klucze 'ranking_ofert', 'kluczowe_roznice', 'rekomendacje'
expected_keys = {'ranking_ofert', 'kluczowe_roznice', 'rekomendacje'}
if not expected_keys.issubset(response.keys()):
error_msg = f"Nieprawid艂owa struktura odpowiedzi: brak oczekiwanych kluczy {expected_keys}"
self.logger.error(error_msg)
return {
'error': error_msg,
'raw_response': response
}
# Formatuj odpowied藕 jako Markdown
formatted_report = self._format_report(response)
# Zwr贸膰 sformatowany raport
return {
'raw_response': response, # Surowa odpowied藕 do debugowania
'formatted_report': formatted_report # Sformatowany raport
}
except Exception as e:
self.logger.error(f"B艂膮d podczas por贸wnywania ofert: {str(e)}")
return {
'error': f'Nie uda艂o si臋 por贸wna膰 ofert: {str(e)}'
}
def _format_report(self, report: Dict) -> str:
"""
Formatuje raport jako tekst Markdown.
Args:
report (Dict): Raport w formacie JSON
Returns:
str: Sformatowany raport w Markdown
"""
# Nag艂贸wek raportu
markdown = "# Por贸wnanie ofert\n\n"
# Ranking ofert
markdown += "## Ranking ofert\n"
for i, oferta in enumerate(report['ranking_ofert'], 1):
markdown += f"{i}. **{oferta['nazwa']}** (ocena: {oferta['total_score']})\n"
markdown += "\n"
# Kluczowe r贸偶nice
markdown += "## Kluczowe r贸偶nice\n"
for porownanie, szczegoly in report['kluczowe_roznice'].items():
markdown += f"### {porownanie}\n"
markdown += f"- **R贸偶nica w ocenie**: {szczegoly['roznica_w_ocenie']}\n"
markdown += f"- **Mocne strony**: {szczegoly['mocne_strony']}\n"
markdown += f"- **S艂abe strony**: {szczegoly['slabe_strony']}\n"
markdown += "\n"
# Rekomendacje
markdown += "## Rekomendacje\n"
rekomendacje = report['rekomendacje']
markdown += f"- **Najlepsza oferta**: {rekomendacje['najlepsza_oferta']}\n"
markdown += f"- **Uzasadnienie**: {rekomendacje['uzasadnienie']}\n"
markdown += f"- **Uwagi**: {rekomendacje['uwagi']}\n"
return markdown
async def clear_all_cache(self):
"""
Czy艣ci ca艂y cache analiz ofert.
"""
try:
# Implementacja czyszczenia cache
return True
except Exception as e:
self.logger.error(f"B艂膮d podczas czyszczenia cache: {str(e)}")
return False