Filipp Mikheev Product engineer · full cycle Разработчик полного цикла

Four systems in production · sole developer on each

Четыре системы на проде · в каждой один разработчик

Systems that hold money, roles and other people's data.

Системы, в которых деньги, роли и чужие данные.

I take a problem from the first conversation to a running service and stay for the incidents. Backend, interfaces, bots, AI agents, deployment — on each project I was the only developer.

Веду задачу от первого разговора до работающего сервиса и остаюсь на инцидентах. Сервер, интерфейсы, боты, ИИ-агенты, выкладка — в каждом проекте я единственный разработчик.

Ten years in audit before engineering. It shows in what I check twice: the arithmetic of money, who may see which number, and what happens to someone else's data during a migration.

До разработки — десять лет в аудите. Это видно по тому, что я проверяю дважды: арифметику денег, права доступа к суммам и судьбу чужих данных при переносе.

4systems in productionсистемы на проде
10 851automated testsавтотеста написано
10years in auditлет в аудите
0seconds of downtimeсекунд простоя

Readings from production

Показания с прода

01 — 05

Client names, domains and personal data withheld. Sector, scope, decisions and volumes are given as they are.

Названия заказчиков, домены и персональные данные не раскрыты. Отрасль, состав работ, решения и объёмы — как есть.

Three breaches nobody could find — and the shop that stopped falling over

Три взлома, которые никто не мог найти, и магазин, переставший падать

In production На проде E-commerce · 2026

A shop on an end-of-life platform, ~13 000 products. Reinfected three times in a month. Two previous investigations found nothing.

Магазин на снятой с поддержки платформе, около 13 000 товаров. Трижды за месяц возвращалось заражение. Два предыдущих разбора ничего не нашли.

  • Signature scans were clean and the request log was empty, yet the infection came back after every cleanup. I ran a watcher snapshotting container processes twice a second with the full parent chain — 3 611 hits in a day — and matched launch times against the web server log.
  • Сигнатурные проверки давали ноль, в журнале запросов было пусто, а заражение возвращалось после каждой чистки. Поставил наблюдателя, снимавшего процессы контейнера дважды в секунду с полной цепочкой родителей — 3 611 срабатываний за сутки — и сопоставил моменты запуска с журналом веб-сервера.
  • The first malicious process had the PHP handler as its parent while the log was clean for that window. So the entry bypassed the web server. A port check confirmed it: the FastCGI service port was listening on all interfaces, reachable from the internet — arbitrary code execution with web-server rights, around nginx.
  • У первого вредоносного процесса родителем был обработчик PHP, а в журнале за это окно было чисто. Значит вход шёл мимо веб-сервера. Проверка портов подтвердила: служебный порт FastCGI слушал все интерфейсы и был доступен из интернета — выполнение произвольного кода с правами веб-сервера в обход nginx.
  • Closed and pinned with firewall rules wrapped in a system unit so they survive a reboot. Miner and traffic-resale processes removed, fail2ban installed, PHP error logging enabled where there had been none.
  • Закрыто и закреплено правилами брандмауэра, оформленными системной службой, чтобы пережить перезагрузку. Майнер и сервисы перепродажи трафика сняты, поставлен fail2ban, включено журналирование ошибок PHP, которого не было вовсе.
  • A replacement platform built in parallel. The redirect map first matched 0 of 11 055 live sitemap URLs; after fixing the address scheme, all 11 055. Search positions survive the move.
  • Параллельно собрана замена платформы. Карта переадресаций сначала совпадала с 0 из 11 055 адресов живой карты сайта, после исправления схемы — со всеми 11 055. Позиции в поиске переезд переживают.

Server load average

Средняя нагрузка на сервер

beforeбыло 9.70
afterстало 0.27

on 2 cores · 36× lower

на двух ядрах · в 36 раз ниже

Product page response

Ответ товарной страницы

beforeбыло 11.6 s
afterстало 0.5 s

template rebuild on every request removed

убрана пересборка шаблонов на каждый запрос

Crawler response (62% of traffic)

Ответ поисковым роботам (62% трафика)

beforeбыло 8.6 s
afterстало 0.6 s

cache for bots only — carts stay intact

кэш только для роботов — корзина не ломается

Old URLs kept alive

Сохранено старых адресов

first tryсначала 0
fixedпосле 11 055

of 11 055 in the live sitemap

из 11 055 в живой карте сайта

0 sec downtimeсек простоя 12 996 products migratedтоваров перенесено 354 testsтеста 13 tables vs 373 beforeтаблиц против 373

Python · FastAPI · SQLAlchemy · PostgreSQL · Alembic · Pillow · React · TypeScript · Docker Compose · nginx · iptables · fail2ban · PrestaShop / MariaDB on the migration side

Grocery delivery: an office panel and two storefronts inside messengers

Доставка продуктов: панель офиса и два магазина внутри мессенджеров

In production На проде Retail · 2026

The service ran on chat messages and spreadsheets. Orders, operators, purchasing, delivery and money had to become one system — without taking the familiar messenger away from the customer.

Служба работала на переписке и таблицах. Заказ, операторов, закупку, доставку и деньги нужно было собрать в одну систему, оставив клиенту привычный вход через мессенджер.

  • Two storefronts with identical capability. Telegram on aiogram, and a second messenger with no ready library at all — I wrote the API client from scratch.
  • Два бота-магазина с одинаковыми возможностями. Telegram на aiogram и второй мессенджер, где готовой библиотеки нет вообще — клиент к его API написан с нуля.
  • Order numbers that finally agreed. The bot showed one number and the panel another for the same order. Day-scoped numbering is now issued atomically under a lock and pinned by a database constraint; 383 historic orders renumbered in one query.
  • Номера заказов, которые наконец сошлись. Бот показывал один номер, панель — другой на тот же заказ. Нумерация внутри дня выдаётся атомарно под блокировкой и закреплена ограничением в базе; 383 старых заказа пронумерованы одним запросом.
  • Customer money as a ledger. Every balance change writes an entry under a row lock, so there is a history rather than a number. Cash reconciliation on handover is idempotent — pressing twice does not double the sum.
  • Деньги клиента — журналом проводок. Правка баланса идёт под блокировкой строки и всегда пишет проводку, поэтому есть история, а не просто число. Сверка наличных при выдаче идемпотентна — повторное нажатие не удваивает сумму.
  • Risky features ship switched off. Group purchasing — a wholesale box split between customers by weight — went to the server behind a settings flag, so production stayed untouched until the client decided.
  • Опасное выкатывается выключенным. Совместная закупка, где коробка делится между клиентами по весу, выложена за признаком в настройках — прод не тронут до решения заказчика.
648 testsтестов ~600 buyersпокупателей 22 migrationsмиграции 2 messengers, one feature setмессенджера, один набор функций

Python 3.12 · FastAPI · SQLAlchemy async · Alembic · PostgreSQL · Redis · aiogram · custom messenger API client · React 18 · TypeScript · Ant Design · Vite · TanStack Query · Docker Compose · nginx

Cleaning company: office CRM and a field app inside Telegram

Клининг: CRM офиса и приложение исполнителя в Telegram

Delivered Сдана заказчику Field services · 2026

One-off consumer jobs and contracted commercial sites lived in spreadsheets and chats. Off-the-shelf systems did not fit the settlements with field staff or the access rules around money.

Разовые заказы и объекты на договоре велись в таблицах и мессенджерах. Коробочные решения не ложились на расчёты с исполнителями и на разграничение доступа к деньгам.

  • The field worker's wallet is a ledger. The balance always equals the sum of entries and is never stored as a field. The original bug: the sign came from the button, so handing in cash doubled the debt instead of clearing it. Now the button sets direction, the operation decides the sign, and the resulting balance is shown before confirmation.
  • Кошелёк исполнителя — журнал проводок. Баланс всегда равен сумме операций и отдельным полем не хранится. Исходная ошибка: знак брался от кнопки, поэтому сдача наличных удваивала долг вместо погашения. Теперь кнопка задаёт направление, знак определяет сама операция, а перед подтверждением показан будущий баланс.
  • Access to money enforced on the server, not in the interface — including the side doors: the calculator returns figures without sums, Excel exports, PDF quotes, the site card, the field app. The "view as role" mode had been swapping only the menu while data still came back under the manager's real role, leaving the sums on screen.
  • Доступ к суммам закрыт на сервере, а не в интерфейсе — вместе с обходными путями: калькулятор отдаёт расчёт без сумм, выгрузки в Excel, коммерческие предложения в PDF, карточка объекта, приложение исполнителя. Режим «посмотреть глазами роли» подменял только меню, а данные приходили под настоящей ролью управляющего, и суммы оставались на экране.
  • Two years of the client's data migrated. Traps found on live data: sums computed by spreadsheet formulas arrived as 6094.999999999999 and, once rounded, the order stopped recognising itself on a re-run; a crew-lead flag written as a word tagged the whole crew; and two near-identical station names turned out to be different places a kilometre apart.
  • Перенесена база заказчика за два года. Ловушки на живых данных: суммы из формул приходили как 6094,999999999999 и после округления заказ переставал узнавать сам себя при повторном прогоне; признак бригадира словом в ячейке метил всю бригаду; два почти одинаковых названия станций оказались разными местами в километре друг от друга.
  • Two-way phone system integration. One authenticated entry point takes four event types: call log with recording, the customer card popping up while the phone is still ringing, a routing reply naming the responsible manager, and the call rating. Outbound: dial by click.
  • Двусторонняя связь с виртуальной АТС. Одна точка входа с проверкой ключа принимает четыре типа событий: звонок в журнал с записью, всплывание карточки клиента пока телефон ещё звонит, ответ с ответственным менеджером для автоперевода и оценку разговора. Обратно — звонок по клику.
1000+ testsтестов 6 rolesролей 4 acceptance roundsкруга приёмки ~550 customers migratedклиентов перенесено

Python 3.11 · FastAPI · SQLAlchemy 2.0 · Alembic · PostgreSQL 16 · Redis · arq · aiogram · MinIO · PyJWT · reportlab · openpyxl · React · TypeScript · Ant Design · Telegram Mini App · Docker Compose · Caddy

Country estate: an installable app that reads receipts and listens

Загородная резиденция: приложение, которое читает чеки и слушает

In daily use В ежедневной работе Household · 2026

The owner and the estate manager tracked expenses, tasks and purchases in chat and on paper. They needed something that opens from a phone icon, works without an app store, and makes sense to a non-technical person.

Владелица и управляющий вели расходы, задачи и закупки в переписке и на бумаге. Нужно было приложение, которое открывается с телефона как иконка, работает без магазина приложений и понятно человеку без технического опыта.

  • Push notifications that had never once arrived. Subscriptions were created, the key was served, the send error was silently swallowed — a convincing illusion of a working feature. The private key was stored in one format and the library accepted only another. I wrote a converter for any format, added a self-check button, and caught it end-to-end on a real phone rather than a stub.
  • Уведомления, которые не доходили ни разу. Подписки создавались, ключ отдавался, ошибка отправки молча гасилась — убедительная иллюзия рабочей функции. Закрытый ключ хранился в одном формате, а библиотека принимала только другой. Написал приведение ключа любого формата, добавил кнопку самопроверки и поймал это на живом телефоне, а не на заглушке.
  • Receipts read by a chain of recognisers. Neural providers first, local Tesseract if they are unreachable, a retry on a straightened copy if nothing legible came back. Recognised line items attach themselves to the pending grocery order on a confident match.
  • Чеки разбираются цепочкой распознавателей. Сначала нейросетевые провайдеры, затем локальный Tesseract, если внешние недоступны, и повтор на выправленной копии, если ничего внятного не вышло. Позиции сами привязываются к ожидающему заказу при уверенном совпадении.
  • Voice input moved to the server. Browser speech recognition behaved unpredictably on iOS, so audio is transcribed with Whisper server-side and works the same on every phone.
  • Голосовой ввод перенесён на сервер. Браузерное распознавание вело себя непредсказуемо на iOS, поэтому запись расшифровывается через Whisper на сервере и работает одинаково на всех телефонах.
70 testsтестов 23 screens verifiedэкрана проверено 3 rolesроли 0 app stores involvedмагазинов приложений

Python 3.12 · FastAPI · PostgreSQL · React · TypeScript · Ant Design · PWA (Workbox, Service Worker) · Web Push (VAPID) · Whisper · Tesseract · multi-provider LLM fallback · Docker Compose · Caddy

An AI engine that audits financial statements

Движок, который проверяет отчётность с ИИ

Flagship · built solo Флагман · построен один AI · 2026

Auditors went through accounting exports by hand to find unusual entries. Reviewing one period took days. I spent ten years doing exactly this work, so the methodology here is mine rather than researched — and the system has grown to industrial size.

Аудиторы вручную перелопачивали бухгалтерские выгрузки, чтобы найти нетипичные операции. Проверка одного периода занимала дни. Я десять лет делал ровно эту работу, поэтому методика здесь своя, а не вычитанная, — и система выросла до промышленного размера.

8 779tests collectedтестов собирается
133klines of sourceстрок исходника
173klines of testsстрок тестов
8packages in one workspaceпакетов в пространстве
3 041commits since Aprilкоммита с апреля

More test code than product code. One developer, five months.

Кода тестов больше, чем кода продукта. Один разработчик, пять месяцев.

  • A two-phase agent instead of one expensive pass. Discovery runs on a light model to locate suspicious areas; a strong model then works through each in parallel. Model spend dropped by more than half against pushing the whole volume through the strong model.
  • Двухфазный агент вместо одного дорогого прохода. Разведка на лёгкой модели находит подозрительные области, затем сильная модель разбирает каждую параллельно. Расходы на модель упали более чем вдвое против разбора всего объёма сильной моделью.
  • Fifteen analytical procedures built in — sampling, period comparison, materiality, factor analysis — carrying the audit methodology I used to apply by hand.
  • Пятнадцать аналитических процедур внутри — выборка, сравнение периодов, существенность, факторный анализ — с той методикой, которую я раньше применял руками.
  • Anonymisation before anything leaves the perimeter, at four levels up to fully air-gapped with no external calls at all.
  • Обезличивание перед отправкой в модель — четыре уровня вплоть до полного отказа от внешних вызовов.
  • Eight packages in one workspace — core, CLI, HTTP API, web, UI, MCP and two data services — checked by a single command: build, type checking, linting, an OpenAPI contract diff and the full test run across all of them.
  • Восемь пакетов в одном рабочем пространстве — ядро, командная строка, HTTP-интерфейс, веб, интерфейсная библиотека, MCP и два сервиса данных — проверяются одной командой: сборка, проверка типов, линтеры, сверка контракта OpenAPI и полный прогон тестов по всем сразу.
  • An MCP server written from scratch so external AI assistants can reach audit results: the payload is limited by the perimeter mode, every request is logged, and the log travels with the final document pack. Verified by a live protocol run over stdio, not only by unit tests.
  • Написан с нуля MCP-сервер, чтобы внешние ИИ-ассистенты получали доступ к результатам аудита: состав выдачи ограничен режимом контура, каждое обращение пишется в журнал, журнал уезжает вместе с итоговым пакетом документов. Проверено живым прогоном по протоколу через stdio, а не только модульными тестами.
15 audit proceduresпроцедур аудита 4 anonymisation levelsуровня обезличивания −50%+ model spendрасходов на модель 1 developerразработчик

Python 3.12 · FastAPI · PostgreSQL · pgvector · Claude API · MCP · Typer · openpyxl · pytest · mypy · ruff

Operating rules

Рабочие правила

06

Habits that came out of production, not out of a style guide.

Привычки, которые взялись из прода, а не из руководства по стилю.

R-01

Verified on production

"Deployed" counts only after a request to the running service. I once had a fix committed while the production container still ran the old function.

Проверяю на проде

«Выложено» считается только после запроса к работающему сервису. Ловил у себя случай, когда правка была в коммите, а в контейнере функция оставалась старой.

R-02

Tests on business rules

Wallet arithmetic, role permissions, order numbering, parsing someone else's exports. I do not chase a coverage percentage.

Тесты на бизнес-правила

Расчёты кошелька, права ролей, нумерация заказов, разбор чужих выгрузок. За процентом покрытия не гонюсь.

R-03

Root cause, not symptom

Three breaches were hunted as a file backdoor until a process watcher showed the entry was bypassing the web server entirely.

Разбор до причины

Три взлома искали закладкой в файлах, пока наблюдатель за процессами не показал, что вход шёл мимо веб-сервера.

R-04

Dangerous things ship disabled

Behind a settings flag until the client decides. Production is not touched in the meantime.

Опасное выкатывается выключенным

За признаком в настройках, пока заказчик не решит включить. Прод при этом не трогается.

R-05

A database copy before every migration

And a restore that has actually been rehearsed, not assumed.

Копия базы до миграции

И восстановление, проверенное на практике, а не в теории.

R-06

AI agents with a harness around them

Project instructions, decomposition into steps an agent does reliably, verification loops, cross-checking by models from different vendors. Directing and verifying, not prompting and pasting.

ИИ-агенты с обвязкой вокруг

Инструкции проекта, разбиение на шаги, которые агент делает надёжно, проверочные циклы, перекрёстная проверка моделями разных вендоров. Направлять и проверять, а не спрашивать и вставлять.

Stack

Стек

07

Backend

Сервер

Python 3.11–3.12 · FastAPI · SQLAlchemy async · Alembic · PostgreSQL · Redis · arq · Pydantic · C# / .NET

Frontend

Интерфейсы

React · TypeScript · Ant Design · Vite · TanStack Query · PWA · Service Worker · Web Push · Chrome MV3

AI

Claude API · OpenAI-compatible APIs · OpenRouter · Groq · Gemini · Whisper · pgvector · RAG · MCP · LLM-as-judge

Bots

Боты

aiogram · Telegram Bot API · Telegram Mini App · custom clients for messenger APIs

Infrastructure

Инфраструктура

Docker Compose · nginx · Caddy · Linux · systemd · iptables · fail2ban · MinIO · scheduled backups · incident response

Quality

Качество

pytest · vitest · mypy · ruff · tsc · Playwright · Puppeteer · health checks

Before engineering

До разработки

08

Ten years in audit

Десять лет в аудите

IFRS and local GAAP audits of large industrial, IT and state-owned companies. Internal control assessment, COSO/SOX control testing, operational audit, forensic work, factor analysis, reporting to CFOs and audit committees. Led teams of up to ten.

Аудит по МСФО и РСБУ крупных промышленных, ИТ и государственных компаний. Оценка системы внутреннего контроля, тестирование контролей по COSO и SOX, операционный аудит, форензик, факторный анализ, отчётность финансовым директорам и комитету по аудиту. Вёл команды до десяти человек.

Commercial backend in a team

Коммерческий backend в команде

A year and a half of .NET backend development at a product company — joined as a junior, grew into a middle-junior role. Shared codebase, code review, release cycle.

Полтора года backend-разработки на .NET в продуктовой компании — пришёл junior-разработчиком, вырос до middle-junior. Общая кодовая база, код-ревью, релизный цикл.

Education

Образование

NRNU MEPhI, Faculty of Cybernetics and Information Security — Economic Security, degree with honours, 2017. EPAM System Analysis, 2024.

НИЯУ МИФИ, факультет кибернетики и информационной безопасности — экономическая безопасность, диплом с отличием, 2017. EPAM System Analysis, 2024.

Working arrangement

Формат работы

Remote or hybrid, full-time. GMT+3, comfortable overlapping with European and US-East hours. English B2. Open to relocation.

Удалённо или гибридно, полная занятость. Часовой пояс GMT+3, удобно пересекаться с Европой и восточным побережьем США. Английский B2. Готов к переезду.

Tell me the problem, not the ticket.

Расскажите задачу, а не тикет.

I will come back with an approach, a realistic estimate and the risks I can already see.

Вернусь с подходом, реалистичной оценкой и рисками, которые вижу уже сейчас.

Client names, domains, server addresses, staff names and customer data are deliberately withheld. Sector, scope, technical decisions and data volumes are given as they are. Figures come from test runs, monitoring and migration logs on the projects described.

Названия заказчиков, домены, адреса серверов, имена сотрудников и данные клиентов намеренно не раскрыты. Отрасль, состав работ, технические решения и объёмы данных приведены как есть. Цифры взяты из прогонов тестов, мониторинга и журналов переноса на описанных проектах.