/
obuxxxov
/
CodeForge
Обзор
Документация
Войти
/
obuxxxov
/
CodeForge
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
comb
analyzer/git_analyzer.py
905 строк
40 KB
ObliInSe
start
18 авг 2025, 00:31
18 авг 2025, 00:31
0671f4e
Код
Авторство
О чём код?
# orchestrator_gigachat.py from __future__ import annotations import os import json from typing import List, Dict, Set from dataclasses import dataclass, field import sys import re from dotenv import load_dotenv from pydantic import BaseModel, Field, ValidationError from langchain.schema import StrOutputParser from langchain.prompts import ChatPromptTemplate from langchain_core.output_parsers import JsonOutputParser from langchain_gigachat import GigaChat # pip install langchain-gigachat # Импорты для Excel try: import pandas as pd from openpyxl import Workbook from openpyxl.styles import Font, PatternFill, Alignment EXCEL_AVAILABLE = True except ImportError: print("Для экспорта в Excel установите: pip install pandas openpyxl") EXCEL_AVAILABLE = False # ------------------- ENV ------------------- load_dotenv() GIGACHAT_TOKEN = os.getenv("GIGACHAT_CREDENTIALS", "") if not GIGACHAT_TOKEN: raise RuntimeError("Добавь GIGACHAT_CREDENTIALS в .env или окружение!") # ------------------- МОДЕЛЬ GigaChat ------------------- llm = GigaChat( credentials=GIGACHAT_TOKEN, model="GigaChat-Pro", verify_ssl_certs=False, timeout=60 ) # ------------------- ЗАГРУЗКА КОМПЕТЕНЦИЙ ИЗ EXCEL ------------------- def load_competencies_from_excel(file_path: str = "Матрица компетенций Python.xlsx") -> Dict[str, str]: """ Загружает компетенции из Excel файла из столбцов 'навык' и 'описание' Args: file_path: Путь к Excel файлу Returns: Словарь {навык: описание} """ if not EXCEL_AVAILABLE: print("Excel недоступен. Используем базовые компетенции") return { "ООП": "Классы, наследование, полиморфизм, SOLID.", "Чистый код / PEP8": "Нэйминг, форматирование, функции и модули, мёртвый код.", "Работа с типами": "type hints, mypy, явные типы, Optional/Union.", "Алгоритмы и сложность": "Сложность по времени/памяти, оптимизации.", "Работа с данными / Pandas": "DataFrame API, векторизация, избегание анти-паттернов.", } try: # Пробуем разные варианты путей к файлу possible_paths = [ file_path, os.path.join(os.path.dirname(__file__), file_path), os.path.join(os.getcwd(), file_path), "full_ver1/Матрица компетенций/Матрица компетенций Python.xlsx" ] excel_file = None for path in possible_paths: if os.path.exists(path): excel_file = path break if not excel_file: print(f"Файл '{file_path}' не найден. Используем базовые компетенции") return { "ООП": "Классы, наследование, полиморфизм, SOLID.", "Чистый код / PEP8": "Нэйминг, форматирование, функции и модули, мёртвый код.", "Работа с типами": "type hints, mypy, явные типы, Optional/Union.", "Алгоритмы и сложность": "Сложность по времени/памяти, оптимизации.", "Работа с данными / Pandas": "DataFrame API, векторизация, избегание анти-паттернов.", } print(f" Загружаем компетенции из: {excel_file}") # Читаем Excel файл df = pd.read_excel(excel_file) # Проверяем наличие нужных столбцов required_columns = ['Навык', 'Описание'] missing_columns = [col for col in required_columns if col not in df.columns] if missing_columns: print(f"В файле отсутствуют столбцы: {missing_columns}") print(f"Доступные столбцы: {list(df.columns)}") return { "ООП": "Классы, наследование, полиморфизм, SOLID.", "Чистый код / PEP8": "Нэйминг, форматирование, функции и модули, мёртвый код.", "Работа с типами": "type hints, mypy, явные типы, Optional/Union.", "Алгоритмы и сложность": "Сложность по времени/памяти, оптимизации.", "Работа с данными / Pandas": "DataFrame API, векторизация, избегание анти-паттернов.", } # Создаем словарь компетенций competencies = {} for _, row in df.iterrows(): skill = str(row['Навык']).strip() description = str(row['Описание']).strip() # Пропускаем пустые строки if skill and skill != 'nan' and description and description != 'nan': competencies[skill] = description print(f"Загружено {len(competencies)} компетенций из Excel") # Показываем первые несколько компетенций для проверки print("Примеры загруженных компетенций:") for i, (skill, desc) in enumerate(list(competencies.items())[:3]): print(f" {i+1}. {skill}: {desc[:50]}...") return competencies except Exception as e: print(f"Ошибка загрузки Excel файла: {e}") print("Используем базовые компетенции") return { "ООП": "Классы, наследование, полиморфизм, SOLID.", "Чистый код / PEP8": "Нэйминг, форматирование, функции и модули, мёртвый код.", "Работа с типами": "type hints, mypy, явные типы, Optional/Union.", "Алгоритмы и сложность": "Сложность по времени/памяти, оптимизации.", "Работа с данными / Pandas": "DataFrame API, векторизация, избегание анти-паттернов.", } # ------------------- СПИСОК КОМПЕТЕНЦИЙ ------------------- COMPETENCY_DEFINITIONS = load_competencies_from_excel() # ------------------- МОДЕЛИ ДАННЫХ ------------------- class RouterDecision(BaseModel): competencies_initial: List[str] rationale: str class AgentResult(BaseModel): competency: str score: float = Field(..., ge=0, le=10) summary: str key_findings: List[str] = [] evidence_snippets: List[str] = [] class CompetencyAnalyzerResult(BaseModel): competency: str suggested_competencies: List[str] = [] rationale: str class OrchestratorReport(BaseModel): pr_title: str pr_url: str considered_competencies: List[str] results: List[AgentResult] overall_comment: str @dataclass class PRFileChange: path: str status: str diff: str @dataclass class PullRequestBundle: title: str description: str url: str files: List[PRFileChange] = field(default_factory=list) def short_payload(self, max_chars: int = None) -> str: blocks = [ f"# PR: {self.title}", f"URL: {self.url}", "## Description:", self.description.strip() or "—", "## Files:" ] for f in self.files: blocks.append(f"\n### {f.path} [{f.status}]\n{f.diff.strip()}") text = "\n".join(blocks) return text if max_chars is None else text[:max_chars] # ------------------- ЗАГРУЗКА КОММИТОВ ИЗ REPORT.JSON ------------------- def load_commit_from_report(report_path: str, commit_key: str) -> tuple: """Загружает коммит из report.json по ключу (first_commit, second_commit, etc.) Возвращает: (pr_for_router, pr_for_agents, mode)""" try: with open(report_path, "r", encoding="utf-8") as f: data = json.load(f) except Exception as e: raise RuntimeError(f"Не удалось прочитать файл '{report_path}': {e}") # Найти коммит по ключу commit_data = data.get(commit_key) if not commit_data: raise RuntimeError(f"Коммит '{commit_key}' не найден в {report_path}") # Извлечь основную информацию code = commit_data.get("code", "") pull_message = commit_data.get("pull message", "") review_comment = commit_data.get("review comment", "") code_comments = commit_data.get("code_comments", []) # Определяем что анализирует РОУТЕР has_pull_message = bool(pull_message.strip()) has_review_comment = bool(review_comment.strip()) title = f"Commit Analysis: {commit_key}" url = f"local://report.json#{commit_key}" # PR для роутера (анализирует сообщения или код) if has_pull_message or has_review_comment: # Роутер анализирует pull message и review comment router_parts = [] if has_pull_message: router_parts.append(f"Pull Message: {pull_message}") if has_review_comment: router_parts.append(f"Review Comment: {review_comment}") if code_comments: router_parts.append(f"Code Comments: {'; '.join(code_comments)}") router_content = "\n\n".join(router_parts) router_files = [ PRFileChange( path="review_feedback.md", status="review", diff=router_content[:8000] ) ] mode = "Режим: роутер анализирует pull message и review comment, агенты анализируют код" else: # Роутер анализирует код router_content = f"Code Analysis\n\n{code}" router_files = [ PRFileChange( path="code.py", status="code", diff=code[:8000] ) ] mode = "Режим: роутер и агенты анализируют код" # PR для агентов (ВСЕГДА анализируют код) agent_content = f"Code Analysis\n\n{code}" agent_files = [ PRFileChange( path="code.py", status="code", diff=code[:8000] ) ] pr_for_router = PullRequestBundle( title=title, description=router_content[:12000], url=url, files=router_files ) pr_for_agents = PullRequestBundle( title=title, description=agent_content[:12000], url=url, files=agent_files ) return pr_for_router, pr_for_agents, mode def list_available_commits(report_path: str) -> List[tuple]: """Возвращает список доступных коммитов в формате [(ключ, описание), ...]""" try: with open(report_path, "r", encoding="utf-8") as f: data = json.load(f) commits = [] for key, commit_data in data.items(): if isinstance(commit_data, dict) and "code" in commit_data: pull_message = commit_data.get("pull message", "") description = pull_message[:60] + "..." if len(pull_message) > 60 else pull_message if not description: description = "Code analysis" commits.append((key, description)) return commits except Exception as e: print(f"Ошибка чтения {report_path}: {e}") return [] # ------------------- ПРОМПТЫ ------------------- router_prompt = ChatPromptTemplate.from_template( """Ты — опытный тимлид. Определи, какие компетенции из списка релевантны для оценки изменений. Список компетенций: {competency_catalog} Правила: - Выбирай релевантные компетенции. Сколько именно — зависит от контекста (обычно 1–3), но не выбирай нерелевантные. - Верни JSON строго по схеме. Никакого Markdown, текста до или после JSON, бэктиков и комментариев. - Используй только двойные кавычки в ключах и строках. Массив competencies_initial должен содержать от 1 до нескольких элементов (ровно те, что действительно нужны). PR (усечённо): {pr_text} Схема: {{ "competencies_initial": ["...", "...", "..."], "rationale": "..." }}""" ) # Агент-оценщик: анализирует код по конкретной компетенции evaluator_prompt = ChatPromptTemplate.from_template( """Ты — эксперт по компетенции "{competency}". Критерии: {criteria} Проанализируй код и выставь оценку от 0 до 10 по данной компетенции. Ответь ТОЛЬКО валидным JSON без какого-либо дополнительного текста, без Markdown и без бэктиков. JSON схема: {{ "competency": "{competency}", "score": 0..10, "summary": "...", "key_findings": ["...", "..."], "evidence_snippets": ["...", "..."] }} PR (усечённо): {pr_text} """) # Агент-анализатор компетенций: определяет какие ещё компетенции нужно проверить competency_analyzer_prompt = ChatPromptTemplate.from_template( """Ты — эксперт по анализу кода. Твоя задача — определить, какие компетенции из списка нужно проверить для данного кода. Список доступных компетенций: {competency_catalog} Текущая компетенция: "{competency}" Проанализируй код и определи, какие ещё компетенции (кроме текущей) нужно проверить. Выбирай только те компетенции, которые действительно релевантны для данного кода. Ответь ТОЛЬКО валидным JSON без какого-либо дополнительного текста, без Markdown и без бэктиков. JSON схема: {{ "competency": "{competency}", "suggested_competencies": ["...", "..."], "rationale": "..." }} PR (усечённо): {pr_text} """) # ------------------- ОРКЕСТРАТОР ------------------- class Orchestrator: def __init__(self, llm, competency_defs: Dict[str, str], max_rounds: int = 3): self.llm = llm self.competency_defs = competency_defs self.max_rounds = max_rounds self.router_parser = JsonOutputParser(pydantic_object=RouterDecision) self.evaluator_parser = JsonOutputParser(pydantic_object=AgentResult) self.analyzer_parser = JsonOutputParser(pydantic_object=CompetencyAnalyzerResult) def _clean_json_response(self, raw_text: str) -> str: """Предочистка JSON ответа от проблематичных символов""" import re # Убираем markdown блоки raw_text = re.sub(r'```json\s*', '', raw_text) raw_text = re.sub(r'```\s*', '', raw_text) # Убираем все до первой { start_idx = raw_text.find('{') if start_idx == -1: return raw_text # Найдем корректную закрывающую скобку, считая вложенность brace_count = 0 end_idx = start_idx for i in range(start_idx, len(raw_text)): char = raw_text[i] if char == '{': brace_count += 1 elif char == '}': brace_count -= 1 if brace_count == 0: end_idx = i break if brace_count != 0: # Если скобки не сбалансированы, используем последнюю } end_idx = raw_text.rfind('}') if end_idx == -1: return raw_text json_part = raw_text[start_idx:end_idx+1] # Исправляем проблематичные экранирования в строках # Заменяем \\` на ` json_part = re.sub(r'\\\\`', '`', json_part) # Заменяем \\) на ) json_part = re.sub(r'\\\\\\)', ')', json_part) # Заменяем \\{ на { json_part = re.sub(r'\\\\\\{', '{', json_part) # Заменяем \\$ на $ json_part = re.sub(r'\\\\\\$', '$', json_part) # Заменяем тройные слеши на одинарные json_part = re.sub(r'\\\\\\/', '/', json_part) # Убираем многоточия в конце строк (они нарушают JSON) json_part = re.sub(r'\.\.\.(?=\s*[",}])', '', json_part) # Убираем лишние пробелы и переносы json_part = re.sub(r'\s+', ' ', json_part) json_part = json_part.strip() return json_part def _route(self, pr: PullRequestBundle) -> RouterDecision: catalog = "\n".join([f"- {k}: {v}" for k, v in self.competency_defs.items()]) chain = router_prompt | self.llm | StrOutputParser() raw = chain.invoke({ "competency_catalog": catalog, "pr_text": pr.short_payload(max_chars=None) }) try: cleaned_raw = self._clean_json_response(raw) parsed_data = self.router_parser.parse(cleaned_raw) # Преобразуем словарь в объект RouterDecision if isinstance(parsed_data, dict): return RouterDecision(**parsed_data) if isinstance(parsed_data, RouterDecision): return parsed_data raise ValueError("Invalid router JSON structure") except Exception as e: # Попробуем извлечь JSON из текста вручную с разными стратегиями import re import json as json_lib # Стратегия 1: ищем полный JSON объект json_patterns = [ r'\{[^{}]*"competencies_initial"[^{}]*\}', r'\{.*?"competencies_initial".*?\}', r'\{"competencies_initial":\s*\[[^\]]*\][^}]*\}' ] for pattern in json_patterns: json_match = re.search(pattern, raw, re.DOTALL) if json_match: try: cleaned_json = self._clean_json_response(json_match.group()) parsed_data = json_lib.loads(cleaned_json) return RouterDecision(**parsed_data) except: continue # Стратегия 2: извлекаем компетенции и rationale отдельно try: comp_match = re.search(r'"competencies_initial":\s*\[([^\]]+)\]', raw) rationale_match = re.search(r'"rationale":\s*"([^"]+)"', raw) if comp_match: comp_str = comp_match.group(1) # Извлекаем названия компетенций из строки competencies = re.findall(r'"([^"]+)"', comp_str) rationale = rationale_match.group(1) if rationale_match else "Автоматически извлечено" return RouterDecision( competencies_initial=competencies[:1], # Берем только первую rationale=rationale ) except: pass print(f"Ошибка парсинга роутера: {e}") print(f"Сырой ответ: {raw[:200]}...") # Возвращаем базовые компетенции return RouterDecision( competencies_initial=["Владение Python"], rationale="Ошибка парсинга, использую базовые компетенции" ) def _run_evaluator(self, pr: PullRequestBundle, competency: str) -> AgentResult: """Запускает агента-оценщика для анализа компетенции""" criteria = self.competency_defs[competency] chain = evaluator_prompt | self.llm | StrOutputParser() raw = chain.invoke({ "competency": competency, "criteria": criteria, "pr_text": pr.short_payload(max_chars=None) }) try: cleaned_raw = self._clean_json_response(raw) parsed_data = self.evaluator_parser.parse(cleaned_raw) if isinstance(parsed_data, dict): res = AgentResult(**parsed_data) elif isinstance(parsed_data, AgentResult): res = parsed_data else: raise ValueError("Invalid evaluator JSON structure") res.competency = competency # Отладочная информация print(f"Основной парсинг для '{competency}': score={res.score}, key_findings={res.key_findings}") return res except Exception as e: # Попробуем извлечь JSON из текста вручную с разными стратегиями import re import json as json_lib # Стратегия 1: ищем полный JSON объект json_patterns = [ r'\{[^{}]*"competency"[^{}]*\}', r'\{.*?"competency".*?\}', r'\{"competency":\s*"[^"]*"[^}]*\}' ] for pattern in json_patterns: json_match = re.search(pattern, raw, re.DOTALL) if json_match: try: cleaned_json = self._clean_json_response(json_match.group()) parsed_data = json_lib.loads(cleaned_json) result = AgentResult(**parsed_data) result.competency = competency return result except: continue # Стратегия 2: извлекаем поля отдельно try: score_match = re.search(r'"score":\s*(\d+(?:\.\d+)?)', raw) summary_match = re.search(r'"summary":\s*"([^"]+)"', raw) key_findings_match = re.search(r'"key_findings":\s*\[([^\]]+)\]', raw) score = float(score_match.group(1)) if score_match else 0.0 summary = summary_match.group(1) if summary_match else "Частично извлечено из ответа" # Извлекаем key_findings key_findings = [] if key_findings_match: findings_str = key_findings_match.group(1) # Извлекаем строки из массива findings = re.findall(r'"([^"]+)"', findings_str) key_findings = findings print(f"Отладка для '{competency}': score={score}, key_findings={key_findings}") return AgentResult( competency=competency, score=score, summary=summary, key_findings=key_findings, evidence_snippets=[] ) except: pass print(f"Ошибка парсинга оценщика для '{competency}': {e}") print(f"Сырой ответ: {raw[:200]}...") return AgentResult( competency=competency, score=0.0, summary="Ошибка парсинга оценщика", key_findings=[], evidence_snippets=[] ) def _run_competency_analyzer(self, pr: PullRequestBundle, competency: str) -> CompetencyAnalyzerResult: """Запускает агента-анализатора для определения дополнительных компетенций""" chain = competency_analyzer_prompt | self.llm | StrOutputParser() raw = chain.invoke({ "competency": competency, "competency_catalog": "\n".join([f"- {k}: {v}" for k, v in self.competency_defs.items()]), "pr_text": pr.short_payload(max_chars=None) }) try: cleaned_raw = self._clean_json_response(raw) parsed_data = self.analyzer_parser.parse(cleaned_raw) if isinstance(parsed_data, dict): res = CompetencyAnalyzerResult(**parsed_data) elif isinstance(parsed_data, CompetencyAnalyzerResult): res = parsed_data else: raise ValueError("Invalid analyzer JSON structure") res.competency = competency return res except Exception as e: # Попробуем извлечь JSON из текста вручную с разными стратегиями import re import json as json_lib # Стратегия 1: ищем полный JSON объект json_patterns = [ r'\{[^{}]*"competency"[^{}]*\}', r'\{.*?"competency".*?\}', r'\{"competency":\s*"[^"]*"[^}]*\}' ] for pattern in json_patterns: json_match = re.search(pattern, raw, re.DOTALL) if json_match: try: cleaned_json = self._clean_json_response(json_match.group()) parsed_data = json_lib.loads(cleaned_json) result = CompetencyAnalyzerResult(**parsed_data) result.competency = competency return result except: continue # Стратегия 2: извлекаем поля отдельно try: suggested_match = re.search(r'"suggested_competencies":\s*\[([^\]]+)\]', raw) rationale_match = re.search(r'"rationale":\s*"([^"]+)"', raw) if suggested_match: suggested_str = suggested_match.group(1) # Извлекаем названия компетенций из строки suggested_competencies = re.findall(r'"([^"]+)"', suggested_str) rationale = rationale_match.group(1) if rationale_match else "Частично извлечено из ответа" return CompetencyAnalyzerResult( competency=competency, suggested_competencies=suggested_competencies, rationale=rationale ) except: pass print(f"Ошибка парсинга анализатора для '{competency}': {e}") print(f"Сырой ответ: {raw[:200]}...") return CompetencyAnalyzerResult( competency=competency, suggested_competencies=[], rationale="Ошибка парсинга анализатора" ) def evaluate_pr(self, pr_router: PullRequestBundle, pr_agents: PullRequestBundle = None) -> OrchestratorReport: # Если pr_agents не передан, используем pr_router для агентов тоже if pr_agents is None: pr_agents = pr_router decision = self._route(pr_router) # Роутер анализирует pr_router planned = [c for c in decision.competencies_initial if c in self.competency_defs] if not planned: planned = ["Владение Python"] # Не ограничиваем: запускаем все, что выбрал роутер (обычно 1–3), дальше расширяем через suggest_more considered: Set[str] = set() results: List[AgentResult] = [] agent_chain = [] # Для отслеживания цепочки назначений print(f" Роутер назначил: {', '.join(planned)}") agent_chain.append(f"Роутер => {', '.join(planned)}") round_num = 1 for _ in range(self.max_rounds): to_run = [c for c in planned if c not in considered] if not to_run: break if round_num > 1: print(f" Раунд {round_num}: запуск {', '.join(to_run)}") round_suggestions = {} # Кто что предложил в этом раунде for comp in to_run: considered.add(comp) # Запускаем ДВА агента для каждой компетенции # 1. Агент-оценщик: анализирует и выставляет оценку evaluator_result = self._run_evaluator(pr_agents, comp) results.append(evaluator_result) # 2. Агент-анализатор: определяет дополнительные компетенции analyzer_result = self._run_competency_analyzer(pr_agents, comp) # Показываем только ключевую информацию print(f" {comp}: {evaluator_result.score}/10") # Показываем рекомендации анализатора if analyzer_result.suggested_competencies: valid_suggestions = [s for s in analyzer_result.suggested_competencies if s in self.competency_defs and s not in considered] if valid_suggestions: round_suggestions[comp] = valid_suggestions print(f" {comp} порекомендовал: {', '.join(valid_suggestions)}") # Собираем все новые предложения extras = set() for suggestions in round_suggestions.values(): extras.update(suggestions) if not extras: break # Записываем в цепочку кто что предложил for agent, suggestions in round_suggestions.items(): agent_chain.append(f"{agent} → {', '.join(suggestions)}") planned.extend(sorted(extras)) round_num += 1 # Выводим итоговую цепочку print(f" Цепочка агентов: {' | '.join(agent_chain)}") return OrchestratorReport( pr_title=pr_router.title, pr_url=pr_router.url, considered_competencies=sorted(considered), results=results, overall_comment=self._summarize_overall(results, pr_router) ) def _summarize_overall(self, results: List[AgentResult], pr: PullRequestBundle) -> str: summary_prompt = ChatPromptTemplate.from_template( """Суммируй результаты анализа PR "{title}" (JSON: {results}) Сделай 1 блок: 1) Итог (5–7 предложений)""" ) chain = summary_prompt | self.llm | StrOutputParser() return chain.invoke({ "title": pr.title, "results": json.dumps([r.model_dump() for r in results], ensure_ascii=False) }) # ------------------- ЭКСПОРТ В EXCEL ------------------- def export_to_excel(all_reports: List[OrchestratorReport], filename: str = "competency_analysis.xlsx"): """Экспортирует итоговые результаты анализа в Excel файл""" if not EXCEL_AVAILABLE: print("Excel экспорт недоступен. Установите pandas и openpyxl") return # Создаем итоговую таблицу компетенций competency_summary = {} for report in all_reports: for result in report.results: comp_name = result.competency if comp_name not in competency_summary: competency_summary[comp_name] = { "оценки": [], "проблемные_темы": set() } # Добавляем оценку competency_summary[comp_name]["оценки"].append(result.score) # Добавляем проблемные темы if result.key_findings: for finding in result.key_findings: competency_summary[comp_name]["проблемные_темы"].add(finding) # Создаем данные для Excel excel_data = [] for comp_name, data in competency_summary.items(): # Вычисляем среднюю оценку avg_score = sum(data["оценки"]) / len(data["оценки"]) # Формируем строку проблемных тем if data["проблемные_темы"]: key_findings_str = "; ".join(data["проблемные_темы"]) else: key_findings_str = "0" excel_data.append({ "Навык": comp_name, "Оценка": round(avg_score, 1), "Проблемные темы": key_findings_str }) # Сортируем по оценке (от низкой к высокой) excel_data.sort(key=lambda x: x["Оценка"]) # Создаем DataFrame df = pd.DataFrame(excel_data) # display(df) df = pd.read_excel('Матрица компетенций Python.xlsx')[['Блок', 'Навык']].merge(df, on='Навык', how='left') # Экспортируем в Excel с форматированием with pd.ExcelWriter(filename, engine='openpyxl') as writer: df.to_excel(writer, sheet_name='Итоговый анализ', index=False) # Получаем рабочую книгу для форматирования workbook = writer.book worksheet = writer.sheets['Итоговый анализ'] # Форматируем заголовки header_font = Font(bold=True, color="FFFFFF") header_fill = PatternFill(start_color="366092", end_color="366092", fill_type="solid") for cell in worksheet[1]: cell.font = header_font cell.fill = header_fill cell.alignment = Alignment(horizontal="center") # Автоматически подгоняем ширину столбцов for column in worksheet.columns: max_length = 0 column_letter = column[0].column_letter for cell in column: try: if len(str(cell.value)) > max_length: max_length = len(str(cell.value)) except: pass adjusted_width = min(max_length + 2, 50) worksheet.column_dimensions[column_letter].width = adjusted_width print(f"Итоговые результаты экспортированы в {filename}") print(f"Всего компетенций: {len(excel_data)}") # ------------------- ЗАПУСК ------------------- if __name__ == "__main__": import argparse parser = argparse.ArgumentParser(description="Анализ компетенций в коде") parser.add_argument("--excel", "-e", type=str, default="Матрица компетенций Python.xlsx", help="Путь к Excel файлу с компетенциями (по умолчанию: Матрица компетенций Python.xlsx)") parser.add_argument("--report", "-r", type=str, default="report.json", help="Путь к файлу report.json (по умолчанию: report.json)") args = parser.parse_args() # Перезагружаем компетенции с указанным файлом print(f" Используем Excel файл: {args.excel}") COMPETENCY_DEFINITIONS = load_competencies_from_excel(args.excel) report_file = args.report # Проверяем наличие файла report.json if os.path.exists(report_file): # Получаем все доступные коммиты available_commits = list_available_commits(report_file) if available_commits: print(f"Найдено {len(available_commits)} коммитов для анализа. Начинаем обработку...\n") # Создаем оркестратор один раз orch = Orchestrator(llm=llm, competency_defs=COMPETENCY_DEFINITIONS) # Обрабатываем все коммиты в цикле all_reports = [] for i, (commit_key, commit_desc) in enumerate(available_commits, 1): print(f"[{i}/{len(available_commits)}] Анализ {commit_key}") try: # Загружаем коммит pr_router, pr_agents, mode = load_commit_from_report(report_file, commit_key) # Запускаем анализ report = orch.evaluate_pr(pr_router, pr_agents) all_reports.append(report) # Краткий вывод результата scores = [f"{r.competency}: {r.score}/10" for r in report.results] print(f" Результат: {', '.join(scores)}") print() except Exception as e: print(f" Ошибка: {e}") print() continue # Итоговый отчет print("=" * 50) print("ИТОГОВЫЙ АНАЛИЗ") print("=" * 50) for report in all_reports: print(f"\n{report.pr_title}") scores = [f"{r.competency}: {r.score}/10" for r in report.results] print(f"Оценки: {', '.join(scores)}") print("-" * 30) print(f"\n Анализ завершен! Обработано {len(all_reports)} из {len(available_commits)} коммитов") # Экспортируем результаты в Excel if all_reports: export_to_excel(all_reports) else: print("В report.json не найдено коммитов для анализа")