feat(web): стабилизация UI, Gemini-скроллинг, роутеры контекста/снапшотов и актуализация роадмапа
This commit is contained in:
+74
-16
@@ -7,6 +7,7 @@ ROLE: Надежная загрузка штата и отсутствий (MS S
|
||||
|
||||
import os
|
||||
import warnings
|
||||
from datetime import datetime # <-- ДОБАВИТЬ ЭТУ СТРОКУ
|
||||
import pandas as pd
|
||||
from config import (
|
||||
normalize_fio, clean_scud_fio_light, load_exceptions,
|
||||
@@ -101,27 +102,84 @@ def load_absent_data(date_str):
|
||||
if df_absent is None:
|
||||
df_absent = pd.DataFrame(columns=['fio_clean', 'Вид_отсутствия'])
|
||||
|
||||
# Обогащение удаленщиками из static_reason_workers.csv
|
||||
# Обогащение удаленщиками из static_reason_workers.csv с ротацией просроченных записей
|
||||
try:
|
||||
static_path = os.path.join(DATA_DIR, "static_reason_workers.csv")
|
||||
if os.path.exists(static_path):
|
||||
df_static = pd.read_csv(static_path, encoding='utf-8')
|
||||
if 'fio' in df_static.columns and 'reason' in df_static.columns:
|
||||
df_static['fio_clean'] = df_static['fio'].apply(normalize_fio)
|
||||
existing_fios = set(df_absent['fio_clean'].dropna().tolist()) if not df_absent.empty else set()
|
||||
|
||||
new_rows = []
|
||||
for _, s_row in df_static.iterrows():
|
||||
if s_row['fio_clean'] not in existing_fios:
|
||||
new_rows.append({
|
||||
'fio_clean': s_row['fio_clean'],
|
||||
'Вид_отсутствия': s_row['reason']
|
||||
df_static = pd.read_csv(static_path, dtype=str, on_bad_lines='skip').fillna("")
|
||||
|
||||
for col in ['fio', 'reason', 'department', 'date_from', 'date_to']:
|
||||
if col not in df_static.columns:
|
||||
df_static[col] = ""
|
||||
|
||||
today_date = datetime.now().date()
|
||||
|
||||
# Разбор целевой даты отчета
|
||||
clean_target_str = str(date_str).replace('_', '.')
|
||||
try:
|
||||
clean_target_date = datetime.strptime(clean_target_str, "%d.%m.%Y").date()
|
||||
except ValueError:
|
||||
clean_target_date = today_date
|
||||
|
||||
def parse_date_safe(d_val):
|
||||
if not d_val or str(d_val).lower() in ['nan', 'none', '', 'nat']:
|
||||
return None
|
||||
s = str(d_val).strip().replace('_', '.')
|
||||
for fmt in ("%d.%m.%Y", "%Y-%m-%d"):
|
||||
try:
|
||||
return datetime.strptime(s, fmt).date()
|
||||
except ValueError:
|
||||
pass
|
||||
return None
|
||||
|
||||
active_for_file = []
|
||||
new_rows_for_report = []
|
||||
file_changed = False
|
||||
|
||||
existing_fios = set(df_absent['fio_clean'].dropna().tolist()) if not df_absent.empty else set()
|
||||
|
||||
for _, s_row in df_static.iterrows():
|
||||
fio_raw = str(s_row.get('fio', '')).strip()
|
||||
if not fio_raw:
|
||||
continue
|
||||
|
||||
fio_c = normalize_fio(fio_raw)
|
||||
reason = str(s_row.get('reason', '')).strip() or "Дистанционная работа"
|
||||
|
||||
d_from = parse_date_safe(s_row.get('date_from'))
|
||||
d_to = parse_date_safe(s_row.get('date_to'))
|
||||
|
||||
# 1. Физическая ротация просроченных: только если текущий реальный день (today) строго больше date_to
|
||||
if d_to is not None and today_date > d_to:
|
||||
print(f" [🧹] Удаленка истекла: {fio_raw} (до {d_to.strftime('%d.%m.%Y')}). Удалена из CSV.")
|
||||
file_changed = True
|
||||
continue
|
||||
|
||||
active_for_file.append(s_row.to_dict())
|
||||
|
||||
# 2. Проверка действия удаленки на дату формируемого отчета:
|
||||
# Если date_from не указана — действует всегда до date_to
|
||||
is_after_start = (d_from is None) or (clean_target_date >= d_from)
|
||||
is_before_end = (d_to is None) or (clean_target_date <= d_to)
|
||||
|
||||
if is_after_start and is_before_end:
|
||||
if fio_c not in existing_fios:
|
||||
new_rows_for_report.append({
|
||||
'fio_clean': fio_c,
|
||||
'Вид_отсутствия': reason
|
||||
})
|
||||
if new_rows:
|
||||
df_absent = pd.concat([df_absent, pd.DataFrame(new_rows)], ignore_index=True)
|
||||
print(f" [✓] Реестр удаленщиков: добавлено {len(new_rows)} чел. из static_reason_workers.csv")
|
||||
existing_fios.add(fio_c)
|
||||
|
||||
# Перезаписываем CSV только если реально были удалены просроченные сотрудники
|
||||
if file_changed:
|
||||
pd.DataFrame(active_for_file).to_csv(static_path, index=False, encoding='utf-8')
|
||||
print(" [✓] Файл static_reason_workers.csv синхронизирован без просроченных записей.")
|
||||
|
||||
if new_rows_for_report:
|
||||
df_absent = pd.concat([df_absent, pd.DataFrame(new_rows_for_report)], ignore_index=True)
|
||||
print(f" [✓] Реестр удаленщиков: добавлено {len(new_rows_for_report)} чел. в отчет за {date_str}")
|
||||
except Exception as e:
|
||||
print(f" [⚠️] Ошибка чтения static_reason_workers.csv: {e}")
|
||||
print(f" [⚠️] Ошибка обработки static_reason_workers.csv: {e}")
|
||||
|
||||
return df_absent if not df_absent.empty else None
|
||||
|
||||
|
||||
Reference in New Issue
Block a user