В 1997 психолог Артур Арон усадил двух незнакомцев напротив друг друга и дал список из 36 вопросов. Они шли по нарастающей: от «с кем бы ты хотел поужинать» до «когда ты последний раз плакал при другом человеке». В конце — четыре минуты молча смотреть в глаза. Часть пар после эксперимента сошлась. Одна — поженилась.
Вопросы сближают. Но только когда отвечают двое и слышат друг друга, а не подсматривают и не подстраиваются. Я взял этот принцип и собрал на нём продукт. Собрал нейронками — я ИИ-консультант и строю тем же инструментом, который продаю. Но архитектуру, развилки и прод-реальность тащу я, а не нейронка. Спроектировал, довёл до прода, продолжаю вести. Бот живёт под именем «Ближе», работает каждый день и делает ровно то, ради чего сделан: двое узнают друг друга глубже.
Кейс не про «сделал бота». Кейс про то, как я довожу AI-продукт до прода нейронками — и что начинается ПОСЛЕ запуска. Потому что «после запуска» как раз и отделяет работающий продукт от папки на гитхабе.
Слепое взаимное раскрытие — это механика, при которой оба партнёра отвечают на один вопрос порознь, и ответ другого открывается только когда ответили оба. Никто не видит чужой ответ заранее и потому не может под него подстроиться — каждый отвечает честно. Это ядро продукта и главный инвариант, вокруг которого построено всё остальное.
Зачем нужен бот и почему он работает?
Цель простая: чтобы пара после бота знала друг о друге больше, чем до. Не «поиграли и забыли», а «о, не знал, что ты так думаешь».
Этот момент не случайность. Он заложен в механику. Ядро — слепое взаимное раскрытие: оба отвечают на один вопрос порознь, и ответ партнёра открывается только когда ответили оба. Никто не видит чужой ответ заранее, а значит, не может под него подстроиться. Каждый отвечает честно — и потому раскрытие бьёт.
Цель достигается не уговорами, а архитектурой. В этом и кейс.
Что у бота внутри?
Коротко: связка в пару, сессии вопросов с нарастанием близости, вопрос дня, игра «угадай ответ» и стрики. По пунктам:
- связка в пару по коду или по пересылаемой ссылке: диплинк → один клик → вы в паре
- сессии по 5 вопросов с нарастанием близости, 4 настроения на 3 уровнях, 128 вопросов
- раздел «Страсть» открывается только по обоюдному согласию
- вопрос дня каждый день, с умным напоминанием тому, кто молчит
- игра «угадай ответ»: сначала отвечаешь сам, потом угадываешь партнёра
- стрики, история раскрытых ответов, админка со статистикой и рассылкой
Как устроена архитектура?
Главное решение — развести чистую логику и обвязку Telegram. Вся суть продукта живёт в модулях без единого импорта телеграма, и потому полностью под тестами. Телеграм — тонкий слой сверху.
app/
чистая логика (без Telegram, под тестами)
pairing.py связка пары, код, разбор приглашения
reveal.py правило раскрытия: видно только когда ответили оба
sessions.py подбор вопросов по нарастанию близости
streaks.py стрики и их пересчёт
daily.py расписание и выбор свежего вопроса дня
данные
db.py SQLite-слой, схема, миграции
обвязка Telegram (тонкая)
handlers/ старт, сессия, вопрос дня, админка, роутер
runner.py сборка, регистрация, фоновый планировщикПочему так, а не «всё в одном файле»? Потому что продукт про правила, а правила надо проверять. Когда логика отделена от телеграма, её гоняют тесты — и при любой правке раскрытие, стрики и подбор вопросов не разъезжаются.
Какие решения и сделали продукт?
Кейс не в том, что «сделали бота». Кейс в решениях.
Слепое раскрытие как инвариант
Ответ партнёра не виден, пока не ответили оба. Одно правило в одной функции, весь продукт построен вокруг него.
Одна активная сессия на пару
Чтобы пара не открыла три разговора разом и не запуталась. Простой инвариант, который снимает целый класс багов.
База под реальные условия
Сначала данные жили во внешней облачной базе. На сервере она дышала через раз: запрос то 0.3 секунды, то 14, то таймаут. Перенёс на локальный SQLite рядом с ботом — стало 0.0003 секунды. Урок: считаться с реальной инфраструктурой, а не с идеальной.
Отказ от лишнего
Был соблазн сделать режим «отвечать с одного телефона по очереди». Убил: он размывает ядро, слепое раскрытие работает только когда у каждого свой экран. Вырезал на уровне замысла, а не доделывал ненужное.
Чем сборка от архитектуры отличается от сборки от функций?
Сборка от функций гонится за количеством возможностей и надеется, что одна выстрелит. Сборка от архитектуры выбирает одну механику-ядро и подчиняет ей всё остальное — «Ближе» собран так.
| От функций | От архитектуры (этот проект) | |
|---|---|---|
| Откуда берётся результат | Больше функций, авось одна выстрелит | Одна механика-ядро, всё работает на неё |
| Как решается объём | Делать всё, что просится | Резать то, что не служит ядру |
| Логика ядра | Сплетена с платформой | Отделена, под тестами |
| Поведение на краях | Латается баг за багом | Держится инвариантами |
| Роль автора | Кодер запросов | Тот, кто ведёт систему |
Продукт сдан. Тут работа и начинается
«Бот готов» — сказал бы тут другой и закрыл проект. Живой тест прошёл, ответ партнёра открывался мгновенно. Я выдохнул. Рано.
Через пару недель я полез в живую базу — просто посмотреть, как идут реальные диалоги. И увидел: один ответ лёг не под тот вопрос. Не «теоретически может», а вот он, в данных. Картинка собралась быстро: человеку приходил новый вопрос дня, а он отвечал на старый, который висел выше в чате — и ответ уезжал не туда.
Чинится это не заплаткой. Чинится двумя решениями:
- Ответ привязан к тому вопросу, который человеку реально показали — а не к «текущему вопросу пары». Telegram не говорит, на что именно отвечают, поэтому привязку держу я.
- Полу-готовый вопрос больше не подменяется. Если один ответил, а второй молчит — там живёт запертый ответ, который ждёт второго. Раньше планировщик мог его затереть новым. Теперь не трогает.
А раз уж залез — докрутил то, ради чего продукт и живёт, удержание двух людей:
- стрик прощает один пропущенный день: одна случайная пропажа не обнуляет накопленное
- замолчавшую пару бот мягко возвращает лесенкой, а не нудит в пустоту
- как только один ответил — второму сразу летит «партнёр ответил, открой и увидишь»
- догадку можно пропустить, раунд раскроется и без неё
- совпала догадка с ответом — отдельный маленький праздник в раскрытии
Было 62 теста. Стало 108.
Вот это и есть разница между «сдал и забыл» и «веду». Баг нашёл я — читая живую базу руками. А не клиент, у которого бот «почему-то путает вопросы».
И тут важное. Код написала нейронка, быстро. Но нейронка не пойдёт сама читать живую базу и ловить, что один ответ лёг не под тот вопрос. Не примет решение, чем привязку держать. Не выкатит и не проверит на живых данных. Код теперь дешёвый — это умеет каждый. Дорого другое: довести его до того, чтобы он реально работал на людях. Вот это и есть моя работа.
Это реально работает?
Да. Всё в проде. Развёрнуто в Docker, база локальная, работает каждый день. 108 автотестов зелёные. Код модульный, с разделением слоёв. Найденный в бою баг закрыт, удержание докручено, изменения выкачены и проверены на живых данных.
Что это демонстрирует?
Это витрина того, как я работаю. Не «накидать функций», а:
- развести чистую логику и обвязку, чтобы суть продукта была под тестами
- держать инварианты вместо латания багов поодиночке
- считаться с реальной инфраструктурой, а не с идеальной
- довести до прода — и не бросить там, а вести по живым данным
И связать всё это с целью. Бот существует не ради фич, а ради того, чтобы двум людям стало теплее. Архитектура тут не самоцель, она работает на близость.
Что я предлагаю?
Беру вашу идею и довожу до работающего AI-продукта в проде. А потом веду его — потому что продукт, который не ведут, тихо умирает.
Кому это
- Бизнесу с аудиторией — коучам, школам, комьюнити, сервисам. Бот, который вовлекает и удерживает людей, а не «ещё один чат, который полистали и забыли».
- Фаундерам — MVP, который не стыдно показать инвестору и пользователю. С архитектурой, тестами и в проде, а не «работает на моей машине».
Как устроено
- Сборка под ключ нейронками — от идеи до прода, быстро. Я строю тем же инструментом, что продаю. Но направляю его, ловлю то, что он проморгал, и довожу до работающего: чистая логика отделена от обвязки, покрыта тестами, развёрнута.
- Саппорт на MRR — я не исчезаю после запуска. Слежу за живыми данными, нахожу дыры раньше пользователей, докручиваю то, что держит людей в продукте.
Почему именно так. Половина «готовых» продуктов умирает не от плохого кода, а от того, что их сдали и бросили. Баг находит пользователь. Удержание никто не считает. Лента замолкает, и все делают вид, что так и было. Я закрываю ровно этот разрыв: довожу до прода и остаюсь рядом, пока продукт живёт.
«Ближе» — не демо «на коленке». Это работающий продукт, на котором видно весь мой подход: от слепого раскрытия как инварианта до бага, пойманного в живой базе руками.
Есть идея, которую пора довести до прода?
Или продукт, который запустили и забросили? Напишите. Гляну, скажу честно: что реально сделать, а что звездёж.
