import json import re import requests import pandas as pd from config import OLLAMA_URL, OLLAMA_MODEL from core.database import get_all_rules_from_db def ask_ollama(prompt, system_prompt=None): """ Универсальная функция отправки запросов к локальной модели Ollama (Qwen 2.5). """ payload = { "model": OLLAMA_MODEL, "prompt": f"{system_prompt}\n\n{prompt}" if system_prompt else prompt, "stream": False } try: response = requests.post(OLLAMA_URL, json=payload, timeout=120) if response.status_code == 200: data = response.json() if "response" in data: return data["response"].strip() elif "message" in data and "content" in data["message"]: return data["message"]["content"].strip() else: return f"⚠️ Неизвестная структура ответа Ollama: {data}" else: return f"⚠️ Ошибка Ollama (Код {response.status_code}): {response.text}" except requests.exceptions.ConnectionError: return "⚠️ Не удалось подключиться к Ollama. Проверьте, запущен ли сервис (ollama run qwen2.5:14b)." except Exception as e: return f"⚠️ Ошибка при обращении к Ollama: {e}" def get_system_rules_context(): """ Загружает глобальные системные правила компании прямо из Базы Данных. """ rules = get_all_rules_from_db() if not rules: return "" rules_text = "\n".join([f"- {r}" for r in rules]) return f"\nОБЯЗАТЕЛЬНЫЕ ГЛОБАЛЬНЫЕ ПРАВИЛА И ПРИОРИТЕТЫ КОМПАНИИ:\n{rules_text}\n" def analyze_scud_mass_failure_ai(df_scud): """ Оценивает процент аномалий 'ANOMALY_NO_IN_HAS_ACTIVITY' в выгрузке. Если процент аномалий превышает 5% от смены, вызывает ИИ для генерации критического алерта. """ if df_scud is None or df_scud.empty: return None total_records = len(df_scud) anomaly_rows = df_scud[df_scud.get('anomaly_flag', 'NONE') == 'ANOMALY_NO_IN_HAS_ACTIVITY'] anomaly_count = len(anomaly_rows) if total_records == 0: return None anomaly_percent = round((anomaly_count / total_records) * 100, 1) if anomaly_percent > 5.0 or anomaly_count >= 10: rules_context = get_system_rules_context() prompt = f""" {rules_context} ВНИМАНИЕ! Проведён анализ дневной смены СКУД: - Всего записей за смену: {total_records} - Выявлено сотрудников без утреннего входа, но с зафиксированной дневной активностью (первая активность): {anomaly_count} ({anomaly_percent}% от смены) Сформируй понятное предупреждение для Администратора СКУД и Руководителя. Объясни, что это критический массовый сбой турникетов/контроллеров входа на КПП, и порекомендуй действия. """ alert_text = ask_ollama(prompt, system_prompt="Ты — ИИ-аналитик контроллинга СКУД. Отвечай кратко и строго по делу.") return { "is_mass_failure": True, "anomaly_count": anomaly_count, "anomaly_percent": anomaly_percent, "alert_text": alert_text } return { "is_mass_failure": False, "anomaly_count": anomaly_count, "anomaly_percent": anomaly_percent, "alert_text": "Массовых сбоев оборудования не зафиксировано." } def ai_verify_scud_against_staff(unrecognized_scud_fios, staff_fios): """ ИИ-сверка опечаток и несовпадений ФИО между СКУД и 1С:ЗУП. Записи из 1С считаются 100% эталоном. """ if not unrecognized_scud_fios or not staff_fios: return {} rules_context = get_system_rules_context() prompt = f""" Ты — кадровый аудитор безопасности СКУД. {rules_context} В СКУД записаны неопознанные ФИО: {json.dumps(unrecognized_scud_fios, ensure_ascii=False)} В официальном Штатном расписании 1С записаны ЭТАЛОНЫ: {json.dumps(staff_fios, ensure_ascii=False)} СТРОГИЕ ПРАВИЛА: 1. Запись из Штат 1С — это 100% ПРАВИЛЬНЫЙ эталон. 2. В СКУД допущена опечатка. 3. Любое присутствие сотрудника по СКУД при наличии в 1С документа отсутствия (кроме командировок) является гарантированной аномалией. 4. В поле "warning" опиши обнаруженную опечатку. 5. Запрещено выдумывать опечатки! ОТВЕЧАЙ ТОЛЬКО ИСКЛЮЧИТЕЛЬНО В ФОРМАТЕ JSON! Формат ответа: {{ "verified_matches": [ {{ "scud_fio": "ФИО из СКУД", "staff_fio": "эталон ФИО из Штат 1С", "warning": "Описание опечатки в СКУД или 'Точное совпадение (без опечаток)'" }} ] }} """ raw_response = ask_ollama( prompt, system_prompt="Ты — JSON API. Выдавай ТОЛЬКО валидный JSON без markdown-разметки." ) mapping = {} try: match = re.search(r'\{.*\}', raw_response, re.DOTALL) if match: json_str = match.group(0) json_str = re.sub(r'\}\s*[^}\]]*$', '}', json_str) data = json.loads(json_str) for item in data.get("verified_matches", []): scud_f = item.get("scud_fio") staff_f = item.get("staff_fio") warn = item.get("warning", "Точное совпадение (без опечаток)") if scud_f and staff_f: mapping[scud_f] = {"staff_fio": staff_f, "warning": warn} except Exception as e: print(f"[!] Ошибка разбора JSON от ИИ при сверке опечаток: {e}") return mapping