feat: Admin-панель в CRM + CLI --repass / --delete-db

- app/app.py: навигация CRM / Управление (st.sidebar.radio), render_admin()
  со статистикой, кнопками rescore / repass / сброс флагов / удаление БД с чекбоксом
- main.py: run_repass() (ренормализация телефонов+доменов + rescore, без HTTP),
  флаги --delete-db и --repass, Path import добавлен

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Aks
2026-06-09 13:36:30 +03:00
parent 98309dcc96
commit e116e508f9
2 changed files with 220 additions and 12 deletions
+81
View File
@@ -40,6 +40,7 @@ import random
import sys
import time
from datetime import datetime
from pathlib import Path
import config
from database import (
@@ -691,6 +692,54 @@ def run_rescore(conn) -> int:
return changed
def run_repass(conn) -> dict:
"""Ренормализация телефонов/доменов + rescore. Без HTTP-запросов."""
from normalization import normalize_phone, normalize_domain
leads = get_all_leads(conn)
phones_fixed = 0
domains_fixed = 0
for row in leads:
lead = dict(row)
updates = {}
# Нормализация phone_primary
raw_phone = lead.get("phone_primary")
if raw_phone and not raw_phone.startswith("+"):
normed = normalize_phone(raw_phone)
if normed and normed != raw_phone:
updates["phone_primary"] = normed
phones_fixed += 1
# Починить website без протокола
raw_site = lead.get("website")
if raw_site and not raw_site.startswith(("http://", "https://")):
updates["website"] = "https://" + raw_site.lstrip("/")
domains_fixed += 1
if updates:
sets = ", ".join(f"{k} = ?" for k in updates)
vals = list(updates.values()) + [lead["id"]]
conn.execute(f"UPDATE leads SET {sets} WHERE id = ?", vals)
if phones_fixed or domains_fixed:
conn.commit()
scores_changed = run_rescore(conn)
logger.info(
f"repass: телефонов нормализовано={phones_fixed}, "
f"доменов исправлено={domains_fixed}, score изменился у {scores_changed}"
)
return {
"phones_fixed": phones_fixed,
"domains_fixed": domains_fixed,
"scores_changed": scores_changed,
"total": len(leads),
}
# ───────────────────────────────────────────────────────────────────────
# CLI
# ───────────────────────────────────────────────────────────────────────
@@ -770,6 +819,17 @@ def main():
action="store_true",
help="Пересчитать score у всех лидов (после изменения формулы)",
)
parser.add_argument(
"--delete-db",
action="store_true",
dest="delete_db",
help="Удалить leads.db + WAL/SHM файлы и пересоздать пустую схему",
)
parser.add_argument(
"--repass",
action="store_true",
help="Ренормализация телефонов/доменов + rescore без HTTP-запросов",
)
parser.add_argument(
"--full",
action="store_true",
@@ -910,10 +970,29 @@ def main():
args.city = known
break
# --delete-db: обработать до открытия соединения
if args.delete_db:
db_path = Path(config.DB_PATH)
for suffix in ("", "-wal", "-shm"):
p = Path(str(db_path) + suffix) if suffix else db_path
if p.exists():
p.unlink()
logger.info(f"Удалён: {p}")
init_db(str(config.DB_PATH))
logger.info("БД пересоздана (пустая схема)")
return
# Инициализация БД
init_db(config.DB_PATH)
conn = get_connection(config.DB_PATH)
try:
if args.repass:
result = run_repass(conn)
logger.info(
f"repass завершён: телефонов={result['phones_fixed']}, "
f"доменов={result['domains_fixed']}, score={result['scores_changed']}/{result['total']}"
)
# Только статистика — выходим сразу
if args.stats:
stats = get_stats(conn)
@@ -956,6 +1035,8 @@ def main():
or args.find_sites
or args.export_master
or args.export_run is not None
or args.delete_db
or args.repass
):
parser.print_help()
sys.exit(1)