Commit Graph

2792 Commits

Author SHA1 Message Date
Дмитрий 09f0ea44df docs(chat): замысел личных ответов вошедшему клиенту
Карточка фактов (баланс/ступень/проекты/заявки) считается кодом и всегда
кладётся боту, когда клиент вошёл. Сторож режет числа не из карточки.
Переписка вошедшего закрывается от посторонних. Данные — строго под RLS
тенанта. Бот только рассказывает, ничего не меняет.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-13 10:55:32 +03:00
Дмитрий 2b13980af8 docs(chat): хвосты закрыты — экскурсии по теме и только из кабинета
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-13 10:15:38 +03:00
Дмитрий 60b0b855c5 docs(bot): в снимок состояния — правило ответственности за источник
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-13 09:50:30 +03:00
Дмитрий 34c6d68cfe docs(sales-finder): HANDOFF 13.07 — повтор сайта, ДФО-перегон #15, публикация отложена, Keys.so финал + новый resume-промт
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-13 09:44:31 +03:00
Дмитрий f6b2fbe262 docs(chat): отчёт о прогоне через свой чат + снимок состояния
Прогон 46 диалогами (250 реплик) через новый чат: 250/250 ответов,
медиана 2,58 с, вранья 0 — переезд чистый. Одна находка (бот выдумал срок
«перезвонят в течение рабочего дня») починена правилом по смыслу.

Отдельно: счётчики от накрутки счёта сидели на файловом кэше, который считает
не атомарно (300 прибавок -> досчиталось 77). Переведены на Redis, 300/300.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-13 09:20:16 +03:00
Дмитрий 45b0e46279 feat(landing): подключить своё окошко чата вместо Jivo
Тот же скрипт, что в кабинете: /chat-widget.js отдаётся приложением
(на liderra.ru лендингу принадлежит только «/», остальные пути — приложению).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-13 09:02:22 +03:00
Дмитрий 477a46b889 docs(chat): пошаговый план стройки своего чата
8 задач по TDD: журнал разговоров -> свой приём сообщений -> опрос ответов ->
счётчики от накрутки счёта -> письмо владельцу со всей перепиской -> окошко
на портале и лендинге -> вычистка Jivo из настроек -> прогон 46 диалогами.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-13 06:37:28 +03:00
Дмитрий 01789381ac docs(chat): замысел своего чата вместо Jivo + разворот в протоколе
Владелец 13.07.2026: уходим от Jivo (3142 руб/мес корпоративный тариф ради
Bot API, ответа их поддержки так и нет), пишем своё окошко чата.
Ключевое: в чужом виджете бот не знает, кто с ним говорит -> личные ответы
невозможны. Привязка к Jivo в коде - 94 строки; мозг бота, 49 статей и
130 тестов переезжают нетронутыми.

Согласовано по частям: чат без постоянного соединения (очередь + опрос),
кабинет + лендинг, эскалация письмом владельцу (живого оператора нет),
три уровня счётчиков от накрутки счёта (потолок 1500 ответов/сутки + тревога).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-13 06:27:29 +03:00
Дмитрий 6e40c0523f docs(bot): правило владельца — замены заявки нет, тему сглаживаем
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-13 05:59:29 +03:00
Дмитрий 204f7b50b8 docs(bot): снимок состояния ИИ-бота на 13.07.2026
Где что лежит, как поднять стенд, что держится кодом (не ослаблять),
что осталось открытым, порядок выката.

Главный вывод доводки: сторож на регулярках проигрывает гонку формулировок —
работает только правило по смыслу + запрет в промпте.

Код: ветка worktree-jivo-bot-core (004a0b17), тесты 126/126. Бот НЕ на проде.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-13 05:35:30 +03:00
Дмитрий a49e1a356c fix(sales-finder): повтор загрузки сайта в шаге рекламируется — флак не выбрасывает рекламодателя
Проверка advertises лезет на сайт фирмы; загрузка флакует (таймаут, TLS
на Windows), не открылся -> advertises=не проверено -> фирма вылетала из
списка (drop_not_advertising). Из-за этого улов по городам плясал (Хабаровск
13->8 между прогонами). Новый salesfinder/sitefetch.fetch_site_html: повтор
requests (https/http) attempts раз, затем xfetch-рендер. web/app._fetch_site
переведён на него. Тесты +6, всего 191 зелёный.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-13 05:16:49 +03:00
Дмитрий fd9358104c docs(bot): отчёт прогона диалогами — 46 разговоров, 250 реплик
Что вскрылось (одиночные вопросы этого не видели): клиент оставил телефон →
бот молчал; бот забывал, что сам сказал; враньё «на паузе списаний нет»;
выдуманный телефон поддержки и юр-гарантии в новых оборотах.

Урок метода: машинный счётчик по запрещённым фразам показал 0 нарушений,
хотя враньё осталось другими словами — считать своими же регулярками нельзя.

Код и починка — в ветке worktree-jivo-bot-core (b037a216). Бот НЕ на проде.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-13 04:52:41 +03:00
Дмитрий bd5c16cfbe chore: gitleaks — исключение для примера телефона в докблоке бота
Пример формата в ContactCapture.php остался в истории коммита 6f72367b
и блокировал push. Не ПДн — образец для распознавания; в рабочем коде уже
замаскирован. Настоящие номера сторож ловит по-прежнему.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-12 20:54:16 +03:00
Дмитрий 368bbf2000 docs(bot): отчёт прогона 250 вопросов — финальная сборка (Haiku + сторож)
Все 250 пар «вопрос → ответ» с финальной конфигурацией бота: модель Claude
Haiku 4.5 через AITunnel, маска ПДн, чистка markdown, сторож ответа.
Итог: 239 ответил сам, 11 увёл к специалисту, 0 «не знаю», 0 запрещённых фраз.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-12 20:48:11 +03:00
Дмитрий 17ab3fa3a3 fix(sales-finder): рендер 2ГИС молча терял фирмы — повтор до появления карточек
Симптом: кластер ДФО дал Владивосток 11 фирм вместо реальных 60.
Причина: xfetch.render повторял только на ПУСТОМ ответе, а 2ГИС-антибот
отдаёт непустую SPA-оболочку без карточек — она проходила как успех,
и вся страница фирм терялась. Фикс: render(ok=...) повторяет пока ok(html)
ложно; fetcher требует наличие /firm/ (не просто непустоту), retries=4.
Живой контроль боевым fetcher: Владивосток 11 -> 60 (x5.5). 185 тестов зелёных.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-12 19:54:54 +03:00
Дмитрий 5a8811c594 feat(sales-finder): вход по логину/паролю на весь портал перед публикацией
HTTP Basic Auth через middleware; логин/пароль из secrets/portal_auth.txt
(gitignore, в код не зашиты); нет файла = без пароля (локально). Тестовые
клиенты отключают пароль явно. 182 теста зелёных.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-12 17:34:00 +03:00
Дмитрий d501a4fc79 feat(sales-finder): двухшаговый сбор контактов ИНН director -> name_standart
По личному ИНН директора: шаг 1 POST /2.0/inn -> ФИО+дата рождения
(юрлицо ООО отсекаем), шаг 2 POST /2.0/name_standart -> ВСЕ телефоны из
телефонных полей + ВСЕ почты, в исходном порядке, без сортировки/обрезки
(паспорт/СНИЛС/счета телефоном не считаем). Фильтр ИНН-эхо для fallback.
Проверено живьём: АСТРЕЯ дир. по личному ИНН -> 37 телефонов + личная почта;
по ИНН фирмы возвращается ООО -> 0 (вход = личный ИНН директора).
2 платных запроса на фирму, только по отмеченным галочкам. Тесты вычищены
от реальных ПДн. 179 тестов зелёных.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-12 17:27:20 +03:00
Дмитрий 5a3746da9c fix(sales-finder): контакты по ИНН фирмы через POST /2.0/inn — живой запрос заработал
Выяснено живьём: сервис принимает POST с ключом+ИНН в теле, эндпоинт /2.0/inn
не inn_fl, ищет по 10-значному ИНН фирмы. GET с ключом в адресе давал 401,
inn_fl давал uncorrect format. Переделал contacts.py на POST с телом,
двухстрочный секрет адрес+тело, parse_config сам определяет POST/GET.
Поиск теперь по firm.inn, галочка активна при наличии ИНН фирмы.
Проверено сквозняком: Илатан 7709918259 -> реальные телефоны и почты в колонках.
167 тестов зелёных.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-12 16:25:13 +03:00
Дмитрий da9d8cf00e feat(sales-finder): сбор контактов телефон+e-mail по личному ИНН директора
Галочки в списке на прозвон -> кнопка Собрать контакты -> по director_inn
тянем телефон и e-mail через сторонний inn_fl API, 2 новые колонки + Excel.
Домен/ключ API живут только в secrets/inn_fl_url.txt со шаблоном {inn} -
в коде не зашиты. Ответ парсим по образцу regex, ИНН/ОГРН телефоном не считаем.
ПДн: сбор только по явной отметке, последовательно, ошибка одной фирмы не роняет пакет.
165 тестов зелёных.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-12 15:55:32 +03:00
Дмитрий fe08b5afcd feat(sales-finder): «ключи» → «запросов в рекламе» + HANDOFF с промтом продолжения
Владелец спросил «что значит ключ?» — «ключи» это жаргон Keys.so. По-человечески
это поисковые запросы, по которым фирма показывает объявления: чем их больше, тем
плотнее она сидит в рекламе. Переименовано в портале и в Excel, добавлено пояснение.

HANDOFF: актуальное состояние (портал, каталог 2ГИС, кластер, 7 починенных багов,
границы и запреты) + RESUME PROMPT для новой сессии.

146 тестов зелёных.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-12 14:06:44 +03:00
Дмитрий 4d937feb76 docs(sales-finder): HANDOFF — портал, каталог 2ГИС, кластер, 7 починенных багов, границы
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-12 13:51:02 +03:00
Дмитрий 13ea0f7254 fix(sales-finder): Keys.so спрашивали ВСЕГДА по московской базе — бюджеты врали, фирмы терялись
Корень сразу двух проблем, на которые указал владелец.

Проверено живьём (Дантистика, Красноярск):
    база msk (как спрашивали) → 1 ключ,   332 ₽/мес   ← мусор в списке
    база kry (правильная)     → 408 ключей, 1 213 837 ₽/мес  ← правда
Разница в 3600 раз.

Второе следствие: канал «кто крутит Директ в нише» тоже спрашивался по Москве,
поэтому местные рекламодатели к нам не попадали — так потерялись СК Корона
(korona24.ru, 9 ключей) и ЛидерДент (liderdent24.ru), которых 2ГИС не отдал.

🔴 КОВАРНО: на НЕИЗВЕСТНЫЙ код базы Keys.so не ругается, а молча отдаёт МОСКОВСКИЕ
цифры (проверил irk/khb/vld/bar/kem — все вернули московские). Поэтому список баз
ЗАКРЫТЫЙ (19 проверенных, взяты из рабочего робота ad-scan), а города вне него
получают base=None → Keys.so не спрашиваем вовсе и бюджет НЕ показываем.
Лучше прочерк, чем московские цифры под видом местных.

Также: семафор MAX_CONCURRENT=2 — Keys.so быстро отвечает 429 «слишком много
запросов» (напоролся при проверке).

146 тестов зелёных.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-12 13:20:23 +03:00
Дмитрий 92d0e7267a fix(sales-finder): подхватывали ИНН ХОСТЕРА вместо клиники
Нашёл владелец: у АРТ-Денталь (Красноярск) в списке стоял ИНН 7801451618 —
это ООО «Бегет», ПИТЕРСКИЙ ХОСТИНГ. А на странице «Контакты» у клиники два своих
юрлица: ООО «СТАРТ» 2466195937 и ООО «АРТ» 2462216093.

Причины:
 1) «Политику конфиденциальности» обходили РАНЬШЕ «Контактов» — а там сплошь и рядом
    прописаны реквизиты хостера/разработчика сайта. Брали первый ИНН и останавливались.
 2) regex спотыкался о «ИНН/КПП: 123/456» (после ИНН идёт кириллица).

Фикс:
 - приоритет страниц: Реквизиты/Контакты → О компании → Политика (последняя);
 - NOT_A_CLIENT_INN: чёрный список хостеров/конструкторов (Бегет, Селектел, Рег.ру,
   Таймвеб, Тильда, 1С, Яндекс, Мэйл) — такие ИНН не берём никогда;
 - extract_inns: берём ВСЕ ИНН страницы (у фирмы может быть несколько юрлиц),
   поддержан формат «ИНН/КПП»;
 - dadata.pick_inn_for_city: из кандидатов выбираем фирму В НАШЕМ ГОРОДЕ —
   чужой питерский хостер не пройдёт даже если просочится.

140 тестов зелёных.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-12 13:13:10 +03:00
Дмитрий 61e11f90ea fix(sales-finder): теряли ПОЛОВИНУ фирм — сайт брали только из листинга 2ГИС
Владелец сравнил с выгрузкой старого робота: по Красноярску тот нашёл 36 фирм,
мы — 13. В логе видно почему: «собрано 49, без сайта 25» — половина отсеивалась.

Причина: сайт брали ТОЛЬКО со страницы результатов поиска 2ГИС, а там ссылка на
сайт есть не у всех — у многих она только в КАРТОЧКЕ фирмы. Проверено:
Реновацио → renovacio-med.ru, Дентал Эксперт → dental-expert24.ru,
Дентоша → dentosha.ru — в листинге сайтов нет, в карточке есть.

Фикс: twogis.site_from_card + шаг resolve_site в пайплайне — для фирм без сайта
открываем карточку (xfetch, параллельно, потолок 3) ДО отсева «без сайта».
Попутно: otello.ru (виджет, висит в каждой карточке 2ГИС) — в чёрный список.

134 теста зелёных.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-12 12:56:39 +03:00
Дмитрий a0fb1994f7 feat(sales-finder): ОГРН и юр. адрес — больше не выбрасываем
Владелец сравнил с выгрузкой старого робота: там есть ОГРН и адрес, у нас нет.
Причина глупая: ДаДата их отдаёт, парсер их читает — а в модели Firm полей не было,
и данные молча терялись.

Firm.ogrn + Firm.address; заполняются в обоих путях ДаДаты (по ИНН и по названию).
В Excel добавлены столбцы «ОГРН» и «Юр. адрес».

129 тестов зелёных.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-12 12:54:32 +03:00
Дмитрий cab8f5f51e fix(sales-finder): ФНС теряла директоров — параллельный долбёж + кэширование неудач
Симптом (нашёл владелец): у 7 из 15 фирм ИНН фирмы есть, а директора нет.

Причина 1: ходили к ФНС в 8 потоков — она отбивается и отдаёт пустоту.
Проверено: те же ИНН по одному отдаются нормально (АСТРЕЯ → Коржова Т.С.,
ИНН 246403602419; Клиника практ. стом. → Джаватханов Д.И., ИНН 246508334975).

Причина 2 (хуже): пустой ответ КЭШИРОВАЛСЯ — фирма навсегда оставалась без
директора даже при пересборе.

Фикс:
 - семафор MAX_CONCURRENT=1 + пауза 0.7с: к ФНС ходим по одному, вежливо;
 - кэшируем ТОЛЬКО удачу; осевшие пустышки выбрасываем при чтении;
 - из кэша вычищено 16 отравленных записей.

129 тестов зелёных.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-12 12:09:04 +03:00
Дмитрий 1ee0e94da4 fix(sales-finder): за сайт фирмы принимались мессенджеры и виджеты записи
Нашёл владелец: у Crystal dent сайт значился max.ru (мессенджер Max), у «Алекуса» —
api.whatsapp.com. В московском списке так же криво: medlineservice.infoclinica.ru,
dentol.infoclinica.ru, n2317362.yclients.com — это виджеты онлайн-записи, а не сайты.

Причина: в чёрном списке не было Max, WhatsApp, платформ записи (yclients, infoclinica,
medflex, dikidi) и он не учитывал ПОДДОМЕНЫ (api.whatsapp.com ≠ whatsapp.com).

Фикс: список расширен (мессенджеры, соцсети, виджеты записи, агрегаторы) + проверка
по суффиксу домена. Если первая ссылка — мусор, берём следующую (настоящий сайт).

126 тестов зелёных.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-12 11:58:40 +03:00
Дмитрий 9a0cbd2226 fix(sales-finder): строчка лога роняла ВЕСЬ прогон (UnicodeEncodeError на cp1251)
Симптом: «Не удалось собрать список» без единой подсказки почему.
Причина: консоль Windows в cp1251; print с символом «→» кидал UnicodeEncodeError,
исключение вылетало из run_pipeline и убивало сбор. Сам сбор при этом работал
(прямой запуск с PYTHONIOENCODING=utf-8 отрабатывал нормально) — поэтому и не
воспроизводилось.

Фикс:
 - app: sys.stdout/stderr переводим в utf-8 (errors=replace);
 - pipeline: вызов deps['log'] обёрнут в try/except — лог НИКОГДА не роняет прогон;
 - стрелка «→» в сообщении заменена на ASCII '->';
 - _do_run больше не глотает исключение молча — печатает traceback.

Регрессионный тест: падающий лог не мешает списку собраться.
Проверено вживую: прогон «Частные стоматологии / Красноярск» — 18 фирм, DONE.

121 тест зелёный.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-12 11:43:45 +03:00
Дмитрий cf8e0201d5 feat(sales-finder): каталог 2ГИС (19 разделов, 100 ниш) + города по макрорегионам + кластер
НИШИ — реальный рубрикатор 2ГИС (снят 12.07.2026 через xfetch), не выдуманный:
19 разделов (Медицина, Красота, Ремонт-стройка, B2B-услуги, Автосервис...),
100 ниш. У денежных ниш — синонимы-запросы (стоматология / стоматологическая
клиника). NB: 2ГИС на странице раздела показывает только верхние подрубрики,
глубже — за «Ещё».

ГОРОДА — столицы регионов по макрорегионам: Москва | Подмосковье | Питер |
Ленобласть | ЦФО | СЗФО | ЮФО | СКФО | ПФО | Урал | Сибирь | ДФО.
Москва отделена от Подмосковья, Питер от Ленобласти (иначе кластер разрастается).
🔑 Слаги СВЕРЕНЫ с реальным списком проектов 2ГИС — 21 из них были неверны:
khabarovsk (не habarovsk), n_novgorod, ulanude, novokuznetsk, p_kamchatskiy,
kh_mansiysk, yoshkarola, lyubertsy, khimki... Майкоп в 2ГИС не найден.

КЛАСТЕР: пункт «★ ВЕСЬ РЕГИОН» гоняет прогон по всем городам группы, у каждой
фирмы свой город (колонка «Город»). В интерфейсе честно показано, сколько
городов и примерное время.

120 тестов зелёных.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-12 11:20:07 +03:00
Дмитрий f91e206411 feat(sales-finder): колонка «Город» в списке и в Excel
Firm.city — город сбора по-русски (из cities.py по слагу 2ГИС). Проставляется
в пайплайне всем фирмам прогона. Видно в таблице портала и в выгрузке —
чтобы при сводной выгрузке нескольких городов было понятно, кто откуда.

107 тестов зелёных.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-12 11:01:25 +03:00
Дмитрий 11a1627bc3 feat(sales-finder): наверх — кто ГОТОВ К ЗВОНКУ (директор + его ИНН)
Главный ключ сортировки теперь «полностью опознан»: есть ФИО директора И его
личный ИНН. Такие идут выше всех, даже если холоднее и беднее — по ним звонок
можно делать прямо сейчас.

Порядок: готов к звонку → горячесть → есть хотя бы ФИО → каналы → бюджет.

Эффект на живом списке: 25 «готовых» занимают позиции 1-25; Ройтберг
(3,8 млн ₽/мес) и К+31 (8,5 млн) уходят за границу — по ним звонить некому.

106 тестов зелёных.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-12 10:57:16 +03:00
Дмитрий 06298b7394 feat(sales-finder): порядок списка — горячесть → директор → каналы → бюджет
Сортировка в 4 ключа (как задал владелец):
 1) горячесть
 2) есть ли директор — кому звонить важнее, чем сколько тратит
 3) в скольких каналах рекламируется (шире реклама — выше)
 4) бюджет Директа

Эффект: К+31 с самым большим бюджетом (до 8,5 млн ₽/мес) уходит вниз,
потому что директор не найден — звонить некому.

104 теста зелёных.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-12 10:42:44 +03:00
Дмитрий f23ac88199 perf(sales-finder): отсев мусора до дорогих шагов + параллельность (xfetch max 3)
Было: всё строго последовательно, и тяжёлое обогащение (обход страниц
реквизитов, ДаДата, выписка ЕГРЮЛ) тратилось даже на фирмы без сайта и на
тех, кто нигде не рекламируется.

Стало — этапы с отсевом:
 1. сбор (2ГИС параллельно по страницам ∥ Keys.so) → свод
 2. ОТСЕВ: нет сайта → выкидываем (проверять и обогащать нечего)
 3. ПАРАЛЛЕЛЬНО дёшево: бюджет Директа + скан главной → «рекламируется?»
 4. ОТСЕВ: нигде не рекламируется → не наш клиент
 5. ПАРАЛЛЕЛЬНО дорого: реквизиты→ИНН, ДаДата, ЕГРЮЛ — только по оставшимся
 6. ранг

parallel.py: pmap (потоки, падение задачи → None, прогон не рушится).
xfetch: глобальный семафор MAX_CONCURRENT=3 — любой параллельный код упирается
в потолок сервиса (тест проверяет пик ≤ 3).
twogis: collect_two_gis_parallel — страницы рубрик тянем разом.
pipeline: deps['log'] сообщает, сколько отсеяно (без тихих обрезаний).

Слой A переехал из scan_ad в enrich; главная страница кэшируется и
переиспользуется (не качаем дважды).

100 тестов зелёных.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-12 10:34:01 +03:00
Дмитрий 37a33a36ef feat(sales-finder): слой D — ЛИЧНЫЙ ИНН руководителя из выписки ЕГРЮЛ ФНС
ДаДата личный ИНН директора НЕ отдаёт: поле managers в ответе есть, но
приходит null (нужен другой платный тариф). В management только ФИО+должность.

Источник — официальная выписка ЕГРЮЛ с egrul.nalog.ru: бесплатно, без капчи
(captchaRequired=false), раздел «Сведения о лице, имеющем право без доверенности
действовать от имени юрлица» содержит ФИО, ЛИЧНЫЙ ИНН и должность.
Проверено: Илатан 7709918259 → Костышев Артур Вадимович, ИНН 561506859802.

egrul.py: parse_director (чистая, тестируется на тексте), fetch_vypiska (PDF,
кэш по ИНН), enrich_director. У ИП ИНН фирмы = личный ИНН человека (12 цифр) —
в ФНС не ходим. Капчу НЕ обходим: если ФНС просит — возвращаем пусто.
ФИО/должность из ЕГРЮЛ официальные — уточняем ими данные ДаДаты.

Колонки: «ИНН директора» и «Должность» (в портале — под ФИО, в Excel — столбцы).

ПДн: ИНН физлица — персональные данные (152-ФЗ), строже ИНН фирмы.
Инструмент внутренний; нужна ПДн-проверка перед реальной эксплуатацией.

91 тест зелёный. Добавлена зависимость pypdf (чтение выписки).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-12 10:22:10 +03:00
Дмитрий a99cdee692 chore(sales-finder): убрать случайно закоммиченные __pycache__/*.pyc
git add -f по каталогу переопределил .gitignore и затянул скомпилированный мусор.
Впредь форсить только конкретные файлы.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-12 10:08:53 +03:00
Дмитрий 177dbd961d feat(sales-finder): визуал портала + колонки «Где рекламируется» и «Бюджет Директа»
Портал (web/ui.py, палитра Forest): экран выбора ниши+города, экран списка
с таблицей, крутилка на время сбора, «Последние списки», кнопка Excel.

Бюджет Директа — ТЯНЕМ (раньше не тянули вовсе): keyso.fetch_domain_context
через domain_dashboard (порт adscan/keyso_api.py): adkeyscnt/adscnt/adcost →
вилка ₽/мес (average…bdg100). Забираем ДО решения «рекламируется?», иначе
фирма с бюджетом, но без следов на сайте, попадала в «нет» (так терялся К+31
с бюджетом до 8,5 млн ₽/мес).

Колонка «Где рекламируется» — adscan.channels_from: Директ / коллтрекинг /
пиксели. Колонка «Бюджет Директа, ₽/мес» — вилка + число ключей.

Ранкер: деньги стали главным весом (до +50 за бюджет). Ложная метка
two_gis_promoted УБРАНА из оценки — парсер ставил её на всю страницу, а не
на фирму, и все получали одинаковые 90. Вместо голой цифры — подпись
горячая/тёплая/холодная.

Проверено живьём: НоваДент 3,2–7,3 млн; К+31 3,6–8,5 млн; Илатан 0,57–1,3 млн;
Дантистофф 0. 82 теста зелёных.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-12 10:08:06 +03:00
Дмитрий 9c01dc162d docs(sales-finder): HANDOFF — слои A+B добавлены, C отклонён, хвосты по скорости
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-12 09:46:17 +03:00
Дмитрий bdbf5f415c feat(sales-finder): слои A+B для охвата ИНН+директора
Слой A (requisites.py): если ИНН нет на главной сайта — ищем страницы
реквизитов/политики конфиденциальности/лицензии/контактов и достаём ИНН
оттуда (по 152-ФЗ ИНН обязан быть на «Политике»). Надёжно, точно.

Слой B (dadata.enrich_by_name): если ИНН так и нет — ищем в ДаДате по
названию+городу с защитой best_match (только действующие с совпадением
города). Директор по названию ВСЕГДА помечается «проверьте» (совпадение
может быть чужой фирмой — напр. Чудо Доктор→Аптека Чудо Доктор).

cities.py: слаг города (латиница, для 2ГИС) → русское имя (для ДаДаты).
Реквизит-страницы тянем только быстрым requests (без медленного xfetch).

Слой C (бесплатные порталы по телефону) отклонён — проверен через xfetch,
контроль провалился, отдаётся нестабильно.

Проверено живьём: слой A дал ИНН+ФИО Доброму доктору, Дентал Фэнтези,
Белгравии и др. со своих сайтов; слой B — Дантистофф/Медлайн/СМ-Стоматология
с пометкой. 61 тест зелёный.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-12 09:45:21 +03:00
Дмитрий 371f171ebd docs(sales-finder): HANDOFF-состояние v1 + resume-промт (2ГИС через xfetch работает)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-12 08:58:37 +03:00
Дмитрий 082d258baa feat(sales-finder): 2ГИС через сервис рендера xfetch + телефон/ИНН с сайта → живой список
Живая проверка 12.07: xf4.ru/fetch рендерит 2ГИС (обход антибота) → парсер листинга
(порт ChannelA) даёт фирмы+сайты. Телефон и ИНН снимаем структурно с сайта (tel:/schema),
директор — ДаДата по ИНН. Сквозной прогон 'стоматология москва': 12 фирм, телефон 10/12,
директор 4/12, за ~43с. 42 теста зелёные. allowlist gitleaks для тест-телефонов sales-finder.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-12 08:52:44 +03:00
Дмитрий 5c6b7280d7 fix(sales-finder): Keys.so парсер под реальную форму ads[] + связка сигналов Директ+сайт
Живая проверка 12.07: keyword_dashboard отдаёт ads[] (список блоков объявлений
с domain/header/bid), а не data[]. Парсер переписан на рекламодателей ниши.
Пайплайн объединяет Директ-сигнал Keys.so с сайтовым (не затирает). 32 теста зелёные.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-12 08:28:40 +03:00
Дмитрий efa2fc6a9d test(sales-finder): регрессионный тест выгрузки /run/{id}/export
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-12 08:13:58 +03:00
Дмитрий 5b3b920c6b feat(sales-finder): веб-слой портала (экраны + прогон + выгрузка)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-12 08:06:44 +03:00
Дмитрий 7dd796f7d5 feat(sales-finder): строки прозвона + выгрузка Excel/CSV
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-12 08:02:46 +03:00
Дмитрий c50a26e2e9 feat(sales-finder): хранилище прогонов + оркестратор пайплайна
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-12 07:59:33 +03:00
Дмитрий 4e90b15a95 feat(sales-finder): ранг горячести из трёх сигналов
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-12 07:56:06 +03:00
Дмитрий e675ff7630 feat(sales-finder): обогащение через ДаДату (ИНН/директор/статус)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-12 07:54:25 +03:00
Дмитрий 2faeaecb8c feat(sales-finder): детектор рекламы (порт signatures + перехват запросов)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-12 07:51:34 +03:00
Дмитрий 07a3167f43 feat(sales-finder): сведение и дедуп фирм из двух источников
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-12 07:49:09 +03:00
Дмитрий 239e92d5d9 feat(sales-finder): Keys.so ниша→рекламодатели (keyword_dashboard)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-12 07:46:37 +03:00