Architecture: Data Model
ChatKcal uses AWS DynamoDB with a Single Table Design (STD) optimized for time-series retrieval per user.
1. Core Schema
The database relies on a composite primary key structure to segregate users and sort their data by time.
- Partition Key (PK):
USER#<Cognito_Sub_ID>- Scope: All data for a single user lives in one partition.
- Sort Key (SK):
MEAL#<ISO_Timestamp>#<Unique_ID>- Behavior: Allows efficient querying of date ranges.
- Unique ID: Appended to ensure no collisions if two meals are logged at the exact same second.
A standard meal record containing the nutritional data and metadata.
- Attributes:
mealSummary: String (e.g., "Bowl of Chili")emoji: String (e.g., "🥣")calories,protein,carbs,fat: FloatextendedNutrients: Map\(e.g., {"fiber": 5.2})notes: StringuserDate: String (e.g., "2025-12-31") - Explicit logical date for aggregation.createdAt: ISO TimestampformatVersion: Number (Current: 4)
Migration Note
Records created prior to version 4 used PascalCase attributes (e.g., MealSummary) and Int values. The system handles both formats transparently.
Stores user-specific settings and goals.
- SK:
CONFIG#TARGETS - Attributes:
calories,protein,carbs,fat: FloatdisplayUnit: String ("kcal" | "kj")nutrientRegistry: Map\(Definitions for extended nutrients) version: Number (Optimistic Locking)updatedAt: ISO Timestamp
Target Validation
- Partial Updates: Users can update a subset of targets (e.g., only Protein).
- Zero Values (
0): Standard way to "clear" or disable a specific target. - Nulls: Ignored by the backend to prevent accidental deletion.
2. Aggregation Strategy
To efficiently support "Unique Days Logged" and "Long-term Charting", we maintain a separate "Summary Item" for each day.
The Day Summary Entity
Instead of scanning thousands of meal records, we update a summary record atomically using TransactWriteItems.
Structure:
- SK:
SUMMARY#<YYYY-MM-DD> - Attributes:
type: "DaySummary"totalCalories,totalProtein,totalCarbs,totalFat: FloatextendedNutrients: Map\mealCount: NumberupdatedAt: ISO Timestamp
Pipeline Flow
| Action | DynamoDB Operation |
|---|---|
| Create Meal | PutItem (Meal) + UpdateItem (ADD totals to Summary) |
| Delete Meal | DeleteItem (Meal) + UpdateItem (SUBTRACT totals from Summary) |
3. Data Format Migration (Version 4)
As of January 2026, the application transitioned from PascalCase/Int to camelCase/Float.
Hybrid Casing Strategy
To ensure zero downtime:
- Write Path: Always uses
camelCase(FormatVersion 4+). - Read Path: Checks both casings (e.g.,
item.calories || item.Calories). - Maintenance: An optional standardization script is available at
scripts/migrate_data_format.py.
4. Future: Flexible Nutrient Schema
Proposed Change
We are currently planning a transition to support user-defined extended nutrients (e.g., Fiber, Sodium).
- Proposed Strategy:
- Store extended nutrients in a DynamoDB Map
extendedNutrientson bothMealandDaySummary. - Extend the existing
CONFIG#TARGETSentity to house nutrient tracking preferences and history.
- Store extended nutrients in a DynamoDB Map
- Efficiency: Combining settings and history into the existing
CONFIG#TARGETSrow minimizes unnecessary DynamoDBGetItemrequests and keeps the application cost-effective. - Details: See the ADR 001: Flexible Nutrient Schema for technical implementation details.
5. Time Logic (The "4 AM Rule")
Single Source of Truth
If we ever change the definition of a "Day" (e.g., changing the 4 AM start time), we rely on the userDate attribute on the Meal records to rebuild the summaries.