feat(cycle): choose cervix as the symptothermal secondary symptom

Let a user pick the Sensiplan double-check's second indicator — cervical
mucus (default) or cervix observation — mirroring the published NFP
standard where the temperature primary sign pairs with one chosen
secondary sign.

- `CycleProfile.secondarySymptom` (MUCUS default | CERVIX) drives which
  second sign the engine confirms against. Casual users keep temperature
  + mucus untouched; the choice lives behind the advanced cycle settings.
- `CycleDayLog` gains the three standard cervix signs — position
  (LOW/HIGH), firmness (FIRM/SOFT), opening/os (CLOSED/OPEN) — as nullable
  columns (migration 0157), wired through Zod, the day-log upsert, the
  DTO, the sync feed, backup/restore and OpenAPI. Additive and
  back-compatible: existing profiles backfill MUCUS, existing day-logs
  keep NULL cervix signs, iOS inherits the fields.
- The engine's `confirmSymptothermal` takes the secondary-symptom choice;
  on CERVIX it confirms against `detectCervixPeak` (last fertile-cervix
  day followed by three closed days — the cervix-closure rule) instead of
  the mucus peak. The double-check still requires BOTH the temperature
  shift AND the secondary peak: a single cervix sign never confirms, and
  the still-learning gate and conservative posture are unchanged.

Engine derived clean-room from the Arbeitsgruppe-NFP / Raith-Paula &
Frank-Herrmann standard.
This commit is contained in:
Marc-André Bombeck committed 2026-06-14 15:23:26 +02:00
1 parent 2c225ce094
commit 609d248252
18 files changed
+561 -6

No files matched your search

+110
View File
@@ -134,6 +134,21 @@ paths:
- CREAMY
- WATERY
- EGG_WHITE
cervixPosition:
type: string
enum:
- LOW
- HIGH
cervixFirmness:
type: string
enum:
- FIRM
- SOFT
cervixOpening:
type: string
enum:
- CLOSED
- OPEN
sexualActivity:
type: boolean
protectedSex:
@@ -275,6 +290,27 @@ paths:
- WATERY
- EGG_WHITE
- type: "null"
cervixPosition:
anyOf:
- type: string
enum:
- LOW
- HIGH
- type: "null"
cervixFirmness:
anyOf:
- type: string
enum:
- FIRM
- SOFT
- type: "null"
cervixOpening:
anyOf:
- type: string
enum:
- CLOSED
- OPEN
- type: "null"
sexualActivity:
type: boolean
protectedSex:
@@ -433,6 +469,21 @@ paths:
- CREAMY
- WATERY
- EGG_WHITE
cervixPosition:
type: string
enum:
- LOW
- HIGH
cervixFirmness:
type: string
enum:
- FIRM
- SOFT
cervixOpening:
type: string
enum:
- CLOSED
- OPEN
sexualActivity:
type: boolean
protectedSex:
@@ -730,6 +781,11 @@ paths:
- TRYING_TO_CONCEIVE
- PERIMENOPAUSE
- OFF
secondarySymptom:
type: string
enum:
- MUCUS
- CERVIX
rawChartMode:
type: boolean
predictionEnabled:
@@ -6267,6 +6323,27 @@ components:
- WATERY
- EGG_WHITE
- type: "null"
cervixPosition:
anyOf:
- type: string
enum:
- LOW
- HIGH
- type: "null"
cervixFirmness:
anyOf:
- type: string
enum:
- FIRM
- SOFT
- type: "null"
cervixOpening:
anyOf:
- type: string
enum:
- CLOSED
- OPEN
- type: "null"
sexualActivity:
type: boolean
protectedSex:
@@ -6344,6 +6421,9 @@ components:
- temperatureExcluded
- ovulationTest
- cervicalMucus
- cervixPosition
- cervixFirmness
- cervixOpening
- sexualActivity
- protectedSex
- pregnancyTest
@@ -6677,6 +6757,27 @@ components:
- WATERY
- EGG_WHITE
- type: "null"
cervixPosition:
anyOf:
- type: string
enum:
- LOW
- HIGH
- type: "null"
cervixFirmness:
anyOf:
- type: string
enum:
- FIRM
- SOFT
- type: "null"
cervixOpening:
anyOf:
- type: string
enum:
- CLOSED
- OPEN
- type: "null"
required:
- date
- phase
@@ -6691,6 +6792,9 @@ components:
- temperatureExcluded
- ovulationTest
- cervicalMucus
- cervixPosition
- cervixFirmness
- cervixOpening
additionalProperties: false
meta:
type: object
@@ -7156,6 +7260,11 @@ components:
- OFF
cycleTrackingEnabled:
type: boolean
secondarySymptom:
type: string
enum:
- MUCUS
- CERVIX
rawChartMode:
type: boolean
predictionEnabled:
@@ -7189,6 +7298,7 @@ components:
required:
- goal
- cycleTrackingEnabled
- secondarySymptom
- rawChartMode
- predictionEnabled
- discreetNotifications