Spaces:
Paused
Paused
| from typing import List, Dict, Optional | |
| from dataclasses import dataclass | |
| import logging | |
| import asyncio | |
| from deepseek_client import DeepSeekClient | |
| from criteria_analyzer import EvaluationCriterion | |
| 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 | |
| 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 |