← Back to Projects Main Page

Ritma — Cycle Tracking SaaS with Partner-Aware Notifications

A privacy-first backend system that models biological cycles, computes probabilistic phases, and delivers context-aware notifications to both users and their partners — without exposing sensitive data or relying on external analytics platforms.

This project started as a simple Telegram bot to track menstrual cycle events. It worked — but the architecture was tightly coupled, state was implicit, and business logic was scattered across handlers.

I rebuilt it as a SaaS-style backend system with explicit domain boundaries: cycle modeling, event storage, notification orchestration, and partner-aware delivery. The goal was not to add features, but to make the system to make the system deterministic, modular, and consistent under real-world data.

The current system supports multi-partner relationships, customizable notification templates, phase-based logic, and strict separation between core business logic and delivery adapters.

Outcome: a production-ready architecture that behaves deterministically under real-world data inconsistencies and supports controlled feature expansion without breaking existing flows.

Category
automation
Stack
python asyncio telegram-bot mongodb pytest domain architecture event modeling background workers privacy-first notification engine
Project Type

My project

Role

Python Automation Engineer / Backend Developer

Status

Completed · Production-validated

Timeline

2 weeks · 4 milestones

5 iterations · architecture → SaaS → UX → notifications → stability

Engineering Metrics

784 tests · 91% coverage · 16s runtime

Architecture

Clean architecture · Use cases layer · Repository pattern · Adapter isolation

Core Features

Multi-partner model · Phase-based notifications · Custom message templates

Data Safety

Isolated user data · Soft-delete model · Controlled access per recipient

Design Decisions

Stateless worker · Idempotent notifications · Timezone-safe logic

Reliability

Failure-safe sending · Retry-ready architecture · No duplicate notifications

Scalability

Async processing · Background workers · Ready for queue integration

Product Surface

Telegram menus · Calendar input · Doctor report · Partner management · Custom notifications

Data Model

Events / Notifications / Partner links / Preferences / Templates

Guarantees

No duplicate periods · No duplicate notifications · Consistent partner state · Idempotent writes

Architecture

ritma/
├── .gitignore
├── README.md
├── config.py
├── pytest.ini
├── requirements.txt
├── requirements-dev.txt
│
├── adapters/
│   ├── __init__.py
│   └── telegram/
│       ├── __init__.py
│       ├── bot.py
│       ├── helpers.py
│       ├── i18n.py
│       ├── keyboards.py
│       └── handlers/
│           ├── __init__.py
│           ├── backfill.py
│           ├── calendar.py
│           ├── delete_events.py
│           ├── events.py
│           ├── menu.py
│           ├── onboarding.py
│           ├── pairing.py
│           ├── partners.py
│           ├── privacy.py
│           ├── report.py
│           ├── start.py
│           ├── timezone.py
│           └── when.py
│
├── core/
│   ├── __init__.py
│   ├── accounts.py
│   ├── cycle.py
│   ├── day_key.py
│   ├── events.py
│   ├── notifications.py
│   ├── pairing.py
│   ├── recipients.py
│   ├── templates.py
│   ├── time.py
│   └── use_cases/
│       ├── __init__.py
│       ├── create_event.py
│       ├── invites.py
│       └── notify.py
│
├── db/
│   ├── __init__.py
│   ├── connection.py
│   ├── indexes.py
│   └── repositories/
│       ├── __init__.py
│       ├── events_repo.py
│       ├── invites_repo.py
│       ├── message_templates_repo.py
│       ├── notifications_repo.py
│       ├── partner_links_repo.py
│       ├── privacy_repo.py
│       ├── recipient_preferences_repo.py
│       └── users_repo.py
│
├── locales/
│   ├── en.json
│   ├── es.json
│   └── ru.json
│
├── workers/
│   ├── __init__.py
│   └── notify_worker.py
│
└── tests/
    ├── __init__.py
    ├── conftest.py
    │
    ├── adapters/
    │   └── telegram/
    │       ├── test_delete_events.py
    │       ├── test_handlers.py
    │       ├── test_keyboards.py
    │       ├── test_onboarding.py
    │       ├── test_partners.py
    │       ├── test_privacy.py
    │       ├── test_report.py
    │       ├── test_start.py
    │       ├── test_timezone.py
    │       └── test_when.py
    │
    ├── core/
    │   ├── test_create_event.py
    │   ├── test_cycle.py
    │   ├── test_day_key.py
    │   ├── test_events.py
    │   ├── test_invites.py
    │   ├── test_notifications.py
    │   ├── test_notify.py
    │   ├── test_pairing.py
    │   ├── test_recipients.py
    │   └── test_templates.py
    │
    ├── db/
    │   ├── test_events_repo.py
    │   ├── test_indexes.py
    │   ├── test_invites_repo.py
    │   ├── test_message_templates_repo.py
    │   ├── test_notifications_repo.py
    │   ├── test_partner_links_repo.py
    │   ├── test_privacy_repo.py
    │   ├── test_recipient_preferences_repo.py
    │   └── test_users_repo.py
    │
    └── workers/
        └── test_notify_worker.py

Domain-driven separation between core logic, delivery adapters, persistence layer, and background workers.

Project repository structure

Private repository structure showing isolated Telegram adapters, domain logic, repositories, background workers, and a dedicated test suite.

Telegram Product Flow

The current delivery adapter is Telegram, but the interaction model is designed as a product flow rather than a command-only bot. Users can navigate cycle predictions, calendar actions, doctor reports, partner management, and settings through structured menus.

User Actions

  • Add or delete cycle-related events
  • Check predicted next period
  • Open calendar-based input flows
  • Generate a doctor-oriented summary report
  • Manage settings and privacy actions

Partner Actions

  • Create invite links for partners
  • See who is subscribed to notifications
  • Disable or remove partner access
  • Customize notification text per partner
  • Control which phase notifications a partner receives
Ritma Telegram bot main menu Ritma Telegram bot calendar input Ritma Telegram bot partner management menu

Telegram UI showing the live product flow: main menu, calendar input, and partner-aware notification controls.

Overview

Ritma is not just a tracker — it is a behavioral system built around time-based biological events. Instead of storing static data, it models temporal sequences and derives phases, predictions, and interaction patterns from them.

The system ingests discrete events (cycle starts, symptoms, interactions), builds an evolving timeline, and computes derived states such as current phase, predicted ovulation window, and expected next cycle.

These states are then used to drive a notification system that adapts both content and delivery targets based on user configuration and partner relationships.

Unlike typical single-user trackers, the system models multi-actor interactions. Each account can have multiple linked recipients (partners), each with independent notification preferences and message customization. This transforms the system from a personal tracker into a multi-recipient communication layer.

A key design constraint was to avoid notification fatigue. The system never sends repeated daily reminders. Instead, notifications are emitted strictly on phase transitions, ensuring that each message represents a meaningful change in state.

What I Built

Core Features

  • Event-driven cycle modeling based on real user input rather than fixed schedules
  • Phase calculation engine using probabilistic cycle length instead of static assumptions
  • Partner-aware notification system with per-recipient preferences and filtering
  • Multi-layer message resolution: default → global → per-partner override
  • Background worker that emits notifications only on phase transitions (no spam)
  • Soft-delete model for events with strict filtering to prevent ghost data in calculations
  • Contract-stable core logic independent from Telegram adapter
  • Test coverage across core logic, repositories, and worker flows

Responsibilities

  • Redesigned MVP architecture into modular SaaS backend
  • Separated domain logic from transport layer (Telegram)
  • Built notification orchestration with state tracking and anti-duplication logic
  • Designed MongoDB schema and indexes for multi-entity relationships
  • Implemented safe data handling and minimized sensitive exposure
  • Refactored legacy flows to eliminate inconsistent data reads
  • Built test suite covering edge cases and real-world data inconsistencies

Technical Details

Conceptually, the notification engine behaves as a state machine. Each account has an implicit state defined by its current cycle phase, and the system reacts only to state transitions. This allows the system to remain idempotent across repeated worker executions and eliminates the need for complex scheduling logic.

Message resolution follows a multi-layer override model. Each notification is resolved by checking for a partner-specific override, then a global override, and finally falling back to a safe default template. This allows high flexibility without breaking baseline behavior.

Data consistency was a critical challenge. Soft-deleted events initially leaked into calculations because different parts of the system used separate data access paths. Some relied on helper-level queries, others on repository abstractions. This led to subtle inconsistencies where deleted events still influenced cycle predictions.

The issue was resolved by enforcing all reads through repository-level queries with strict filtering rules and eliminating duplicate access patterns. This ensured that business logic operates on a single, consistent view of the data.

The system is designed to tolerate imperfect data. Cycle calculations use recent intervals and avoid overfitting to historical outliers. Predictions are expressed as ranges rather than точные даты, reflecting real-world variability.

Project repository structure

Test and coverage output showing 784 passing tests and 91% coverage across core logic, repositories, and workers.

Idempotency is a core requirement of the system. Notifications are generated with deterministic keys, ensuring that repeated executions of the worker do not produce duplicates. This makes the system safe to rerun, retry, or scale horizontally without introducing inconsistent behavior.

The system is extensively tested across all layers: domain logic, repositories, and background workers. Tests cover not only expected flows but also edge cases derived from real-world data inconsistencies, ensuring stable behavior under imperfect input conditions.

Although currently delivered via Telegram, the system is not tied to it. The bot acts as a thin adapter layer. All core logic is transport-agnostic, making it possible to extend the system to web or other messaging platforms without rewriting business logic.

Challenges & Solutions

Development Timeline

The system was not built in a single pass. It evolved through several iterations, each focusing on a specific layer: architecture, data model, notifications, and user interaction flows.

v1.0 — Architecture Foundation

  • Core architecture separated from Telegram handlers
  • Initial domain model (users, events)
  • Repository layer introduced
  • Baseline test coverage

v2.0 — SaaS Model

  • Multi-user and multi-recipient structure
  • Partner links and invite system
  • Recipient preferences and isolation
  • Full data model stabilization

v2.1 — UX Layer

  • Telegram menu redesign
  • Partner management UI
  • Doctor report generation
  • Improved interaction flows

v3.0 — Notification Engine

  • Phase-based notification system
  • Idempotent notification keys
  • Privacy-safe deletion logic
  • Custom user and partner messages

v4.0 — Stability & Coverage

  • Onboarding flow implemented
  • Edge-case handling for real data
  • Expanded test coverage to 90%+
  • Worker reliability improvements

v5.0 — Runtime & Deployment

  • System deployed as a continuously running service
  • Worker and bot operate as separate runtime components
  • Deterministic behavior under real user data
  • Ready for migration to API / multi-channel delivery

Each version focused on a specific system layer, allowing controlled growth without breaking existing behavior.

Result

The system evolved from a single-file Telegram bot into a modular backend capable of handling multi-actor interactions, probabilistic state modeling, and controlled notification flows.

It now supports real-world usage scenarios without breaking under inconsistent data, avoids notification spam, and provides a foundation for future extensions such as web interfaces or additional communication channels.

Try the Product

The system is live and can be tested through the Telegram interface. You can explore the full flow — from event tracking to partner-aware notifications.

No installation required — works directly in Telegram.