# Privacy: Period Tracker ```text Status: Draft Owner: _null Last reviewed: 2026-08-20 Governs: README.md as the project-facing overview for Privacy: Period Tracker Review trigger: The first buildable feature release; any change to the stack, the privacy promise, or how the project is built and run ``` A private Android period tracker that learns **your** cycle rather than the average person's — and never sells your data. [What it is](#what-it-is) | [Status](#status) | [Build and run](#build-and-run) | [Repository map](#repository-map) | [Project docs](#project-docs) | [Agent notes](#agent-notes) _{ Android / Google Play | Kotlin + Jetpack Compose | Room, offline-first | Personalized prediction with an honest window | Discreet notifications | No account, no server, no data sale }_ The core loop is: ```text Track → Learn → Predict → Remind → Learn again ``` ## What it is A focused tracker that answers one question well: **when is my next period likely to start?** It records confirmed period dates, learns the individual's pattern from them, and produces a most-likely date with a window and a confidence label — never a bare date presented as fact. From that it estimates ovulation and the fertile window, and it asks discreetly whether the period started, using both *yes* and *not yet* to improve the next forecast. Two promises hold the product up: > **Your period. Better predicted.** — once there is enough personal history, > the app must not fall back to a generic 28-day cycle. A user recording 34, 35, > 36, 34, 35 who is predicted 28 is a core product defect, not a tuning issue. > **Your cycle belongs to you.** We will never sell period history, fertility > information, ovulation estimates, cycle predictions or personally identifiable > information — to advertisers, data brokers or anyone else. The second is held structurally rather than remembered, and the rule was written before the code it constrains: `checkModuleBoundaries` in the root `build.gradle.kts` already carries `":core:ads" to emptySet()`, so when the ads module arrives in Batch 07 it may declare **no** project dependency at all — stricter than "nothing from the cycle database or the prediction domain". It is not in `settings.gradle.kts` yet, so today the rule matches no module, which is why the guard is proved against injected violations rather than trusted. What it deliberately is **not** — no community, no pregnancy mode, no chatbot, no article feed, no symptom encyclopedia, and not contraception — is in [docs/planning/PROJECT_PLAN.md](docs/planning/PROJECT_PLAN.md). ## Status Foundation through Batch 06 are closed, Batch 08 polish is complete, and monetization (Batch 07) remains unbuilt. Every row below cites the file or test that proves it. | Surface | Status | Evidence | | --- | --- | --- | | Documentation tree and tracker convention | Adopted | this tree; milestones and issues in the tracker | | Gradle / Kotlin / Compose project | Builds | `./gradlew assembleDebug` and `assembleRelease` both pass | | Material 3 theme and tokens | Built | `core/designsystem` | | Four-tab navigation shell | Built | `app/src/main/kotlin/dev/privacyllc/period/navigation/PeriodApp.kt` — Today, Calendar, Insights, Settings, each wired to its real screen | | Cycle model and interval derivation | Built | `domain/cycle` (13 tests); `domain/prediction/src/main/kotlin/dev/privacyllc/period/domain/prediction/IntervalAnalysis.kt` (12 tests) | | Prediction engine | Built — personalized | `PersonalPredictionEngine`, wired in `app/src/main/kotlin/dev/privacyllc/period/di/DataModule.kt`; the 12 acceptance tests from PRODUCT_PLAN §51 run against both engines (`PersonalAcceptanceTest`, `BaselineAcceptanceTest`), and `EngineComparisonTest` scores the personal engine against the retained baseline every build | | Room persistence, DataStore | Built | `core/database` (schema v1 exported, `SchemaTest` + `scripts/schema-guard.sh`), `core/datastore`, `core/data` | | Onboarding, Today, Calendar, Insights | Built | `app/src/main/kotlin/dev/privacyllc/period/feature/` — onboarding, today, calendar, insights; `OnboardingViewModelTest`, `TodayViewModelTest`, `TodayUiStateTest` | | Fertility window and ovulation estimate | Built | `domain/prediction/src/main/kotlin/dev/privacyllc/period/domain/prediction/FertilityEstimate.kt`, 11 tests; shown on Today and Calendar | | Discreet notifications | Built | `core/notifications` (24 JVM tests), instrumented `NotificationPrivacyTest` | | App lock (PIN + fingerprint) | Built | `core/security` (Keystore-backed verifier, lockout policy), `app/src/main/kotlin/dev/privacyllc/period/lock` gate; 28 JVM tests plus `KeystoreVerifierTest` run on `PeriodMinSdk26` and `PeriodQA` | | Discreet launcher alias | Built | `app/src/main/AndroidManifest.xml` aliases; `LauncherAliasSwitcherTest`, `PrivacyViewModelTest`; device checked on `emulator-5554` | | Export My Data | Built | `core/export` (format pinned byte-for-byte against `core/export/src/test/resources/golden-v1.json`), written through the Storage Access Framework by `app/src/main/kotlin/dev/privacyllc/period/feature/export` | | Monetization | Not built | Batch 07 | | QA | Round 3 run, partial | [docs/qa/ClaudeReport.md](docs/qa/ClaudeReport.md) — partial at `0451fbe`; TalkBack, text scaling, `minSdk` and a real locked screen still unreached | `BaselinePredictionEngine` is still in the tree, but it stopped being the product in Batch 02. `DataModule` provides `PersonalPredictionEngine` — the recency-weighted, trend-aware, "not yet"-conditioned engine PRODUCT_PLAN §12 specifies — and the baseline is now the control: `EngineComparisonTest` scores both over the §51 fixtures every build, so "the new engine is better" is a number rather than an opinion. ## Build and run Prerequisites: a JDK 21 and the Android SDK with **platform 37** and **build-tools 37.0.0** installed, `ANDROID_HOME` set. ```bash git config core.hooksPath .githooks # per clone, every clone — see below ./gradlew test # JVM suites, no emulator, ~1s ./gradlew assembleDebug # app/build/outputs/apk/debug/ ./gradlew assembleRelease # minified, unsigned ``` `compileSdk` is 37 because the current AndroidX libraries require it; `targetSdk` is 36, Google Play's floor for new apps from 2026-08-31. They are deliberately different — raising `targetSdk` opts the app into runtime behaviour changes and is a tested decision, not a build fix. **`git config core.hooksPath .githooks` is per clone.** Without it the hooks are not installed and fail silently, which is the failure mode they exist to prevent. `post-commit` pushes; see [docs/architecture/githooks/README.md](docs/architecture/githooks/README.md). ## Repository map ```text app/ application module, MainActivity, navigation shell, DI core/designsystem/ Material 3 theme and colour tokens core/database/ Room entities, DAOs, converters, the exported schema core/datastore/ user preferences that are not health history core/data/ CycleRepository — the only module that touches a DAO core/notifications/ reminder copy, privacy modes, WorkManager scheduling domain/cycle/ pure Kotlin — period records, cycle derivation domain/prediction/ pure Kotlin — the forecast, window and confidence scripts/ six from the template, plus schema-guard.sh, our own .githooks/ pre-commit, commit-msg, post-commit docs/ product, architecture, design, brand, QA, security, history ``` `domain/*` are `kotlin("jvm")` modules and cannot see the Android SDK. That is on purpose: the prediction engine is the product, so it needs the most tests, and tests that need an emulator are tests that do not get run. ## Project docs Detailed procedures belong in docs; **open work belongs in the tracker**, not in this README. | Doc | Purpose | | --- | --- | | [docs/DOC_TRUST_MAP.md](docs/DOC_TRUST_MAP.md) | Which document owns which answer, and which source wins when records disagree. **Read this first.** | | [docs/planning/PRODUCT_PLAN.md](docs/planning/PRODUCT_PLAN.md) | The full V1 specification — prediction requirements, screens, copy, compliance | | [docs/planning/PROJECT_PLAN.md](docs/planning/PROJECT_PLAN.md) | The short vision, the stack and its reasons, and what this is deliberately not | | [docs/WORK_CYCLE.md](docs/WORK_CYCLE.md) | What happens at the end of a piece of work | | [docs/architecture/README.md](docs/architecture/README.md) | Modules, boundaries, data shapes, migrations | | [docs/design/README.md](docs/design/README.md) | Tone, and the four rules that settle design arguments | | [docs/security/SECURITY.md](docs/security/SECURITY.md) | Threat model, the advertising boundary, logging rules | | [docs/TOOLS.md](docs/TOOLS.md) | Which script to run, and which ones can stop you | ## Where the tracker is Milestones are batches, issues are deliverables, and severity labels are exactly `P0`, `P1`, `P2` and `release-blocker`. Credentials come from the environment, never from this repository: ```bash set -a; . ~/.openclaw/docker-registry.env; set +a python3 scripts/forgejo-issue.py list ``` Two traps that cost an hour each otherwise: Cloudflare 1010-blocks clients that do not look like a browser or curl, so every request needs `User-Agent: curl/8.5.0` — `forgejo-issue.py` sends it and anything new must too. And `/issues` returns pull requests unless `type=issues` is passed. ## Agent notes - **Product truth comes from the code and the tracker before prose.** - Do not keep a work list in this README or anywhere else in `docs/`. - Finish with [docs/WORK_CYCLE.md](docs/WORK_CYCLE.md), every time: close what you finished with the evidence, close the milestone if the batch landed, update the documents the change triggered **in the same commit**, push, log the entry, then reconcile and write the summary and next action. - Nothing on privacyllc.dev writes itself except the tracker counts and the pushed docs. - **Do not claim a feature is built** unless you can cite the file, test or screenshot that proves it. The status table above is the standard. - **Verify current stable versions before changing the toolchain.** The ones in `gradle/libs.versions.toml` were checked against the official sources on 2026-08-18; PRODUCT_PLAN.md asks for that check rather than for its own numbers to be trusted. - Never put a cycle date, a prediction or a fertility state into a log, an analytics event, or an ad request.