Ritma — Cycle Tracking SaaS with Partner-Aware Notifications
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.
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.
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
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.
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
-
Challenge: Inconsistent data due to soft-deleted events appearing in calculations.
Solution: Centralized filtering in repository layer and removed legacy helper-based access.
Result: Deterministic and correct cycle calculations. -
Challenge: Notification spam caused by naive daily execution.
Solution: Introduced phase-transition-based emission with persisted state tracking.
Result: Notifications only fire when meaningful state changes occur. -
Challenge: Conflicting message templates between global and per-partner customization.
Solution: Redesigned database index to include recipient scope and implemented layered resolution logic.
Result: Predictable override hierarchy without data conflicts. -
Challenge: MVP architecture tightly coupled business logic to Telegram handlers.
Solution: Extracted domain logic into core modules and introduced adapter pattern.
Result: System became extensible to other platforms without rewriting logic.
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.