← Back to docs

Calorie tracker data model

Purpose

This model is designed for a trustworthy, low-friction calorie and nutrient tracker. It supports packaged foods, whole foods, household recipes, common dishes, saved meals, quantity ranges, barcode/search logging, repeat logging, and AI-assisted drafts without allowing estimates to masquerade as exact data.

The database is PostgreSQL hosted by Supabase. Flutter may read and write user-owned CRUD data through Row Level Security (RLS). Privileged imports, AI processing, and computed operations run through trusted server code.

Design rules

  1. A historical log never changes when catalog data changes. Every confirmed log stores a nutrient snapshot.
  2. Precision is explicit. Estimated quantities and nutrients carry minimum, expected, and maximum values.
  3. Published nutrition records are immutable. Corrections create a new item version.
  4. Meals, recipes, and foods are distinct. Foods and recipes are loggable nutrition items; saved meals are reusable collections of those items.
  5. Public and user-owned data share behavior, not permissions. Ownership and visibility are explicit on every relevant row.
  6. All external data is traceable. Imported records retain source, source identifier, version, and timestamps.
  7. AI creates drafts only. A user confirms or edits a draft before it becomes a log.
  8. Canonical units are boring. Mass uses grams, volume uses millilitres, energy uses kcal, and each nutrient has one canonical unit.
  9. User-local dates are stable. Logs retain the IANA timezone and local calendar date that applied when consumed.
  10. Retries are safe. Client-created records use UUIDs and idempotency keys.

Domain boundaries

Core tables

All primary keys are UUIDs unless noted. Every mutable table has created_at and updated_at. User-deletable synced tables also have deleted_at as a tombstone.

Identity and onboarding

profiles

One row per auth.users row.

user_preferences

Use join tables instead of JSON when a value becomes searchable or gains business behavior.

user_goal_selections

Supports up to three onboarding goals without embedding an array in profiles.

Goals and measurements

body_measurements

Append-only measurement history.

nutrition_goals

Effective-dated user intent.

Only one active goal may overlap a given date.

daily_targets

Versioned targets so historical reports use the target that was active that day.

Generated targets must pass safety limits before activation. The calculation service records the formula version and never silently changes an active target. A user may have no target row at all; this is observe mode, not a zero-calorie target.

Data provenance

data_sources

import_runs

Server-only operational record.

Rejected rows go to protected import diagnostics, not into the catalog.

Nutrient dictionary

nutrients

Canonical units never change after use. Source-specific units are converted at ingestion boundaries.

Nutrition items

nutrition_items

Represents an atomic food, common dish, or recipe.

RLS treats owner_user_id IS NULL as catalog data, not globally writable data.

nutrition_item_versions

Published versions are append-only.

nutrition_values

One row per nutrient and item version.

Amounts are per the version's basis. Constraints enforce 0 <= min <= expected <= max. Exact label values use the same number for all three fields.

serving_options

A serving must either resolve to grams or directly resolve to a known item basis. UI labels are localized separately from stable unit codes.

barcodes

Index normalized barcode lookup. Do not assume a barcode identifies the same formulation in every market.

item_aliases

Search combines names, aliases, brands, recency, user ownership, and source quality. PostgreSQL full-text search plus trigram indexes are sufficient initially.

Recipes and cooking

recipe_details

One-to-one with a recipe item version.

recipe_ingredients

A database/service validation prevents recipe cycles. Recalculating a recipe creates a new recipe version rather than rewriting a published version.

retention_profiles and retention_factors

Retention math must be covered by fixture-based tests and reviewed before it becomes user-facing. It is an estimate, so calculated recipe outputs retain ranges and provenance.

Saved meals

meal_templates

meal_template_items

Logging a template creates fresh log rows in one transaction. It does not point reports directly at mutable template rows.

Confirmed logs

meal_logs

Header for one eating event.

Clients may assemble items and nutrient snapshots only while a meal is a draft. finalize_meal_log validates ownership, requires at least one item and nutrient snapshots for every item, then activates the meal. Active or deleted history rejects direct child inserts and all direct mutation.

logged_items

logged_nutrients

Immutable nutrition snapshot used for totals and reports.

Daily totals are derived from logged_nutrients. A cache may be introduced only after measurement shows aggregation is too slow; snapshot rows remain the source of truth.

log_change_events

Append-only audit trail for corrections and deletion.

The normal edit transaction validates ownership, updates the current user-visible log, and appends an event. Audit events are deleted with their owning account so they cannot block the promised account-deletion path.

Assisted logging drafts

logging_drafts

draft_items

Confirmation uses a server transaction to create meal_logs, logged_items, and logged_nutrients. Provider output is validated before it reaches these tables. Raw model responses are not trusted domain records. NLP drafts identify food and quantity; deterministic nutrition calculations produce calorie and nutrient snapshots.

user_entitlements

Server-managed capabilities keep subscription decisions out of Flutter authorization logic.

Feature flags can disable a provider or enhanced parsing globally. Standard text parsing remains functional when enhanced parsing is removed, replaced, unavailable, or no longer entitled.

See docs/free-text-meal-parsing.md for the parser contract and failure policy.

Personalization

user_favorites

meal_patterns

Derived, replaceable data—not authoritative history.

suggestion_events

This allows recommendation usefulness and notification fatigue to be measured.

Important transactions and RPCs

Use database functions or trusted Node endpoints where multiple writes must succeed together:

  1. finalize_meal_log(meal_log_id)
  2. confirm_logging_draft(draft_id, edits, idempotency_key)
  3. log_meal_template(template_id, consumed_at, quantity_overrides, idempotency_key)
  4. repeat_meal_log(source_meal_log_id, consumed_at, quantity_overrides, idempotency_key)
  5. publish_recipe_version(recipe_id, calculation_inputs)
  6. activate_daily_target(target_id)

Every function checks auth.uid()/server identity, validates all ranges and references, and returns a complete immutable result rather than requiring follow-up mutation.

RLS policy matrix

RLS tests must prove both allowed access and cross-user denial for every table and RPC. The Supabase service-role key never ships in Flutter.

Integrity and indexing

Required constraints and indexes include:

Macro-derived energy (4p + 4c + 9f) is a quality warning, not a hard constraint, because fiber, alcohol, organic acids, and source labeling rules create legitimate differences.

Validation and test fixtures

Before UI work depends on the model, automated tests cover:

Explicit non-goals for the first tracker release