fix: устранены все найденные аудитом баги и тихие падения

- SQL injection паттерн → параметризованные запросы во всех местах
- except: pass/continue → logger.warning() везде, ничего не тонет молча
- WAL mode + индекс domain_dedup_key в database.py
- try/finally для conn в main.py, утечка соединения устранена
- backoff 30с при 403/429 от Rusprofile/ЕГРЮЛ
- ликвидированные компании → egrul_status="liquidated"
- max_candidates в contacts_finder считает только реальных кандидатов
- DB_PATH абсолютный (Path(__file__).parent), HH_PAUSE_BETWEEN_QUERIES в config
- HH_SIGNAL_QUERIES дубль убран из launcher.py → импорт из config
- path traversal защита в egrul_enricher debug_dump_html

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Aks
2026-06-09 13:19:52 +03:00
parent f78f35fb3f
commit 98309dcc96
9 changed files with 208 additions and 186 deletions
+9 -3
View File
@@ -4,6 +4,7 @@
Сама занимается дедупликацией: ИНН > телефон > домен.
"""
import json
import logging
import sqlite3
from datetime import datetime
from pathlib import Path
@@ -12,6 +13,8 @@ from typing import Optional
import config
from normalization import phone_dedup_key, normalize_domain
logger = logging.getLogger(__name__)
# ───────────────────────────────────────────────────────────────────────
# Схема таблиц
# ───────────────────────────────────────────────────────────────────────
@@ -119,6 +122,7 @@ CREATE INDEX IF NOT EXISTS idx_leads_score ON leads(score DESC);
CREATE INDEX IF NOT EXISTS idx_leads_source ON leads(source);
CREATE INDEX IF NOT EXISTS idx_leads_outreach ON leads(outreach_status);
CREATE INDEX IF NOT EXISTS idx_leads_has_website ON leads(has_website);
CREATE INDEX IF NOT EXISTS idx_leads_domain ON leads(domain_dedup_key);
CREATE TABLE IF NOT EXISTS sources_log (
id INTEGER PRIMARY KEY AUTOINCREMENT,
@@ -208,6 +212,7 @@ def get_connection(db_path: str = "leads.db") -> sqlite3.Connection:
"""Открыть соединение с включённым row_factory (sqlite3.Row для dict-доступа)."""
conn = sqlite3.connect(db_path)
conn.row_factory = sqlite3.Row
conn.execute("PRAGMA journal_mode=WAL")
return conn
@@ -748,8 +753,9 @@ def cleanup_bad_director_names(conn: sqlite3.Connection) -> int:
"Производство", "Услуги", "Работ",
]
# Совпадение если первое слово в director_name — должность
where_parts = [f"director_name LIKE '{m}%'" for m in bad_markers]
where_parts = ["director_name LIKE ?"] * len(bad_markers)
where_clause = " OR ".join(where_parts)
params = [f"{m}%" for m in bad_markers]
sql = f"""
UPDATE leads
SET director_name = NULL,
@@ -757,7 +763,7 @@ def cleanup_bad_director_names(conn: sqlite3.Connection) -> int:
egrul_status = NULL
WHERE {where_clause}
"""
cursor = conn.execute(sql)
cursor = conn.execute(sql, params)
conn.commit()
return cursor.rowcount
@@ -878,7 +884,7 @@ def get_stats(conn: sqlite3.Connection) -> dict:
).fetchall())
threshold = config.HOT_LEAD_THRESHOLD
hot = conn.execute(
f"SELECT COUNT(*) FROM leads WHERE score >= {int(threshold)}"
"SELECT COUNT(*) FROM leads WHERE score >= ?", (int(threshold),)
).fetchone()[0]
with_phone = conn.execute(
"SELECT COUNT(*) FROM leads WHERE phone_primary IS NOT NULL"