7 мин

Мобильное приложение для снятия показаний счётчиков: офлайн и выгрузка

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

Мобильное приложение для снятия показаний счётчиков: офлайн и выгрузка

Какая задача у приложения и почему важен офлайн

Мобильное приложение для снятия показаний счётчиков нужно, чтобы исполнитель мог быстро пройти маршрут (подъезд, улица, частный сектор), собрать показания и фото, а затем передать всё в диспетчерскую систему без потерь и путаницы.

На практике сбор часто выглядит просто: утром выдают список адресов, дальше исполнитель идёт «по подъезду сверху вниз» или «по улице от дома к дому». Внутри одного дня встречаются разные типы объектов: многоквартирные дома с несколькими подъездами, частные дома, нежилые помещения. Если приложение заставляет ждать сеть или каждый раз заново искать объект, обход растягивается, а ошибок становится больше.

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

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

Без этого почти сразу всплывают типовые проблемы: дубли (одно и то же помещение отправили дважды), неверный объект (показание записали «в соседнюю квартиру»), потеря фото (снимок сделали, но он не прикрепился), путаница по датам. Офлайн с понятным сохранением на устройстве и последующей синхронизацией снижает эти риски и делает работу предсказуемой.

Данные: какие объекты нужны и как их хранить

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

Основа - это иерархия объектов и один главный факт: «снятие» (измерение). Обычно хватает такой структуры:

  • Объект: дом, подъезд, квартира (или помещение), связи «дом -> подъезд -> помещение».
  • Счётчик: привязка к помещению, тип ресурса (вода, газ, электричество, тепло), единицы измерения.
  • Снятие: показание, время, статус (принято, отправлено, ошибка, пропуск), комментарий, исполнитель.
  • Фото: отдельная сущность, привязка к снятию, файл и метаданные (время, ориентация, размер).
  • Справочники: типы счётчиков, причины пропуска (нет доступа, сорвана пломба, нет прибора), статусы.

Поля, которые экономят время на сверках: стабильные идентификаторы (внутренний id и внешний id из вашей базы, если он есть), нормализованный адрес (город, улица, дом, корпус, подъезд, этаж, кв.), тип ресурса и единицы.

Для счётчика полезно хранить серийный (или инвентарный) номер и признак того, сколько знаков после запятой допустимо. А «снятие» лучше хранить отдельной записью, а не перезаписывать «последнее значение» внутри счётчика: так видна история, проще объяснять спорные случаи и делать повторный обход.

Фото не стоит держать внутри сущности «снятие» одним полем. У одного снятия может быть несколько фото (общий вид и табло), а ещё фото иногда «доезжает» позже. Поэтому фото храните отдельно, а в снятии держите только ссылки.

Для хранения на устройстве подойдёт локальная база (например, SQLite) с ключами и индексами по адресу и счётчику. Если собираете MVP в TakProsto, можно сразу описать эти сущности в чате и получить модели для Flutter и API под Go с PostgreSQL.

Пользовательский сценарий обхода: от списка до сохранения

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

1) Маршрут на сегодня

Исполнитель открывает приложение и видит список: дома, подъезды или точки на улице. В каждой строке полезно показывать статус (не начато, частично, готово) и прогресс, например 6 из 12.

Чтобы обход шёл без лишних тапов, добавьте быстрые фильтры «только не пройденные» и поиск по адресу. Это особенно помогает, когда диспетчер добавил внеплановый дом, а связь слабая.

2) Карточка объекта и форма снятия

В карточке объекта показывайте список счётчиков (вода, тепло, электричество), их номера и последний результат с датой. Это помогает сразу заметить ошибку, например когда выбран не тот прибор.

Дальше пользователь открывает форму и делает минимум действий: вводит показания (без лишних символов), делает фото, при необходимости оставляет короткую заметку, отмечает «нет доступа», если дверь закрыта или счётчик спрятан, и нажимает «Сохранить». После этого он должен увидеть понятный результат: запись сохранена.

Быстрые действия снижают усталость на длинном маршруте: «следующий» ведёт к следующему счётчику или квартире, «повторить фото» заменяет снимок, «пропустить» отмечает точку как не выполненную без потери адреса.

Проверки лучше делать мягкими. Если показания выглядят как слишком большой скачок относительно прошлого значения, покажите предупреждение и попросите подтвердить. Так меньше ошибок и меньше споров.

Пример: мастер идёт по подъезду, открывает подъезд 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 при любых повторах отправки. Сервер хранит результаты по request_id и на повторный запрос отвечает тем же итогом, не создавая вторую запись.

Практичный ключ уникальности зависит от бизнеса, но часто выглядит так:

  • meter_id (счётчик)
  • period (например, 2026-01)
  • performer_id (исполнитель)
  • reading_time (округление до минуты или до дня, как решите)
  • optional: serial_number или объект (квартира/дом), если meter_id не гарантирован

Пример: обходчик снял показание, добавил фото, связь пропала. Очередь попробует ещё раз через 10 минут. Благодаря тому же request_id сервер вернёт уже созданную запись, а приложение отметит задачу как завершённую.

Защита от двойного нажатия и повторного ввода

На клиенте нужна простая защита от «дважды нажал Сохранить»: кнопка блокируется после первого нажатия, а запись сразу получает статус в очереди (например, queued). Если пользователь возвращается назад, показывайте явную метку: «уже сохранено, ожидает отправки».

На сервере добавьте проверку: если запись с таким request_id или ключом уникальности уже есть, возвращайте существующий результат (и статус), а не ошибку. Это защищает и от офлайн-повторов, и от пересинхронизаций после обновления приложения.

Сервер и API: что нужно для синхронизации и контроля

Сервер здесь нужен не только чтобы принять показания, но и чтобы управлять обходом: раздать актуальный список объектов, принять пакет данных одним запросом, связать фото с записью и дать понятный статус синхронизации. Чем проще и предсказуемее API, тем меньше «зависших» задач у исполнителей.

Минимальный набор операций обычно такой:

  • получить объекты для обхода (дом, подъезд, квартиры, счётчики) с версией списка и датой актуальности
  • отправить пакет показаний (несколько записей за раз) и получить разбор: что принято, что отклонено и почему
  • загрузить фото (отдельно или вместе с пакетом) и привязать к конкретному измерению
  • получить статус синхронизации и историю отправок по устройству/пользователю

Отдельное внимание - ответам сервера. Не ограничивайтесь «200 OK». В ответе на пакет удобно вернуть массив результатов по каждой записи: accepted/rejected, код причины (например, «не найден счётчик», «показание меньше предыдущего») и ссылку на серверный идентификатор принятой записи. Тогда приложение подсветит ошибки и не заставит человека искать проблему на глаз.

Аутентификация должна быть простой: короткоживущий токен доступа и обновление по refresh-токену. Для контроля добавьте идентификатор устройства и версию приложения в заголовки, чтобы понимать, откуда пришла отправка.

Для логов и аудита сохраняйте минимум: кто отправил, когда, с какого устройства, какой объект, какой результат. Персональные поля (ФИО, телефон) лучше не передавать без необходимости. Если они нужны диспетчеру, храните отдельно и отдавайте замаскированно. Например, исполнитель видит только «кв. 24», а диспетчер в админке - полную карточку.

Если вы собираете MVP на TakProsto, удобно сразу заложить backend на Go + PostgreSQL и контракт API так, чтобы его было легко тестировать пакетами: отправляете один батч по подъезду, сервер возвращает точный отчёт, что вошло в базу и что нужно исправить на месте.

Частые ошибки в таких приложениях и как их избежать

Меньше ошибок на объекте
Добавьте предупреждения по скачкам и контролю фото, чтобы меньше правок в диспетчерской.

Самые дорогие ошибки появляются не на сервере, а на лестничной клетке: плохой свет, спешка, нет связи. Если заранее не продумать проверки и статусы, вы получите «красивую выгрузку» из неправильных данных.

Частая проблема: исполнитель открывает квартиру или объект, но вводит показания не в тот счётчик из списка. Лечится простым правилом: счётчик выбирается по двум признакам (например, тип + серийный номер) и подтверждается фото. На экране ввода должен быть явный якорь - что именно сейчас снимаем.

Вторая боль - фото есть, но оно бесполезно. Не стоит надеяться на дисциплину. Лучше добавить проверку на месте: подсказку по кадру и требование переснять, если фото слишком тёмное или смазанное. Как минимум - если цифры не попали в фокус, запись не должна уходить как «готово».

Офлайн тоже ломается неожиданно. Плохой вариант - кнопка «Отправить» зависла, и человек не понимает, сохранилось ли. Нужны статусы: «сохранено локально», «в очереди», «отправлено», «ошибка». И очередь должна жить в памяти устройства, а не только в оперативной памяти приложения.

Ещё один источник хаоса - повторная выгрузка и дубли. Это решается заранее:

  • присваивайте каждой отправке request_id и храните его локально
  • задайте уникальный ключ записи (объект + счётчик + дата/время обхода)
  • на сервере принимайте запросы идемпотентно: повтор не создаёт вторую запись
  • показывайте пользователю результат: что уже принято, а что нет

Наконец, форма. Если в одном экране 10 полей, люди начинают угадывать. Оставьте минимум: показание, фото, комментарий по необходимости. При обходе подъезда без сети исполнитель должен закрывать одну точку за 20-30 секунд, иначе ошибки станут нормой.

Короткий чеклист для исполнителя и диспетчера

Чеклист помогает не терять показания, не привозить домой «сырые» черновики и быстрее закрывать ошибки.

Исполнитель (обходчик)

Перед выходом потратьте 2 минуты на подготовку. Это дешевле, чем возвращаться в подъезд из-за одной пропущенной квартиры.

  • Маршрут и список объектов уже в телефоне и открываются без интернета.
  • На устройстве достаточно памяти под фото (пара сотен снимков занимают заметно).
  • Дата и время на телефоне верные (потом проще разбирать спорные случаи).
  • На первом объекте проверьте: камера фокусируется, вспышка работает, ввод показаний не «съедает» нули.
  • Если есть несколько счётчиков, убедитесь, что выбран правильный (например, холодная/горячая вода).

На объекте правило простое: одно показание = одна понятная запись. Фото должно быть читаемым (цифры, пломба/серийный номер при необходимости), показание - в допустимом формате, а адрес и счётчик - совпадать с фактом.

После обхода остановитесь на минуту и проверьте статусы: все записи должны быть «готово», без незаполненных черновиков и без пустых фото.

Диспетчер (приём и контроль)

Перед выгрузкой договоритесь о режиме: полная отправка или частичная (например, только «готово»), если связь нестабильна. После выгрузки важны не слова «успешно», а конкретные подтверждения.

  • Есть список принятых записей с количеством и временем.
  • Ошибки видны отдельно: что не прошло и почему (формат, фото, не тот счётчик, конфликт по дате).
  • Понятно, что нужно дозаполнить, и это возвращается исполнителю как задача.
  • Дубли не «схлопываются» молча: если запись уже была, система показывает причину отклонения.
  • Маршрут закрывается только когда нет «висящих» записей в очереди отправки.

Если вы собираете MVP в TakProsto, такой чеклист удобно встроить прямо в экран «Перед выходом» и «Перед отправкой», чтобы команда реже полагалась на память.

Пример реального обхода: подъезд и улица с офлайн выгрузкой

Planning mode для сценариев
Разложите сценарии обхода, статусы и конфликты, прежде чем генерировать код.

Исполнитель начинает утром с подъезда на 9 этажей, затем переходит на соседнюю улицу с частным сектором. В подъезде связь нестабильна: в лифте и на лестничной клетке часто нет интернета. Приложение заранее показывает список точек обхода по адресу и позволяет работать полностью офлайн.

На каждой точке он открывает карточку, вносит показание и делает фотофиксацию. Одна запись выглядит так: адрес (дом, подъезд, этаж, квартира), тип счётчика (вода/электро), серийный номер или метка, показание, два фото (общий вид и крупно табло), короткий комментарий (например, «пломба целая» или «доступ через консьержа»). После сохранения запись получает статус «Ожидает отправки» и попадает в очередь.

За 40 минут он собирает 15 снятий: 9 в подъезде и 6 на улице. На улице связь тоже пропадает между домами, поэтому он просто продолжает вводить данные, не думая о сети. В конце обхода у офиса появляется стабильный интернет, и он нажимает «Отправить всё».

Пакетная отправка уходит одним действием, но внутри приложение показывает статусы:

  • 12 записей: «Принято»
  • 3 записи: «Отклонено (дубль)»

По каждой из трёх видно причину, например: «Показание за этот счётчик уже принято сегодня в 09:14» или «Совпадает ключ: адрес + серийный номер + дата». Исполнитель не создаёт новые записи вручную. Он открывает отклонённые, сверяет фото и номер, и нажимает «Повторить отправку» после правки (если она нужна). Сервер отвечает идемпотентно: уже принятые подтверждает повторно, а исправленные принимает как обновление, без появления лишних дублей.

Такой сценарий удобно собрать в TakProsto: в чате описываете поля, статусы и логику очереди, а затем получаете мобильные экраны для обхода и кнопку пакетной выгрузки.

Следующие шаги: как быстро собрать MVP и запустить пилот

Начните с короткого ТЗ на одну страницу. Для MVP важно не «всё и сразу», а чтобы приложение уверенно работало в реальном обходе: без сети, с фото, с понятной выгрузкой и без повторов.

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

Дальше соберите прототип экранов и отдайте 1-2 реальным исполнителям. Пусть они пройдут один подъезд или короткий участок улицы и отметят, где теряется время: поиск адреса, ввод показаний, фото, комментарии, исправления.

Чтобы не упереться в синхронизацию, заранее спланируйте API и схему БД под офлайн очередь и фото. Минимальный набор шагов:

  • определить формат записи: показание, фото, координаты (если нужны), временная метка, локальный id и серверный id
  • продумать очередь отправки: повторные попытки, ограничение по размеру пачки, обработка ошибок
  • заложить идемпотентность: idempotency key или уникальный ключ в БД на уровне сервера
  • решить, как хранить фото: локально до отправки и метаданные после
  • описать роли: исполнитель и диспетчер, кто может править и подтверждать

Если делаете через TakProsto, удобно сначала написать сценарии в чате и включить planning mode: какие экраны, какие события, какие ответы сервера. Для команды, которой важно быстро собрать мобильный клиент (Flutter) и серверную часть (Go + PostgreSQL) без долгой раскачки, TakProsto (takprosto.ai) может быть практичной стартовой точкой.

На пилоте измеряйте не только «работает или нет», а:

  • среднее время на один счётчик
  • долю записей, ушедших с первого раза
  • число конфликтов и дублей

После первых обновлений включите экспорт исходников, настройте деплой и используйте snapshots для отката, если новая версия внезапно ломает отправку или работу офлайн. "}

FAQ

Почему офлайн режим для снятия показаний — это не опция, а необходимость?

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

Какая структура данных самая простая для такого приложения?

Храните иерархию «дом → подъезд → помещение», затем счётчики, привязанные к помещению, и отдельные записи «снятие» с временем, исполнителем и статусом. Фото лучше вести отдельной сущностью, связанной со снятием, чтобы поддержать несколько снимков и отложенную догрузку.

Какие поля обязательно фиксировать в каждой записи снятия?

Храните само показание, тип ресурса, адресную привязку до конкретного помещения, дату и время, исполнителя и статус записи. Если нужны разборы спорных случаев, добавьте серийный номер счётчика и историю снятий, а не только «последнее значение».

Как правильно хранить фото офлайн, чтобы они не терялись?

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

Как устроить очередь отправки и статусы, чтобы исполнитель не путался?

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

Как отправлять данные быстро: по одной записи или пакетами?

По умолчанию отправляйте данные пакетами по 20–50 записей и делайте автоотправку в фоне при появлении стабильной связи, не блокируя работу. Кнопку ручной отправки оставьте для контроля, когда интернет появляется кратко и нужно «успеть».

Отправлять фото вместе с показаниями или отдельно?

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

Как защититься от дублей при плохой связи и повторной отправке?

Сделайте идемпотентность: при локальном сохранении генерируйте постоянный request_id и используйте его при любых повторах отправки. На сервере повтор с тем же request_id должен возвращать уже созданный результат, а не создавать вторую запись.

Что делать, если при синхронизации возник конфликт (счётчик заменили или объект обновили)?

Если на сервере изменился объект, например заменили счётчик, покажите понятное предупреждение и предложите действие: привязать снятие к новому счётчику, пометить как «требует проверки» или отменить отправку. Главное — не «молча» перекидывать данные на другой объект.

Как снизить ошибки ввода показаний и некачественные фото прямо на объекте?

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

Похожие статьи