Мобильное приложение для геодезистов: модель данных объект-точка-измерение, офлайн-работа с фото и синхронизация с сервером через AI-кодинг.

В поле важен не «красивый интерфейс», а быстрый и надежный способ фиксировать результаты без потерь. Приложением будут пользоваться и бригадир, и исполнитель на точке: первому нужен прогресс по объекту, второму - быстрый ввод измерения, фото и заметки, чтобы сразу идти дальше.
Набор данных обычно повторяется от проекта к проекту. На уровне объекта (участок, стройплощадка, трасса) нужны общие сведения: название, заказчик, даты, состав бригады, заметки. Внутри объекта создаются точки: номер/код, координаты (если уже известны), тип (ось, репер, пикет), статус (запланирована, снята, проверена) и привязка к месту (карта или список).
На каждой точке фиксируются измерения: что измеряли (высота, расстояние, отметка), значение, единицы, метод, прибор, время и исполнитель. Комментарии должны жить рядом с точкой или конкретным измерением: «ветер сильный», «штатив просел», «повторить после обеда». Фото часто выручает, когда нужно объяснить, где стояла точка, что мешало обзору или почему приняли решение на месте.
Офлайн-режим - обязательный. Связь в поле пропадает, батарея уходит быстрее на холоде, экран мокрый от дождя, руки в перчатках. Приложение должно полностью работать без интернета: создавать объект, добавлять точки, сохранять фото, быстро открывать списки и не зависать.
Для первого релиза обычно достаточно четырех основных экранов:
Если начать с этих экранов и четкой структуры данных, позже проще «нарастить» офлайн-хранилище и синхронизацию.
Чтобы приложение работало без сюрпризов, начните с простой и устойчивой модели: объект (проект) содержит точки, а у каждой точки есть набор измерений. Это разделяет три разных смысла: что снимаем, где это на местности и как именно получили значения.
Объект - контейнер для всей съемки. В нем хранится то, что общее для всех точек: название, заказчик, выбранная система координат, статус работ (черновик, в работе, сдано), ответственный.
Точка - конкретное место на объекте. У нее есть координаты (X/Y или широта/долгота), высота, тип (пикет, репер, угол здания), оценка точности и метки (например, «нужна проверка»). Фото и комментарии чаще привязывают к точке: так легче восстановить контекст.
Измерение - запись о том, как получены данные по точке. Здесь лежат метод (GNSS, тахеометрия, нивелирование), прибор, дата и время, измеренные значения, погрешность и служебные параметры (например, режим съемки).
Минимум обязательных полей, без которых модель начинает «сыпаться»:
Остальные поля можно сделать опциональными: заказчик, высота, точность, метки, прибор, погрешность, фото, комментарии. На рекогносцировке, например, удобно быстро набросать точки с фото и короткими заметками, а точные измерения добавить позже.
Чтобы приложение не превратилось в набор несвязанных заметок, связи и ключи стоит продумать до экранов и синхронизации. База простая: объект содержит точки, точка содержит измерения. Офлайн добавляет детали, которые лучше заложить сразу.
Связи обычно такие: Object 1:N Point, Point 1:N Measurement. На уровне хранения удобно явно фиксировать внешние ключи: у точки есть objectLocalId, у измерения - pointLocalId. Это помогает фильтровать данные быстро и не терять контекст, даже если сервер еще не выдал свои идентификаторы.
Для каждой сущности полезно иметь два идентификатора:
localId (UUID) - создается на телефоне сразу, используется офлайн и как ключ связейserverId - появляется после синхронизации, нужен для API и сверкиДополнительно часто нужны:
clientId или deviceId - чтобы понимать источник изменений при конфликтахsyncState (например, new, dirty, synced, deleted) - чтобы вести очередь отправкиПрактическое правило: пока serverId пустой, все связи строятся только через localId. Иначе легко получить «сиротские» измерения или фото.
Для надежной синхронизации добавьте метки изменений: updatedAt и deletedAt (мягкое удаление), плюс version или revision (целое число). Тогда можно уверенно отвечать на вопросы: что изменилось, что удалено, чья версия новее.
Правила целостности лучше сделать явными и в UI, и в данных. Например, удаление точки с измерениями:
deletedAt) все измерения точкиТак на сервер не улетят измерения без точки.
Фото и файлы удобнее хранить отдельной сущностью Attachment с привязкой к точке (и при необходимости к измерению). В таблице держите метаданные: pointLocalId, тип, комментарий, localPath, sha256, createdAt, uploadedAt.
Сценарий простой: в поле фото сохраняется локально и попадает в очередь, а при появлении связи отправляется на сервер вместе с метаданными.
Если вы собираете это через TakProsto, зафиксируйте в планировании правило «все связи только через localId до получения serverId» и попросите сгенерировать миграции и проверки на удаление. Это обычно экономит часы отладки офлайн-ошибок.
В первой версии важнее всего быстрый ввод в поле и понятная проверка данных. Когда экранов слишком много, люди начинают писать «на бумажку» и переносить позже, а это убивает смысл офлайна.
Минимальный набор экранов можно держать коротким:
Рабочий поток должен идти «по рукам» без лишних шагов: создали объект, добавили точки (часто сразу с GPS), в каждой точке внесли измерения и прикрепили фото с коротким комментарием.
В поле решают быстрые действия. Помогают две вещи: заметная кнопка «Добавить точку» в объекте и шаблоны измерений (например, «отметка», «угол», «расстояние»), чтобы не выбирать тип каждый раз.
Чтобы не ловить ошибки при синхронизации, валидации лучше делать на вводе: обязательные поля (код точки, тип измерения, единицы), формат чисел, переключатель единиц (метры/миллиметры), разумные диапазоны. Для «подозрительных» значений лучше показывать предупреждение, а не блокировку.
Карта нужна, но простая: маркер точки + рядом список точек с расстоянием и временем обновления. Достаточно двух действий: нажал на маркер - открыл карточку точки; выбрал точку в списке - подсветил на карте.
Главное правило полевого софта простое: любое действие сначала записывается локально, а синхронизация - отдельный процесс, который догоняет позже.
Для структурированных данных удобен SQLite: быстрый, встроенный, хорошо ложится на «объект -> точка -> измерение». Важнее не выбор БД, а подготовка запросов под полевой поиск. Если бригадир ищет точку по коду или фильтрует по статусу «не снято», приложение не должно тормозить. Поэтому заранее добавьте индексы: по object_id, point_code, updated_at, deleted_at (если есть), а также по sync_state.
Фото лучше хранить не в базе, а файлами на устройстве. В SQLite оставьте метаданные: путь, размер, время съемки, привязку к точке или измерению и контрольную сумму (хотя бы простую) для поиска дублей. Так база остается легкой, а фото можно загружать по одному, не блокируя работу.
Чтобы офлайн не превращался в хаос, заведите очередь изменений. На практике это либо журнал операций (append-only), либо таблица pending_changes. Минимальный набор полей:
entity_type и entity_id (что меняли)op (insert/update/delete)payload (что поменялось)created_at и retry_countstatus (pending/sent/failed)Удаление делайте мягким: вместо реального delete ставьте deleted_at и deleted_by. Физическую уборку фото и строк в БД выполняйте только после подтверждения от сервера.
В интерфейсе показывайте понятный статус: «все сохранено локально», «ждет отправки», «ошибка синхронизации». Пользовательские действия (создание точки, измерения, фото, комментарии) должны идти только через локальное хранилище, а фоновый синк лишь меняет статусы.
Если делаете через TakProsto, заранее попросите сгенерировать миграции SQLite, модели и слой репозитория, чтобы не забыть индексы и поля sync_state.
Синхронизация в полевом софте должна быть скучной и предсказуемой. Пользователь работает офлайн, а при появлении сети данные уходят на сервер без потерь. Для такого сценария хорошо подходит схема pull + push с версиями.
На сервере держите простые операции и версионирование (revision) для каждой сущности (объект, точка, измерение, вложения):
lastSyncToken (или по времени + курсор)clientId и временемsyncTokenPull обычно делайте первым: так клиент увидит свежие правки коллег и только потом отправит свои.
Самая частая ошибка - «последняя запись победила» для всей сущности. Практичнее считать конфликтом конкретные поля.
Пример: один инженер сменил тип точки, другой добавил фото и комментарий. Это не конфликт. Поэтому текстовые поля и атрибуты точки лучше решать через выбор (мое/серверное), а измерения и фото чаще всего просто «сливать» как отдельные записи.
Фото синхронизируйте в два шага: сначала метаданные, потом файл. Файл грузите с повторами и возобновлением, чтобы не начинать заново после обрыва.
Чтобы синк не ломался при слабой сети, добавьте:
Такую логику удобно быстро собрать в TakProsto: описываете правила синка и конфликты словами, а затем проверяете на симуляции «сеть пропала на 30% запросов».
Задача прототипа простая: создать объект, добавить точки, прикрепить фото, работать без связи и безопасно отправить изменения на сервер.
В TakProsto удобно стартовать с Planning mode: сначала описываете данные и экраны словами, затем просите платформу сгенерировать каркас проекта.
Пример задачи для AI: «Нужно хранить измерение с типом (угол, расстояние), значением, единицей, временем, автором. Для синка используем serverVersion и clientUpdatedAt. При конфликте побеждает запись с более новым clientUpdatedAt, но сохраняем старую в history».
objectId, pointId, updatedAt и статусу синкаПосле этого сделайте простой тест: создайте объект, 10 точек и по 2 фото офлайн, затем включите сеть и проверьте, что все записи перешли в статус «Отправлено» без пропусков.
Для полевого приложения важнее всего надежно принять данные, привязать их к объекту и пользователю, сохранить версии и отдать их обратно при синхронизации. Технологически логичный минимум: Flutter на мобильной стороне, а на сервере Go + PostgreSQL.
Если вы собираете прототип через TakProsto, этот стек используется часто: мобильная часть на Flutter, сервер на Go, база PostgreSQL. Это упрощает старт и поддержку.
На сервере обычно хватает понятной структуры и метаданных синка:
В каждой сущности держите: server_id, client_uuid, updated_at, deleted_at (для мягкого удаления) и автора изменения. Это помогает синхронизировать офлайн-редактирование и не терять историю.
На старте достаточно простого токена: пользователь логинится, получает access token, а сервер проверяет его в каждом запросе. Данные удобно привязывать либо к пользователю, либо к бригаде, если несколько людей работают по одному объекту.
Фото лучше хранить файлами, а в PostgreSQL держать только метаданные и связь с точкой/измерением. На практике часто используют S3-совместимый объектный сторедж, развернутый в РФ, плюс таблицу attachments с путями, хэшами и статусом загрузки.
Если проект должен гарантированно работать в России, размещайте сервер, базу и файловое хранилище в пределах РФ и фиксируйте это в настройках и договорах. Тогда данные не «уедут» в другие страны и проще пройти требования заказчиков.
Первая боль почти всегда начинается с идентификаторов. Когда в интерфейсе смешивают локальные id и серверные id, связи «объект -> точка -> измерение» рвутся: точка есть, а измерения не находятся, или фото «переезжает» на другую точку после синка. Надежнее сразу заводить стабильный локальный UUID и хранить отдельно server_id, а в UI и связях использовать локальный ключ.
Вторая ошибка - нет журнала изменений. Если приложение не знает, что именно пользователь сделал офлайн (создал точку, изменил комментарий, удалил фото), синхронизация превращается в «отправим все подряд» или в гадание. Нужен простой outbox: очередь операций с типом, временем, ссылкой на локальные сущности и статусом отправки.
Фото часто кладут прямо в базу одним большим blob. Это быстро упирается в размер, скорость и сбои при записи. Лучше хранить метаданные в БД (путь, хэш, размер, привязка к точке), а сами файлы - в файловом кеше приложения с понятной стратегией: сжатие, превью, повторная отправка.
Если нет стратегии конфликтов, данные тихо перетираются. Пример: инженер в поле правит комментарий к точке, а диспетчер в офисе меняет тип измерения. При синке «последний записавший победил» может уничтожить важную правку. Минимум - версия/updated_at и явное правило по полям, максимум - показ конфликта пользователю.
И еще один типичный провал: синхронизация «по одной записи» медленная и ест батарею. Практичнее паковать изменения батчами и отправлять по событиям (появилась связь, пользователь нажал «Синхронизировать», приложение на зарядке).
Проверьте перед запуском:
server_id отдельно, маппинг хранится явноВ TakProsto удобно сначала описать эти правила в Planning mode, а затем сгенерировать офлайн-слой и синк так, чтобы они им следовали.
Перед выездом на объект прогоните короткий тест на одной «учебной» съемке. Это занимает 10-15 минут, но часто спасает день.
Переведите телефон в авиарежим и работайте так, будто связи не будет:
Затем выберите одну точку и специально внесите правку: измените комментарий и одно значение измерения. Так проще поймать баг, когда редактирование создает «вторую версию» вместо обновления.
Верните интернет и дождитесь отправки:
Если вы делаете прототип через TakProsto, держите этот чеклист рядом: он быстро показывает, где ломается офлайн-хранилище, вложения (фото) или логика синхронизации.
Бригада выезжает на трассу: связи нет почти весь день, план жесткий - снять около 30 точек до вечера. В приложении важно, чтобы работа не зависела от интернета: все создается, сохраняется и ищется локально, а синхронизация ждет, пока появится сеть.
Объект заводят на месте: название (например, «Км 12-18, обочина»), заказчик, дата, бригада, прибор. Дальше выбирают шаблон точек: «опоры», «границы», «колодцы», «пикеты». Шаблон подставляет тип точки, обязательные поля и стандартный набор измерений, чтобы не тратить время на ручной ввод.
На каждой точке чаще всего заполняют минимум: номер/код, тип, координаты (GNSS или из тахеометра), высоту, точность/качество фиксации, статус (черновик или готово). Важно, чтобы приложение позволяло оставить часть данных «на потом»: описание, принадлежность к участку, дополнительные параметры, уточняющие заметки.
Фото и комментарии добавляют на месте за 10-15 секунд. Обычно хватает 1-3 фото: общий вид, привязка к ориентиру, табличка или маркировка. Комментарий короткий и прикладной: «мешает куст, смещение 0.3 м», «точка у столба, отметка на бетоне», «доступ через калитку». Фото сохраняются локально, а в точке лежит ссылка на файл и флаг «не загружено».
Вечером, когда появляется интернет, пользователь нажимает «Синхронизировать». Приложение отправляет новые объекты, точки, измерения, затем фото (обычно это самый долгий этап) и получает подтверждение. После этого можно собрать простой отчет: сколько точек готово, где есть пропуски, какие фото не прикреплены, что осталось в черновиках. Хорошо, если синхронизация умеет продолжать с места остановки, если сеть снова пропала.
Начните не с «всего сразу», а с одного главного сценария: создать объект, добавить точку, записать одно измерение, прикрепить фото и комментарий, затем синхронизировать при появлении связи.
Хороший стартовый план:
Дальше расширяйте итеративно, только когда базовый сценарий не ломается. Обычно первыми добавляют новые типы измерений (разные приборы и единицы), экспорт (CSV/GeoJSON по требованиям заказчика), роли (исполнитель, проверяющий), историю изменений и журнал действий. Модель лучше усложнять постепенно: чаще достаточно справочника типов измерений, чем множества новых сущностей.
Для быстрого старта TakProsto позволяет через чат собрать прототип экранов и модели, а затем точечно доработать офлайн и синк. Если вы работаете на takprosto.ai, полезно сразу включить то, что помогает не бояться правок: снапшоты и откат, экспорт исходников, деплой и хостинг для тестового сервера.
По тарифам логика обычно простая: начать с free для проверки идеи и UX, перейти на pro, когда нужно больше проектов и стабильные сборки, и рассмотреть business, если работает команда и важны управляемые доступы и процессы.
Начните с тройки сущностей: Объект хранит общие данные съемки, Точка описывает место и контекст, Измерение фиксирует, как именно получено значение. Такая структура не путает «где» и «как измерили» и хорошо работает для офлайна и синхронизации.
Сделайте обязательными только поля, без которых дальше все ломается: у объекта — id, название, система координат, статус; у точки — id, ссылка на объект, код/номер, координаты или возможность их оставить пустыми, тип и время создания; у измерения — id, ссылка на точку, метод, дата/время и значение. Остальное оставьте опциональным, чтобы в поле можно было быстро набросать данные и дополнять позже.
Используйте два идентификатора: localId (UUID) создается на телефоне сразу и участвует во всех связях, а serverId появляется после синхронизации. Пока serverId пустой, связывайте точки, измерения и фото только через localId, иначе легко получить «сиротские» записи и проблемы при повторной отправке.
Правило простое: любое действие сначала пишется локально, а синхронизация идет отдельным фоновым процессом. Для структуры «объект → точка → измерение» обычно хватает SQLite и правильно подобранных индексов, чтобы поиск по коду точки и фильтры по статусам не тормозили даже на большом объекте.
Держите очередь изменений (outbox): приложение складывает туда операции insert/update/delete с временем, типом сущности и статусом отправки. Тогда синк не «угадывает», что делать, а просто последовательно отправляет накопленные операции, умеет повторять при ошибках и не теряет изменения после перезапуска.
Фото храните файлами на устройстве, а в базе держите только метаданные: привязку к точке или измерению, локальный путь, время создания и признак загрузки. При синхронизации сначала отправляйте метаданные, а потом файл, чтобы можно было безопасно возобновлять загрузку после обрыва связи.
Используйте мягкое удаление через deletedAt, а не физический delete, и удаляйте окончательно только после подтверждения от сервера. Если пользователь удаляет точку, заранее определите правило для измерений и вложений: чаще всего их тоже нужно помечать удаленными, чтобы на сервер не ушли данные без родителя.
По умолчанию синхронизируйте в режиме pull + push: сначала заберите изменения сервера, затем отправьте локальные. Для конфликтов лучше опираться на версии (revision/updatedAt) и решать конфликт на уровне полей, потому что добавление фото и правка типа точки обычно не должны мешать друг другу.
Держите первый релиз коротким: список объектов, список/карта точек, карточка точки с фото и заметками, форма добавления измерения и журнал изменений. Валидации делайте на вводе и показывайте предупреждения для подозрительных значений, чтобы не останавливать работу в поле, но и не плодить явные ошибки.
Проверяйте сценарий «без сети» целиком: создание объекта, 10–30 точек, несколько фото, редактирование, перезапуск приложения и последующая отправка при появлении интернета без дублей. Если собираете прототип в TakProsto, удобно сначала описать модель и правила синка в Planning mode, затем сгенерировать Flutter-клиент с SQLite и сервер на Go + PostgreSQL, а после — прогнать тест и откатиться снапшотом, если правки сломали офлайн.