промежуточный положительный результат. Отчеты коррктно формируются. Перед автоматической загрузкой файлов 1С.
This commit is contained in:
+83
-38
@@ -1,34 +1,72 @@
|
||||
import json
|
||||
import difflib
|
||||
import re
|
||||
from modules.ai_verifier import ask_ollama
|
||||
from modules.knowledge_base import load_knowledge_base
|
||||
|
||||
|
||||
def generate_markdown_report(merged_df, absent_explained, absent_unexplained, scud_present_but_absent_in_1c, anomalies_list=None, raw_scud_df=None, raw_staff_df=None, date_str="28.07.2026"):
|
||||
def find_python_fuzzy_matches(unexplained_df, raw_absent_df):
|
||||
"""
|
||||
Точный поиск совпадений ФИО силами Python (без галлюцинаций ИИ).
|
||||
Сравнивает фамилию и полное имя с порогом сходства >= 0.80.
|
||||
"""
|
||||
if unexplained_df.empty or raw_absent_df is None or raw_absent_df.empty:
|
||||
return []
|
||||
|
||||
# Собираем словарь чистых ФИО 1С -> сырые ФИО 1С
|
||||
absent_dict = {}
|
||||
for idx, r in raw_absent_df.iterrows():
|
||||
raw_fio = str(r.get('ФИО', ''))
|
||||
clean_fio = str(r.get('fio_clean', ''))
|
||||
reason = str(r.get('Вид_отсутствия', ''))
|
||||
if clean_fio and clean_fio not in ['Ао "Ленморниипроект"', 'Сотрудник', 'Nan', 'None']:
|
||||
absent_dict[clean_fio] = {'raw_fio': raw_fio, 'reason': reason}
|
||||
|
||||
matches = []
|
||||
unexplained_fios = unexplained_df['fio_clean'].unique() if 'fio_clean' in unexplained_df.columns else []
|
||||
|
||||
for fio in unexplained_fios:
|
||||
fio_parts = fio.split()
|
||||
if not fio_parts:
|
||||
continue
|
||||
surname = fio_parts[0].lower()
|
||||
|
||||
for abs_clean, abs_info in absent_dict.items():
|
||||
abs_parts = abs_clean.split()
|
||||
if not abs_parts:
|
||||
continue
|
||||
abs_surname = abs_parts[0].lower()
|
||||
|
||||
# Строгая проверка совпадения фамилии
|
||||
if surname == abs_surname:
|
||||
ratio = difflib.SequenceMatcher(None, fio.lower(), abs_clean.lower()).ratio()
|
||||
if ratio >= 0.75:
|
||||
matches.append({
|
||||
"target_fio": fio,
|
||||
"found_in_1c_raw": abs_info['raw_fio'],
|
||||
"reason_1c": abs_info['reason']
|
||||
})
|
||||
break
|
||||
|
||||
return matches
|
||||
|
||||
|
||||
def generate_markdown_report(merged_df, absent_explained, absent_unexplained, scud_present_but_absent_in_1c, anomalies_list=None, raw_scud_df=None, raw_staff_df=None, raw_absent_df=None, date_str="29.07.2026"):
|
||||
kb = load_knowledge_base()
|
||||
custom_rules = kb.get("rules", [])
|
||||
custom_rules_str = "\n".join([f"- {r}" for r in custom_rules]) if custom_rules else "Специфических правил компании пока нет."
|
||||
|
||||
detailed_cases_for_ai = []
|
||||
unexplained_fios = absent_unexplained['fio_clean'].unique() if 'fio_clean' in absent_unexplained.columns else []
|
||||
# 1. Точный расчет неточных совпадений через Python (Исключает галлюцинации ИИ)
|
||||
fio_mismatches_python = find_python_fuzzy_matches(absent_unexplained, raw_absent_df)
|
||||
|
||||
for fio_clean in unexplained_fios:
|
||||
staff_matches = []
|
||||
if raw_staff_df is not None and not raw_staff_df.empty and 'fio_clean' in raw_staff_df.columns:
|
||||
st_m = raw_staff_df[raw_staff_df['fio_clean'] == fio_clean]
|
||||
cols_staff = [c for c in ['ФИО', 'Подразделение', 'Должность', 'Таб_№'] if c in st_m.columns]
|
||||
staff_matches = st_m[cols_staff].to_dict(orient='records')
|
||||
|
||||
scud_matches = []
|
||||
if raw_scud_df is not None and not raw_scud_df.empty and 'fio_clean' in raw_scud_df.columns:
|
||||
sc_m = raw_scud_df[raw_scud_df['fio_clean'] == fio_clean]
|
||||
cols_scud = [c for c in ['Сотрудник', 'Подразделение', 'Должность', 'Начало_дня', 'Статус'] if c in sc_m.columns]
|
||||
scud_matches = sc_m[cols_scud].to_dict(orient='records')
|
||||
|
||||
detailed_cases_for_ai.append({
|
||||
"fio_clean": fio_clean,
|
||||
"staff_records_1C": staff_matches,
|
||||
"scud_records_LOGS": scud_matches
|
||||
})
|
||||
# 2. Список истинно неизвестных для ИИ
|
||||
unexplained_list = []
|
||||
if not absent_unexplained.empty:
|
||||
for idx, r in absent_unexplained.iterrows():
|
||||
fio = r.get('Сотрудник', r.get('fio_clean', ''))
|
||||
dept = r.get('Подразделение', '—')
|
||||
pos = r.get('Должность', '—')
|
||||
unexplained_list.append(f"{fio} — {dept}, {pos}")
|
||||
|
||||
total_staff = len(merged_df)
|
||||
is_working_mask = (merged_df['Пришел'] == True) | (
|
||||
@@ -41,25 +79,34 @@ def generate_markdown_report(merged_df, absent_explained, absent_unexplained, sc
|
||||
|
||||
prompt = f"""
|
||||
Ты — старший аудитор кадровой безопасности и контроллинга СКУД.
|
||||
Проанализируй аномалии и пересечения данных за {date_str}.
|
||||
Сформируй итоговую сводку кадрового контроля на {date_str}.
|
||||
|
||||
📌 НАКОПЛЕННЫЕ ПРАВИЛА ПРЕДПРИЯТИЯ И ИСКЛЮЧЕНИЯ:
|
||||
📌 БАЗА ЗНАНИЙ И ПРАВИЛА ПРЕДПРИЯТИЯ:
|
||||
{custom_rules_str}
|
||||
|
||||
🚨 ВХОДНЫЕ МЕТРИКИ:
|
||||
- Всего сотрудников: {total_staff}
|
||||
- На работе / Удаленно / Командировка: {present_cnt}
|
||||
- Официально отсутствуют (1С/Реестры): {explained_cnt}
|
||||
- Неизвестно (требуют выяснения): {unexplained_cnt}
|
||||
- Выявлено аномалий и конфликтов реестров: {anomalies_cnt}
|
||||
- Работают (офис / удаленка / командировки): {present_cnt}
|
||||
- Официально отсутствуют: {explained_cnt}
|
||||
- Неизвестно (истинно неотмеченные): {unexplained_cnt}
|
||||
- Выявлено аномалий/конфликтов реестров: {anomalies_cnt}
|
||||
|
||||
---
|
||||
🚨 ОБНАРУЖЕННЫЕ АНОМАЛИИ И КОНФЛИКТЫ МЕЖДУ СКУД, 1С, СТАТИЧЕСКИМИ РЕЕСТРАМИ И ИСКЛЮЧЕНИЯМИ ({anomalies_cnt} шт.):
|
||||
🚨 ПОДТВЕРЖДЁННЫЕ АНОМАЛИИ ({anomalies_cnt} шт):
|
||||
{json.dumps(anomalies_list, ensure_ascii=False, indent=2)}
|
||||
|
||||
---
|
||||
⚠️ ПОДТВЕРЖДЁННЫЕ PYTHON ОШИБКИ СОПОСТАВЛЕНИЯ ФИО В 1С ({len(fio_mismatches_python)} шт):
|
||||
{json.dumps(fio_mismatches_python, ensure_ascii=False, indent=2)}
|
||||
|
||||
---
|
||||
📊 СПИСОК НЕИЗВЕСТНЫХ СЛУЧАЕВ ({unexplained_cnt} чел):
|
||||
{json.dumps(detailed_cases_for_ai, ensure_ascii=False, indent=2)}
|
||||
{json.dumps(unexplained_list, ensure_ascii=False, indent=2)}
|
||||
|
||||
СТРОГИЕ ИНСТРУКЦИИ ДЛЯ ИИ:
|
||||
1. КАТЕГОРИЧЕСКИ ЗАПРЕЩЕНО придумывать несуществующие совпадения ФИО или приписывать суффиксы "(осн.)". Используй ТОЛЬКО массив `ПОДТВЕРЖДЁННЫЕ PYTHON ОШИБКИ СОПОСТАВЛЕНИЯ ФИО`. Если этот массив пуст, напиши в этом разделе: "Ошибок сопоставления ФИО и неточностей в 1С не обнаружено."
|
||||
2. В разделе "Неизвестные случаи" выведи НУМЕРОВАННЫЙ СПИСОК всех {unexplained_cnt} человек ровно в том виде, в котором они переданы выше.
|
||||
3. В разделе "Рекомендации" дай 2-3 конкретные системные рекомендации для кадровой службы. НЕ ПЕРЕЧИСЛЯЙ конкретные ФИО в тексте рекомендаций.
|
||||
|
||||
СТРОГИЙ ШАБЛОН ОТВЕТА:
|
||||
|
||||
@@ -72,19 +119,17 @@ def generate_markdown_report(merged_df, absent_explained, absent_unexplained, sc
|
||||
- Выявлено аномалий/конфликтов реестров: **{anomalies_cnt}** шт.
|
||||
|
||||
#### 🚨 Выявленные ИИ аномалии и конфликты источников ({anomalies_cnt}):
|
||||
(Подробно разбери каждый конфликт из `anomalies_list`. Опиши суть нарушения. Если аномалий нет — напиши "Аномалий и конфликтов не обнаружено.")
|
||||
(Описание аномалий из anomalies_list. Если их нет — "Аномалий не обнаружено.")
|
||||
|
||||
#### ⚠️ Подозрения на ложные срабатывания / Совмещения:
|
||||
(Укажи сотрудников с повторными проходами или несколькими картами, если такие есть)
|
||||
#### ⚠️ Подозрения на ошибки сопоставления ФИО и несоответствия 1С:
|
||||
(Выведи данные ИСКЛЮЧИТЕЛЬНО из массива fio_mismatches_python. Если он пуст — "Ошибок сопоставления ФИО и неточностей в 1С не обнаружено.")
|
||||
|
||||
#### Неизвестные случаи: {unexplained_cnt}
|
||||
(Выведи НУМЕРОВАННЫЙ СПИСОК ВСЕХ {unexplained_cnt} человек в формате:
|
||||
1. ФИО — Подразделение, Должность
|
||||
2. ФИО — Подразделение, Должность)
|
||||
(Выведи нумерованный список всех {unexplained_cnt} человек)
|
||||
|
||||
### Рекомендации:
|
||||
(Сформулируй 2-3 общие системные рекомендации для кадровой службы. КАТЕГОРИЧЕСКИ ЗАПРЕЩЕНО перечислять конкретные ФИО сотрудников в скобках или тексте рекомендаций — они уже перечислены выше.)
|
||||
### Точечные рекомендации:
|
||||
(2-3 системные рекомендации без указания ФИО сотрудников)
|
||||
"""
|
||||
|
||||
sys_prompt = "Ты — эксперт кадрового аудита. Пиши сжато, лаконично, без дублирования ФИО сотрудников в блоке рекомендаций."
|
||||
sys_prompt = "Ты — эксперт кадрового аудита. Будь точен, не придумывай непереданные данные."
|
||||
return ask_ollama(prompt, system_prompt=sys_prompt)
|
||||
Reference in New Issue
Block a user