feat(etl): stable pipeline, exception registry in SQLite, multi-pass aggregation and db_cli
This commit is contained in:
+31
-26
@@ -6,10 +6,6 @@ from services.knowledge_base import load_knowledge_base
|
||||
|
||||
|
||||
def find_python_fuzzy_matches(unexplained_df, raw_absent_df):
|
||||
"""
|
||||
Точный поиск совпадений ФИО силами Python (без галлюцинаций ИИ).
|
||||
Сравнивает фамилию и полное имя с порогом сходства >= 0.75.
|
||||
"""
|
||||
if unexplained_df.empty or raw_absent_df is None or raw_absent_df.empty:
|
||||
return []
|
||||
|
||||
@@ -50,40 +46,46 @@ def find_python_fuzzy_matches(unexplained_df, raw_absent_df):
|
||||
|
||||
|
||||
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="05.08.2026"):
|
||||
# 1. Загрузка Базы Знаний
|
||||
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 "Специфических правил компании пока нет."
|
||||
|
||||
# 2. Предварительный расчет ключевых метрик (РЕШАЕТ NameError: total_staff)
|
||||
total_staff = len(merged_df)
|
||||
is_working_mask = (merged_df['Пришел'] == True) | (
|
||||
merged_df['Вид_отсутствия'].astype(str).str.lower().str.contains('командировк|удален|дистанцион|разъездн', regex=True, na=False)
|
||||
)
|
||||
present_cnt = len(merged_df[is_working_mask])
|
||||
explained_cnt = len(absent_explained)
|
||||
|
||||
# 1. Пришедшие в офис
|
||||
present_office_cnt = len(merged_df[merged_df['Пришел'] == True])
|
||||
|
||||
# 2. Не пришедшие удаленщики
|
||||
reason_series = merged_df['Вид_отсутствия'].astype(str).str.lower()
|
||||
is_remote_reason = reason_series.str.contains('удален|дистанцион', regex=True, na=False)
|
||||
remote_home_cnt = len(merged_df[(merged_df['Пришел'] == False) & is_remote_reason])
|
||||
|
||||
# 3. Официально отсутствующие (без удаленки)
|
||||
explained_cnt = len(merged_df[(merged_df['Пришел'] == False) & (merged_df['Вид_отсутствия'].notna()) & (~is_remote_reason) & (~merged_df['Вид_отсутствия'].astype(str).str.startswith('Исключение'))])
|
||||
|
||||
unexplained_cnt = len(absent_unexplained)
|
||||
anomalies_cnt = len(anomalies_list) if anomalies_list else 0
|
||||
|
||||
# 3. Точный расчет неточных совпадений через Python
|
||||
fio_mismatches_python = find_python_fuzzy_matches(absent_unexplained, raw_absent_df)
|
||||
|
||||
# 4. Формирование списка неизвестных с приоритетом аббревиатуры СКУД
|
||||
anomalies_formatted = []
|
||||
if anomalies_list:
|
||||
for idx, a in enumerate(anomalies_list, 1):
|
||||
anomalies_formatted.append(f"{idx}. {a.get('fio', 'Сотрудник')}: {a.get('description', '')}")
|
||||
anomalies_text_block = "\n".join(anomalies_formatted) if anomalies_formatted else "Аномалий не обнаружено."
|
||||
|
||||
unexplained_list = []
|
||||
if not absent_unexplained.empty:
|
||||
for idx, r in absent_unexplained.iterrows():
|
||||
fio = r.get('Сотрудник', r.get('fio_clean', ''))
|
||||
|
||||
dept_scud = str(r.get('department_scud', '')).strip()
|
||||
dept_1c = str(r.get('Подразделение', '')).strip()
|
||||
dept = dept_scud if (dept_scud and dept_scud.lower() != 'nan' and dept_scud != '—') else dept_1c
|
||||
if not dept:
|
||||
dept = '—'
|
||||
|
||||
pos = r.get('Должность', '—')
|
||||
unexplained_list.append(f"{fio} — {dept}, {pos}")
|
||||
|
||||
# 5. Сборка промпта для Ollama
|
||||
prompt = f"""
|
||||
Ты — старший аудитор кадровой безопасности и контроллинга СКУД.
|
||||
Сформируй итоговую сводку кадрового контроля на {date_str}.
|
||||
@@ -93,14 +95,15 @@ def generate_markdown_report(merged_df, absent_explained, absent_unexplained, sc
|
||||
|
||||
🚨 ВХОДНЫЕ МЕТРИКИ:
|
||||
- Всего сотрудников: {total_staff}
|
||||
- Работают (офис / удаленка / командировки): {present_cnt}
|
||||
- Итого на работе (в офисе): {present_office_cnt}
|
||||
- В том числе на удаленной работе: {remote_home_cnt}
|
||||
- Официально отсутствуют: {explained_cnt}
|
||||
- Неизвестно (истинно неотмеченные): {unexplained_cnt}
|
||||
- Выявлено аномалий/конфликтов реестров: {anomalies_cnt}
|
||||
|
||||
---
|
||||
🚨 ПОДТВЕРЖДЁННЫЕ АНОМАЛИИ ({anomalies_cnt} шт):
|
||||
{json.dumps(anomalies_list, ensure_ascii=False, indent=2)}
|
||||
{anomalies_text_block}
|
||||
|
||||
---
|
||||
⚠️ ПОДТВЕРЖДЁННЫЕ PYTHON ОШИБКИ СОПОСТАВЛЕНИЯ ФИО В 1С ({len(fio_mismatches_python)} шт):
|
||||
@@ -111,32 +114,34 @@ def generate_markdown_report(merged_df, absent_explained, absent_unexplained, sc
|
||||
{json.dumps(unexplained_list, ensure_ascii=False, indent=2)}
|
||||
|
||||
СТРОГИЕ ИНСТРУКЦИИ ДЛЯ ИИ:
|
||||
1. КАТЕГОРИЧЕСКИ ЗАПРЕЩЕНО придумывать несуществующие совпадения ФИО или приписывать суффиксы "(осн.)". Используй ТОЛЬКО массив `ПОДТВЕРЖДЁННЫЕ PYTHON ОШИБКИ СОПОСТАВЛЕНИЯ ФИО`. Если этот массив пуст, напиши в этом разделе: "Ошибок сопоставления ФИО и неточностей в 1С не обнаружено."
|
||||
2. В разделе "Неизвестные случаи" выведи НУМЕРОВАННЫЙ СПИСОК всех {unexplained_cnt} человек ровно в том виде, в котором они переданы выше.
|
||||
3. В разделе "Рекомендации" дай 2-3 конкретные системные рекомендации для кадровой службы. НЕ ПЕРЕЧИСЛЯЙ конкретные ФИО в тексте рекомендаций.
|
||||
1. КАТЕГОРИЧЕСКИ ЗАПРЕЩЕНО выводить JSON! В блоке "Выявленные ИИ аномалии" выведи читаемый нумерованный список строк ровно так, как передано в разделе "ПОДТВЕРЖДЁННЫЕ АНОМАЛИИ". Если аномалий нет, напиши: "Аномалий не обнаружено."
|
||||
2. В разделе "Подозрения на ошибки сопоставления ФИО" используй ТОЛЬКО массив `ПОДТВЕРЖДЁННЫЕ PYTHON ОШИБКИ СОПОСТАВЛЕНИЯ ФИО`. Если он пуст, напиши: "Ошибок сопоставления ФИО и неточностей в 1С не обнаружено."
|
||||
3. В разделе "Неизвестные случаи" выведи НУМЕРОВАННЫЙ СПИСОК всех {unexplained_cnt} человек ровно в том виде, в котором они переданы выше.
|
||||
4. В разделе "Рекомендации" дай 2-3 конкретные системные рекомендации для кадровой службы без перечисления конкретных ФИО.
|
||||
|
||||
СТРОГИЙ ШАБЛОН ОТВЕТА:
|
||||
|
||||
**Сводка контроллинга СКУД и 1С:ЗУП на {date_str}**
|
||||
|
||||
- Всего офисных сотрудников: **{total_staff}**
|
||||
- Работают (офис / удаленка / командировки): **{present_cnt}**
|
||||
- Итого на работе (в офисе): **{present_office_cnt}**
|
||||
- В том числе на удаленной работе: **{remote_home_cnt}**
|
||||
- Официально отсутствуют: **{explained_cnt}**
|
||||
- Неизвестно (истинно неотмеченные): **{unexplained_cnt}** чел.
|
||||
- Выявлено аномалий/конфликтов реестров: **{anomalies_cnt}** шт.
|
||||
|
||||
#### 🚨 Выявленные ИИ аномалии и конфликты источников ({anomalies_cnt}):
|
||||
(Описание аномалий из anomalies_list. Если их нет — "Аномалий не обнаружено.")
|
||||
(Выведи нумерованный текстовый список аномалий или 'Аномалий не обнаружено.')
|
||||
|
||||
#### ⚠️ Подозрения на ошибки сопоставления ФИО и несоответствия 1С:
|
||||
(Выведи данные ИСКЛЮЧИТЕЛЬНО из массива fio_mismatches_python. Если он пуст — "Ошибок сопоставления ФИО и неточностей в 1С не обнаружено.")
|
||||
(Выведи данные ИСКЛЮЧИТЕЛЬНО из массива ошибок ФИО. Если пуст — 'Ошибок сопоставления ФИО и неточностей в 1С не обнаружено.')
|
||||
|
||||
#### Неизвестные случаи: {unexplained_cnt}
|
||||
(Выведи нумерованный список всех {unexplained_cnt} человек)
|
||||
|
||||
### Точечные рекомендации:
|
||||
(2-3 системные рекомендации без указания ФИО сотрудников)
|
||||
(2-3 системные рекомендации)
|
||||
"""
|
||||
|
||||
sys_prompt = "Ты — русскоязычный кадровый аудитор. Пиши СТРОГО на русском языке по предоставленному Markdown-шаблону. Категорически запрещено выводить JSON или китайские символы."
|
||||
sys_prompt = "Ты — русскоязычный кадровый аудитор. Пиши СТРОГО на русском языке обычным форматированным текстом. Категорически запрещено выводить фигурные скобки JSON или технический код."
|
||||
return ask_ollama(prompt, system_prompt=sys_prompt)
|
||||
Reference in New Issue
Block a user