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

Мобильное приложение для снятия показаний счётчиков нужно, чтобы исполнитель мог быстро пройти маршрут (подъезд, улица, частный сектор), собрать показания и фото, а затем передать всё в диспетчерскую систему без потерь и путаницы.
На практике сбор часто выглядит просто: утром выдают список адресов, дальше исполнитель идёт «по подъезду сверху вниз» или «по улице от дома к дому». Внутри одного дня встречаются разные типы объектов: многоквартирные дома с несколькими подъездами, частные дома, нежилые помещения. Если приложение заставляет ждать сеть или каждый раз заново искать объект, обход растягивается, а ошибок становится больше.
Офлайн режим здесь не «приятная опция», а базовая необходимость. Связь пропадает в подвалах, лифтовых холлах, щитовых, за городом и в частном секторе. Даже при нормальном интернете постоянные запросы «на каждый ввод» тратят время и батарею. Офлайн даёт простой ритм работы: открыл карточку объекта, ввёл цифры, сделал фото, сохранил и пошёл дальше.
В каждой записи важно фиксировать минимум: показание и тип ресурса (вода, тепло, электричество), фото счётчика (или пломбы, если требуется), адрес и привязку к конкретному объекту (дом, подъезд, квартира/помещение), дату и время, исполнителя и статус (черновик, готово к отправке, отправлено, ошибка).
Без этого почти сразу всплывают типовые проблемы: дубли (одно и то же помещение отправили дважды), неверный объект (показание записали «в соседнюю квартиру»), потеря фото (снимок сделали, но он не прикрепился), путаница по датам. Офлайн с понятным сохранением на устройстве и последующей синхронизацией снижает эти риски и делает работу предсказуемой.
Чтобы приложение работало без сюрпризов, сначала договоритесь о данных. Чем проще и однозначнее модель, тем легче собрать офлайн режим, фото и выгрузку.
Основа - это иерархия объектов и один главный факт: «снятие» (измерение). Обычно хватает такой структуры:
Поля, которые экономят время на сверках: стабильные идентификаторы (внутренний id и внешний id из вашей базы, если он есть), нормализованный адрес (город, улица, дом, корпус, подъезд, этаж, кв.), тип ресурса и единицы.
Для счётчика полезно хранить серийный (или инвентарный) номер и признак того, сколько знаков после запятой допустимо. А «снятие» лучше хранить отдельной записью, а не перезаписывать «последнее значение» внутри счётчика: так видна история, проще объяснять спорные случаи и делать повторный обход.
Фото не стоит держать внутри сущности «снятие» одним полем. У одного снятия может быть несколько фото (общий вид и табло), а ещё фото иногда «доезжает» позже. Поэтому фото храните отдельно, а в снятии держите только ссылки.
Для хранения на устройстве подойдёт локальная база (например, SQLite) с ключами и индексами по адресу и счётчику. Если собираете MVP в TakProsto, можно сразу описать эти сущности в чате и получить модели для Flutter и API под Go с PostgreSQL.
Цель сценария простая: исполнитель проходит маршрут быстро, не путается в адресах и не теряет данные. В типичном приложении всё строится вокруг трёх экранов: маршрут на сегодня, карточка объекта и форма ввода.
Исполнитель открывает приложение и видит список: дома, подъезды или точки на улице. В каждой строке полезно показывать статус (не начато, частично, готово) и прогресс, например 6 из 12.
Чтобы обход шёл без лишних тапов, добавьте быстрые фильтры «только не пройденные» и поиск по адресу. Это особенно помогает, когда диспетчер добавил внеплановый дом, а связь слабая.
В карточке объекта показывайте список счётчиков (вода, тепло, электричество), их номера и последний результат с датой. Это помогает сразу заметить ошибку, например когда выбран не тот прибор.
Дальше пользователь открывает форму и делает минимум действий: вводит показания (без лишних символов), делает фото, при необходимости оставляет короткую заметку, отмечает «нет доступа», если дверь закрыта или счётчик спрятан, и нажимает «Сохранить». После этого он должен увидеть понятный результат: запись сохранена.
Быстрые действия снижают усталость на длинном маршруте: «следующий» ведёт к следующему счётчику или квартире, «повторить фото» заменяет снимок, «пропустить» отмечает точку как не выполненную без потери адреса.
Проверки лучше делать мягкими. Если показания выглядят как слишком большой скачок относительно прошлого значения, покажите предупреждение и попросите подтвердить. Так меньше ошибок и меньше споров.
Пример: мастер идёт по подъезду, открывает подъезд 3, квартира 45, видит прошлые 01234, вводит 01298, делает фото, добавляет заметку «пломба цела» и нажимает «следующий». Через минуту он уже на квартире 46, а данные не путаются и не теряются.
Офлайн режим нужен, когда связь пропадает в лифте, в подвале или между домами. Приложение должно работать так, будто сеть есть, а синхронизация происходит позже.
На устройстве держите локальную базу (например, SQLite), чтобы не зависеть от интернета. В ней важно хранить не только «показание», но и контекст - иначе позже будет сложно проверить, что именно сняли.
Обычно хватает таких полей:
Фото лучше хранить как файлы в памяти устройства, а в базе держать только путь, размер, дату и флаг «сжато». Сжатие делайте сразу после съёмки: так очередь отправки легче, и приложение не забивает память.
Когда исполнитель идёт по маршруту, он постоянно создаёт и правит записи. Каждое действие попадает в очередь изменений: новое снятие, обновление (например, исправили цифру), удаление черновика (если фото размыто и решили переснять).
У каждой записи должен быть понятный статус:
Конфликты решайте предсказуемо. Если на сервере поменялся объект (например, заменили счётчик), приложение должно показать предупреждение и предложить вариант: привязать снятие к новому счётчику, сохранить как «требует проверки» или отменить отправку.
Если вы описываете MVP в TakProsto, удобно сразу проговорить правила статусов и очереди: «сохраняй локально, отправляй пачкой при появлении сети, ошибки не теряй, повторяй безопасно».
Во время обхода главное - не заставлять человека ждать возле подъезда, пока всё «улетит» в сеть. Заложите два способа отправки: вручную кнопкой «Отправить сейчас» и автоматически, как только появилась стабильная связь. Автоотправку лучше делать тихо в фоне, а кнопку оставить для контроля и ситуаций, когда сеть есть только на минуту.
Размер пакета почти всегда важнее красоты API. Отправляйте показания пачками по 20-50 записей за запрос: так меньше накладных расходов и ниже шанс упереться в таймауты. С фото сложнее - одно фото может весить как десяток текстовых записей.
Есть два рабочих подхода:
На практике чаще удобнее второй вариант: в пакете указываете, что фото будет догружено, и показываете прогресс «Показания отправлены, фото: 7 из 12».
Если запрос не прошёл, не нужно повторять каждую секунду. Делайте повтор с экспоненциальной задержкой, например 5 сек, 15 сек, 45 сек, 2 мин, и ограничьте число попыток (обычно 5-7 хватает). После лимита показывайте понятный статус «Не удалось отправить, попробуйте позже» и кнопку «Повторить».
Ошибки должны быть исправимы на месте. Вместо «400 Bad Request» покажите: «Нет фото по квартире 12», «Показание меньше предыдущего, проверьте цифры», «Не выбран счётчик (ХВС/ГВС/электро)». В TakProsto такие правила валидации и тексты сообщений удобно фиксировать прямо в чате, чтобы исполнитель сразу понимал, что именно нужно поправить.
Дубли появляются чаще, чем кажется: связь пропала и запись ушла два раза, исполнитель случайно ввёл одно и то же повторно, или подъезд обошли ещё раз по старому списку. Для приложения это не мелочь: один дубль может испортить начисления и вызвать споры.
Сначала договоритесь, что именно считается дублем по вашим правилам. Чаще всего это одно и то же снятие для одного счётчика в одном периоде, даже если его отправили повторно из офлайн-очереди.
Надёжный способ - сделать каждое снятие идемпотентным. Клиент генерирует request_id при сохранении записи локально (до отправки) и использует тот же request_id при любых повторах отправки. Сервер хранит результаты по request_id и на повторный запрос отвечает тем же итогом, не создавая вторую запись.
Практичный ключ уникальности зависит от бизнеса, но часто выглядит так:
Пример: обходчик снял показание, добавил фото, связь пропала. Очередь попробует ещё раз через 10 минут. Благодаря тому же request_id сервер вернёт уже созданную запись, а приложение отметит задачу как завершённую.
На клиенте нужна простая защита от «дважды нажал Сохранить»: кнопка блокируется после первого нажатия, а запись сразу получает статус в очереди (например, queued). Если пользователь возвращается назад, показывайте явную метку: «уже сохранено, ожидает отправки».
На сервере добавьте проверку: если запись с таким request_id или ключом уникальности уже есть, возвращайте существующий результат (и статус), а не ошибку. Это защищает и от офлайн-повторов, и от пересинхронизаций после обновления приложения.
Сервер здесь нужен не только чтобы принять показания, но и чтобы управлять обходом: раздать актуальный список объектов, принять пакет данных одним запросом, связать фото с записью и дать понятный статус синхронизации. Чем проще и предсказуемее API, тем меньше «зависших» задач у исполнителей.
Минимальный набор операций обычно такой:
Отдельное внимание - ответам сервера. Не ограничивайтесь «200 OK». В ответе на пакет удобно вернуть массив результатов по каждой записи: accepted/rejected, код причины (например, «не найден счётчик», «показание меньше предыдущего») и ссылку на серверный идентификатор принятой записи. Тогда приложение подсветит ошибки и не заставит человека искать проблему на глаз.
Аутентификация должна быть простой: короткоживущий токен доступа и обновление по refresh-токену. Для контроля добавьте идентификатор устройства и версию приложения в заголовки, чтобы понимать, откуда пришла отправка.
Для логов и аудита сохраняйте минимум: кто отправил, когда, с какого устройства, какой объект, какой результат. Персональные поля (ФИО, телефон) лучше не передавать без необходимости. Если они нужны диспетчеру, храните отдельно и отдавайте замаскированно. Например, исполнитель видит только «кв. 24», а диспетчер в админке - полную карточку.
Если вы собираете MVP на TakProsto, удобно сразу заложить backend на Go + PostgreSQL и контракт API так, чтобы его было легко тестировать пакетами: отправляете один батч по подъезду, сервер возвращает точный отчёт, что вошло в базу и что нужно исправить на месте.
Самые дорогие ошибки появляются не на сервере, а на лестничной клетке: плохой свет, спешка, нет связи. Если заранее не продумать проверки и статусы, вы получите «красивую выгрузку» из неправильных данных.
Частая проблема: исполнитель открывает квартиру или объект, но вводит показания не в тот счётчик из списка. Лечится простым правилом: счётчик выбирается по двум признакам (например, тип + серийный номер) и подтверждается фото. На экране ввода должен быть явный якорь - что именно сейчас снимаем.
Вторая боль - фото есть, но оно бесполезно. Не стоит надеяться на дисциплину. Лучше добавить проверку на месте: подсказку по кадру и требование переснять, если фото слишком тёмное или смазанное. Как минимум - если цифры не попали в фокус, запись не должна уходить как «готово».
Офлайн тоже ломается неожиданно. Плохой вариант - кнопка «Отправить» зависла, и человек не понимает, сохранилось ли. Нужны статусы: «сохранено локально», «в очереди», «отправлено», «ошибка». И очередь должна жить в памяти устройства, а не только в оперативной памяти приложения.
Ещё один источник хаоса - повторная выгрузка и дубли. Это решается заранее:
Наконец, форма. Если в одном экране 10 полей, люди начинают угадывать. Оставьте минимум: показание, фото, комментарий по необходимости. При обходе подъезда без сети исполнитель должен закрывать одну точку за 20-30 секунд, иначе ошибки станут нормой.
Чеклист помогает не терять показания, не привозить домой «сырые» черновики и быстрее закрывать ошибки.
Перед выходом потратьте 2 минуты на подготовку. Это дешевле, чем возвращаться в подъезд из-за одной пропущенной квартиры.
На объекте правило простое: одно показание = одна понятная запись. Фото должно быть читаемым (цифры, пломба/серийный номер при необходимости), показание - в допустимом формате, а адрес и счётчик - совпадать с фактом.
После обхода остановитесь на минуту и проверьте статусы: все записи должны быть «готово», без незаполненных черновиков и без пустых фото.
Перед выгрузкой договоритесь о режиме: полная отправка или частичная (например, только «готово»), если связь нестабильна. После выгрузки важны не слова «успешно», а конкретные подтверждения.
Если вы собираете MVP в TakProsto, такой чеклист удобно встроить прямо в экран «Перед выходом» и «Перед отправкой», чтобы команда реже полагалась на память.
Исполнитель начинает утром с подъезда на 9 этажей, затем переходит на соседнюю улицу с частным сектором. В подъезде связь нестабильна: в лифте и на лестничной клетке часто нет интернета. Приложение заранее показывает список точек обхода по адресу и позволяет работать полностью офлайн.
На каждой точке он открывает карточку, вносит показание и делает фотофиксацию. Одна запись выглядит так: адрес (дом, подъезд, этаж, квартира), тип счётчика (вода/электро), серийный номер или метка, показание, два фото (общий вид и крупно табло), короткий комментарий (например, «пломба целая» или «доступ через консьержа»). После сохранения запись получает статус «Ожидает отправки» и попадает в очередь.
За 40 минут он собирает 15 снятий: 9 в подъезде и 6 на улице. На улице связь тоже пропадает между домами, поэтому он просто продолжает вводить данные, не думая о сети. В конце обхода у офиса появляется стабильный интернет, и он нажимает «Отправить всё».
Пакетная отправка уходит одним действием, но внутри приложение показывает статусы:
По каждой из трёх видно причину, например: «Показание за этот счётчик уже принято сегодня в 09:14» или «Совпадает ключ: адрес + серийный номер + дата». Исполнитель не создаёт новые записи вручную. Он открывает отклонённые, сверяет фото и номер, и нажимает «Повторить отправку» после правки (если она нужна). Сервер отвечает идемпотентно: уже принятые подтверждает повторно, а исправленные принимает как обновление, без появления лишних дублей.
Такой сценарий удобно собрать в TakProsto: в чате описываете поля, статусы и логику очереди, а затем получаете мобильные экраны для обхода и кнопку пакетной выгрузки.
Начните с короткого ТЗ на одну страницу. Для MVP важно не «всё и сразу», а чтобы приложение уверенно работало в реальном обходе: без сети, с фото, с понятной выгрузкой и без повторов.
Зафиксируйте требования так, чтобы их можно было проверить в поле. Опишите объекты (дом, подъезд, квартира, счётчик), статусы (в работе, снято, пропущено, требует уточнения) и правило дубля: что считать повтором (например, одинаковый счётчик + дата/смена + тип ресурса), и что делать при повторной отправке.
Дальше соберите прототип экранов и отдайте 1-2 реальным исполнителям. Пусть они пройдут один подъезд или короткий участок улицы и отметят, где теряется время: поиск адреса, ввод показаний, фото, комментарии, исправления.
Чтобы не упереться в синхронизацию, заранее спланируйте API и схему БД под офлайн очередь и фото. Минимальный набор шагов:
Если делаете через TakProsto, удобно сначала написать сценарии в чате и включить planning mode: какие экраны, какие события, какие ответы сервера. Для команды, которой важно быстро собрать мобильный клиент (Flutter) и серверную часть (Go + PostgreSQL) без долгой раскачки, TakProsto (takprosto.ai) может быть практичной стартовой точкой.
На пилоте измеряйте не только «работает или нет», а:
После первых обновлений включите экспорт исходников, настройте деплой и используйте snapshots для отката, если новая версия внезапно ломает отправку или работу офлайн. "}
Для полевого обхода связь часто пропадает в лифте, подвале, щитовой или частном секторе, поэтому ввод «в сеть» тормозит и повышает риск потери данных. Рабочий минимум — всё сохраняется на устройстве сразу, а синхронизация уходит позже, когда интернет появляется.
Храните иерархию «дом → подъезд → помещение», затем счётчики, привязанные к помещению, и отдельные записи «снятие» с временем, исполнителем и статусом. Фото лучше вести отдельной сущностью, связанной со снятием, чтобы поддержать несколько снимков и отложенную догрузку.
Храните само показание, тип ресурса, адресную привязку до конкретного помещения, дату и время, исполнителя и статус записи. Если нужны разборы спорных случаев, добавьте серийный номер счётчика и историю снятий, а не только «последнее значение».
Снимки сохраняйте как файлы в памяти устройства, а в локальной базе держите путь, размер, время и флаг загрузки. Сжимайте фото сразу после съёмки, чтобы очередь отправки была легче и приложение не забивало хранилище.
Нужна локальная база и очередь изменений, где каждое действие получает статус: черновик, готово к отправке, отправлено, ошибка или конфликт. Тогда пользователь всегда понимает, что уже сохранено, что ждёт сети, а что требует исправления.
По умолчанию отправляйте данные пакетами по 20–50 записей и делайте автоотправку в фоне при появлении стабильной связи, не блокируя работу. Кнопку ручной отправки оставьте для контроля, когда интернет появляется кратко и нужно «успеть».
Практичнее сначала отправить «лёгкий» пакет показаний, а фото догружать отдельно очередью, показывая прогресс. Так обрыв связи не ломает всё целиком, и повторные попытки проще возобновлять.
Сделайте идемпотентность: при локальном сохранении генерируйте постоянный request_id и используйте его при любых повторах отправки. На сервере повтор с тем же request_id должен возвращать уже созданный результат, а не создавать вторую запись.
Если на сервере изменился объект, например заменили счётчик, покажите понятное предупреждение и предложите действие: привязать снятие к новому счётчику, пометить как «требует проверки» или отменить отправку. Главное — не «молча» перекидывать данные на другой объект.
Лучше сразу предупреждать о подозрительном скачке относительно прошлого значения и просить подтверждение, чем потом разбирать ошибки в диспетчерской. Для фото добавьте простую проверку читаемости на месте и не переводите запись в «готово», если снимок явно бесполезен.