Todo Serialization Format and Mapping Logic in Lifetrace: A Complete Guide

The Lifetrace backend converts SQLAlchemy Todo models into flat Python dictionaries using TodoIcalMixin._todo_to_dict, applying field-specific fallback logic and normalization helpers to match the TodoResponse schema before JSON serialization.

The freeu-group/lifetrace repository provides a robust task management backend that bridges database storage with API consumption and iCalendar export. Understanding the todo serialization format is essential for developers integrating with the Lifetrace API or extending its calendar functionality. When a Todo entity is retrieved from the database, it undergoes a precise transformation process defined in the storage layer that maps SQLAlchemy model attributes to a standardized dictionary structure.

Core Serialization Architecture

The _todo_to_dict Method

The conversion logic resides in lifetrace/storage/todo_manager_ical.py within the TodoIcalMixin class. The _todo_to_dict method accepts an active SQLAlchemy session and a Todo model instance, returning a plain Python dictionary that aligns exactly with the TodoResponse schema defined in lifetrace/schemas/todo.py.

This flat dictionary serves as the intermediate format for both JSON API responses and iCalendar component generation, ensuring consistent data representation across all output channels.

Field Mapping Logic and Fallbacks

The mapping logic handles over 30 fields with intelligent fallbacks to ensure data integrity and backward compatibility:

  • id: Required field mapping directly from todo.id; raises ValueError when missing.
  • uid: Maps from todo.uid; may be None.
  • name: Direct mapping from todo.name.
  • summary: Uses getattr(todo, "summary", None) or todo.name to guarantee a non-empty string by falling back to the name field.
  • due: Maps from todo.due with fallback to the legacy todo.deadline field.
  • dtstart/dtend: Maps from todo.dtstart and todo.dtend, falling back to start_time and end_time respectively.
  • time_zone: Maps from todo.time_zone, with tzid as an alternative source.
  • is_all_day: Boolean field that defaults to False when the database column is None.
  • percent_complete: Defaults to 0 when the database value is None.
  • order: Defaults to 0 when not explicitly set.
  • item_type: Stored as an upper-case iCalendar component (e.g., VTODO).
  • ical_status: Maps from todo.ical_status or derives from todo.status via _to_ical_status.
  • dtstamp, created, last_modified: Various timestamp columns with fallbacks to updated_at.
  • source_type, source_key, source_date: Direct attribute mappings for optional metadata.

Relational data is hydrated separately via abstract methods:

  • tags: Retrieved via _get_todo_tags(session, todo_id), returning a list of strings.
  • attachments: Retrieved via _get_todo_attachments(session, todo_id), returning a list of dictionaries matching TodoAttachmentResponse.
  • related_activities: Processed through _safe_int_list to guarantee a list of integers.

Data Normalization Helpers

Specialized utilities in lifetrace/storage/todo_manager_utils.py ensure consistent data formats before serialization.

Status Mapping with _to_ical_status

The _to_ical_status function maps internal status values to iCalendar-compliant strings. According to the source code, active maps to NEEDS-ACTION, completed maps to COMPLETED, and canceled maps to CANCELLED.

Reminder Offset Normalization

The _normalize_reminder_offsets helper parses raw JSON strings into clean, sorted lists of positive integers. For example, the input '[30, "15", -5, "bad"]' is sanitized to [15, 30], with negative values, duplicates, and invalid entries removed. The test suite in tests/test_todo_serialization.py confirms that None values become empty lists.

Safe Integer List Conversion

The _safe_int_list utility safely converts potentially null or JSON values into lists of integers, used specifically for the related_activities field to ensure type safety.

Working with Todo Serialization

Converting a Todo Instance


# Assuming manager is a concrete subclass of TodoIcalMixin

# and session is an active SQLAlchemy session

from lifetrace.storage.models import Todo

todo = session.query(Todo).get(42)
data = manager._todo_to_dict(session, todo)

# data is now a dict matching TodoResponse schema

Expected JSON Output Structure

{
  "id": 42,
  "uid": "2023-10-12T09:00:00Z",
  "name": "Buy groceries",
  "summary": "Buy groceries",
  "description": "Milk, eggs, bread",
  "is_all_day": false,
  "status": "active",
  "priority": "none",
  "reminder_offsets": [10, 30],
  "tags": ["shopping", "errands"],
  "attachments": [],
  "related_activities": [7, 12],
  "created_at": "2023-10-10T12:00:00Z",
  "updated_at": "2023-10-11T08:45:00Z"
}

Using Normalization Helpers Directly

from lifetrace.storage.todo_manager_utils import _normalize_reminder_offsets
from lifetrace.storage.todo_manager_ical import _to_ical_status

# Clean malformed reminder offsets

raw_offsets = '[30, "15", -5, "bad"]'
clean_offsets = _normalize_reminder_offsets(raw_offsets)

# Result: [15, 30]

# Map status to iCalendar format

ical_status = _to_ical_status("completed")

# Result: "COMPLETED"

Summary

  • The todo serialization format in Lifetrace is a flat Python dictionary matching the TodoResponse Pydantic schema defined in lifetrace/schemas/todo.py.
  • Core logic resides in lifetrace/storage/todo_manager_ical.py, specifically within TodoIcalMixin._todo_to_dict.
  • Field fallbacks ensure data completeness, such as summary falling back to name, and due falling back to deadline.
  • Normalization helpers enforce data quality by parsing reminder offsets, mapping status values to iCalendar format, and safely converting integer lists.
  • Relational data (tags and attachments) is loaded via abstract methods that must be implemented by concrete manager subclasses.

Frequently Asked Questions

Where is the main todo serialization logic implemented?

The primary serialization logic is implemented in lifetrace/storage/todo_manager_ical.py within the TodoIcalMixin class. The _todo_to_dict method performs the conversion from SQLAlchemy model to dictionary format that matches the TodoResponse schema.

How does Lifetrace handle missing summary fields?

When the summary attribute is null or missing, the serializer falls back to the name field using the expression getattr(todo, "summary", None) or todo.name. This ensures the API always returns a non-empty string for the summary field.

What format are reminder offsets stored in after serialization?

Reminder offsets are normalized into a sorted list of positive integers. The _normalize_reminder_offsets helper parses JSON strings, filters out negative values and duplicates, and converts valid entries to integers, returning an empty list if the input is None.

How is the Todo status mapped to iCalendar format?

The _to_ical_status function maps internal status values to iCalendar-compliant strings: active becomes NEEDS-ACTION, completed becomes COMPLETED, and canceled becomes CANCELLED. This mapping ensures compatibility with standard calendar applications.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →