Database

Every live table on the device: what it holds, who writes it, who reads it, and how it syncs.

Urvi keeps its data in six on-device stores. Two of them hold the same farmers, lands and crop plans in different shapes. Every table is described column by column in the table reference; this page explains how the pieces fit and what a rewrite should change.

Paths use app/... for app/src/main/java/org/platformcommons/urvi/ and data/... for data/src/main/java/org/mindtree/app/agricultor/data/.

The six on-device stores

Store (file name)VersionTablesPurposeOwner
agri_db6264 registered (55 live)Main domain data: farmers, lands, crop plans, farming models, compact blocks, reference data, endorsements, sync errors.:data module, Room (data/db/AppDatabase.kt)
ie_register.db2012 (11 live)Legacy UI copy of farmers, assets, portfolios and inventory, plus dropdown data from bundled assets and FCM notifications.:app module, Room (app/datasource/database/AppDatabase.kt)
error_log_db21Raw HTTP failure log of sync calls. Only read when the file is exported.:data module, Room (data/db/ErrorLogDatabase.kt)
sync_ledger_db13App outbox: which local rows need POST, PATCH, PUT or DELETE, and in what order.:data module, Room (data/db/SyncLedgerDB.kt), schema not exported
SYNC_QUEUE_DBn/a1 (OPERATION)The HTTP request queue that actually sends data.External library OfflineAndroid / CoreAPI (plain SQLite)
Assessment library DBunknownunknownAssessments, activity tracker data and attachments.External library commons-assessment-urvi-lib; source not in this repo

Facts that apply to all of them:

  • No store is encrypted. Aadhaar, ration card and phone numbers are in plain text.
  • agri_db and sync_ledger_db allow queries on the main thread.
  • ie_register.db uses full destructive fallback: any missing migration wipes it.
  • Only sync_dependency has real foreign keys. Every other link is by convention.

How data flows

  1. Setup and the "data down-sync" screens download data into agri_db.
  2. A user edit writes to agri_db with syncStatus = SYNC_NOT_INITIALIZED and adds a row to sync_ledger.
  3. Sync workers turn ledger rows into library operations in SYNC_QUEUE_DB.
  4. Response handlers (data/sync/rh/*RH.kt) write server ids back and mark the row SYNC_DONE.
  5. "Bridge" mappers (app/bridge/*) copy farmers, assets and portfolios into ie_register.db, which many older screens still read.

Three farmer representations

The same farmer exists in three tables, each with its own key.

TableStoreShapePrimary keyBusiness keyUsed for
ie_flat_entityagri_dbOne wide row from the server report datasetuniqueIdentifier (local autoincrement)uuid (farmer alias), iEOLTPId (server id)Farmer list, filters, endorsement, compact blocks
ie + 6 ie_person_* / ie_ietagging child tablesagri_dbPlatform API DTOappId (local UUID)aliasId (farmer alias)What gets POSTed and PATCHed
ie (legacy)ie_register.dbDenormalised UI projectionuniqueIdentityKey (local autoincrement)aliasId (farmer alias)Farmer detail screen, Add farmer

How the keys connect, in plain words:

  • Farmer alias is the same value in all three: ie_flat_entity.uuid = ie.aliasId = legacy ie.aliasId.
  • Server farmer id is ie_flat_entity.iEOLTPId. Other tables store it as owningEntityId (assets, portfolio, farming models, compact block members), entityId (endorsements), iEId (sync errors) and ie_id (roles).
  • Local farmer id is ie.appId. Rows created before the farmer reaches the server point to it through appmill_metadata_ie_appId.
  • Farming models also use aliasId to find farmers that are not yet synced.

Because the three copies are filled by different code paths, they drift. If the bridge did not run, the farmer detail screen shows blanks.

Sync status lifecycle

Most agri_db tables carry three columns from BaseEntity: appId (local UUID primary key), opId and syncStatus.

StepRow syncStatussync_ledger.sync_statusWhat happens
Downloaded from serverSYNC_DONE (default)no rowNothing to send.
Created or edited on deviceSYNC_NOT_INITIALIZEDSYNC_NOT_INITIALIZEDA ledger row is added. Dependencies are recorded in sync_dependency (for example, a crop plan waits for its farmer).
Worker picks it upIN_SYNC_QUEUEIN_SYNC_QUEUEA library operation is created; its id goes into opId and sync_op_id.
Server acceptsSYNC_DONESYNC_DONEThe response handler writes the server id. Done ledger rows are deleted by collectGarbage.
Server rejectsunchangedunchangedFailure goes to error_logs, sync_error_logs and/or sync_errors; PLIs and farming models set hasSyncError.

Rules worth keeping:

  • A newer PUT replaces a PUT that has not started yet, so the latest edit wins.
  • While an entity has a pending ledger row, the UI locks editing (checkIfEntityHasPendingSync).
  • sync_work_status is a single-row flag that stops two sync runs at once.

Known gaps:

  • The row status and the ledger status can disagree.
  • ATM harvest skips the ledger and writes straight into the library queue.
  • Server id is null or 0 until synced. Many queries use id IS NOT NULL AND id != 0 to mean "on the server".

SharedPreferences and files

All main keys sit in the default, unencrypted SharedPreferences file, shared by the :data module (data/pref/SharedPrefSource.kt) and the :app module (PreferenceUtil, LocaleManager). Values are not listed here.

Key groupExample keysMeaning
Session and identityX-SESSIONID, key_tenant_*, key_user_*, account, user_account, key_is_unit_loginSession header and logged-in user/tenant. The session token is stored in plain text.
Setup and languagesetup_status, KEY_IS_SETUP_COMPLETED, is_synced, language, language_status, selectedLanguage, refresh_dataOnboarding state and chosen language.
Work node and tenantworknode_detail, VOWorkNodeIdSelected VO/habitation (two Vrutti-only keys are unused, see Set-aside code).
Sync progressSHARED_PREF_KEY_LAST_SYNC_TIME, PREF_KEY_FARMER_SYNC_*, PREF_KEY_ALL_SYNCS_COMPLETE, SHARED_PREF_KEY_LAST_FORCE_SYNC_LOG_TIMELast sync time and down-sync paging.
Cached API datakey_product_data, key_product_json_data, key_facility_data, key_disabled_land_data*, cb_crops_data, pre_cb_crops_data, 365DKG, SyncErrorLogsJSON copies of server data.
Screen statekey_habitat_vo_filter, key_selected_vo, key_selected_shg, key_selected_cadre, key_is_endorsement_cachedFilters and endorsement selections.
Content and analyticskey_program_configuration, key_program_instructions, key_aap_reject_reasons, key_youtube_video_links, key_analytics_*, key_login_history, key_current_season_valueInfo screens, dashboard cache, history.
Clear-data auditshared_pref_key_is_app_data_cleared, shared_pref_key_is_cleared_data_sent_to_firebaseFlags after a data wipe.

Other key-value stores: device_registration_prefs (device session id), an osmdroid map cache file, and Android AccountManager.

File locationContent
filesDir/geofencing/Polygon files for land, crop plan and ATM geofences
filesDir/images/Camera photos and downloaded attachment images
cacheDir/Endorsement abstract PDFs, map exports
getExternalFilesDir(null)ATM harvest photos
getExternalFilesDir("/")/<app_name>/Full copy of prefs, files and databases, zipped for "share logs"

The "share logs" zip contains agri_db (with PII) and the prefs (with the session token). SYNC_QUEUE_DB is uploaded to Firestore. The FileProvider exposes very broad roots.

Migration situation

StoreState
agri_db62 versions. Manual migrations up to 52, auto-migrations 52-58 and 59-62. Schema JSONs 10-51 are missing, so old migrations cannot be tested. MIGRATION_1_2 is defined but not registered. Migrations 41-42 and 42-43 are empty. harvest_logs was dropped and recreated in 14-15 (data lost). MIGRATION_12_13 left an orphan table asset_quality_labels. One migration has a typo'd column type (INTERGER); another sets a preference.
ie_register.db20 versions, manual up to 18, then auto. Destructive fallback wipes it on any gap.
error_log_dbv2, one rebuild.
sync_ledger_dbv1, no migrations, schema not exported.

Advice for the rewrite: do not migrate in place. Start a fresh schema at v1. Before the old stores are discarded, upload every unsynced row (syncStatus != SYNC_DONE) and every pending sync_ledger / SYNC_QUEUE_DB operation, or force a full re-download once the queue is empty.

Data model problems

ProblemExampleEffect
Two databases for one domainie_register.db copies farmers, assets, portfoliosDouble writes, drift, blank screens
Three farmer tablesie_flat_entity, ie, legacy ieFour different join keys
Inconsistent identityappId, uuid, autoincrement and server id all used as PK; land linked by asset (TEXT), assetId (INTEGER) and assetCheckSumHard joins, wrong matches
Random appId on downloaded rowsBaseEntity.appId = UUID.randomUUID() with REPLACERe-downloads can insert duplicates
Child tables never clearedasset_*_labels, ie_person_*, product_nameml, refdata_labelsLikely duplicate rows after each refresh (verify on a device)
Mixed conflict rulesinsert(List) is IGNORE, insert(T) is REPLACECompact blocks never refresh; farmer REPLACE wipes device-only flags
No foreign keys, few indexesowningEntityId, portfolioId, titleId, iEOLTPId, entityId unindexedSlow lists, orphans after hard deletes
Wrong queriesappId LIKE, assetCode LIKE '%12%', ORDER BY a TEXT date, season update on all PLIsWrong rows, missed index
Over-flattened DTOsworknodehierarchy 36 cols, inventoryproduct 44, units copied into every rowWide tables, repeated data
Duplicate reference dataglobalrefdata vs gref_data; refdata vs asset_master_data; five product stores; languages hardcoded (ENG/TEL, ENG/TEL/TAM)Inconsistent labels
Three error stores, two notification storessync_errors, sync_error_logs, error_logs; notifications vs comment rowsConfusing error UI
JSON blobs and year columnsfarmingModelLineItemDTOs, annualActionPlanData2526, NFCategory2526Not queryable; new column every year
Mixed time typesTEXT millis, INTEGER millis, DateWrong sorting
PrivacyUnencrypted PII, plain session token, full DB in share-logs zipData exposure risk
God repositoryDataRepository.kt, 3,395 linesEvery feature coupled to one class

Target data model

One encrypted database, local UUID primary keys, a unique nullable server id on every synced table, real foreign keys to local ids, and epoch-millis INTEGER times.

EntityKey columnsRelationshipsReplaces
farmerlocal_id PK, server_id UNIQUE, alias_id UNIQUEbelongs to geo_node (habitation, VO, SHG)ie_flat_entity, ie + children, legacy ie
farmer_identity, farmer_contact, farmer_familylocal_id, farmer_id FKmany per farmerie_person_*
geo_nodelocal_id, server_id, type, name_keytree via geo_edge(parent_id, child_id)worknode*, worknodehierarchy
farmer_rolefarmer_id FK, role_codemany per farmerierolemap, legacy designation
assetlocal_id, server_id, farmer_id FK, category_code, area, uom_codemany per farmer; asset_lease, asset_spec childrenfarmerassets, ie_asset
geofencelocal_id, owner_type, owner_id, file_path, server_attachment_idone per asset / plan line / farming modelgeofence columns on 4 tables
portfoliolocal_id, server_id, farmer_id FKone per farmerportfolio, ie_portfolio
plan_linelocal_id, server_id, title_id, portfolio_id FK, asset_id FK, seasonmany per portfolioportfolio_portfoliolineitem
plan_croplocal_id, plan_line_id FK, product_code, dates, area_sharemany per plan_linePPLI, ie_portfolio_productsku
plan_answerplan_line_id FK, question_code, answermany per plan_lineportfolio_question_response
farming_modellocal_id, server_id, farmer_id FK, asset_id FK, model_typefarming_model_crop childrenfarming_model and its JSON crops
harvestlocal_id, server_id, crop_type (plan or model), crop_idone or more per cropharvest_logs, farming_model_harvest, JSON harvests
compact_blocklocal_id, server_id, title_id UNIQUEcompact_block_member(block_id FK, plan_line_id FK, farmer_id FK)compact_block, compact_block_member
endorsementlocal_id, entity_type, entity_id, statuscomment childrenendorsement_entity, endorsement_comment_entity
notificationlocal_id, source, read_atnonenotifications, NOTIFICATIONS comment rows
farmer_planfarmer_id FK, plan_year, data JSONmany per farmerAAP and NF year columns
product, product_skucode PKlocalized_labelproduct*, product_master, product_sku_master, cropdata
practice, method, ref_data, uomcode PKlocalized_labelmasters, globalrefdata, gref_data, refdata, asset_master_data, legacy uom
localized_label(entity, code, lang) PK, textany language, no hardcodingall *_name, *_labels, *_nameml tables
attachmentlocal_id, owner_type, owner_id, path, server_idany ownerimage columns on PLI, ATM, harvest
outboxlocal_id, entity, entity_local_id, op, payload, depends_on, attempts, last_error, statuswritten in the same transaction as the datasync_ledger, sync_dependency, SYNC_QUEUE_DB, row syncStatus
sync_checkpointkey UNIQUE, cursor, status, updated_atnonesync_status, worknode page counters, paging prefs
error_log, analytics_eventlocal_id, created_atnoneerror_logs, sync_errors, sync_error_logs, user_analytics

Session tokens move to secure storage (Keystore / EncryptedSharedPreferences, or flutter_secure_storage). Support exports should be redacted.

All tables