Skip to content

Generate GoFlo Test Data #1

Generate GoFlo Test Data

Generate GoFlo Test Data #1

name: Generate GoFlo Test Data
on:
workflow_dispatch:
inputs:
reference_date:
description: "End date for generated data (YYYY-MM-DD). Leave blank to use today."
required: false
default: ""
jobs:
generate:
runs-on: ubuntu-latest
steps:
- name: Generate test data JSON
run: |
python3 << 'PYEOF'
import json
import calendar
from datetime import date, timedelta
# ── Date window ──────────────────────────────────────────────────────
ref_str = "${{ inputs.reference_date }}".strip()
today = date.fromisoformat(ref_str) if ref_str else date.today()
def subtract_months(d, n):
m = d.month - n
y = d.year
while m <= 0:
m += 12
y -= 1
return date(y, m, min(d.day, calendar.monthrange(y, m)[1]))
window_start = subtract_months(today, 3)
# ── Categories ───────────────────────────────────────────────────────
# 2 of each type:
# default → Symptoms (system), Mood (custom)
# numeric_slider → Flow (system), Brain Fog (custom)
# increment → Pads Used (custom), Medication (custom)
# numeric_free → Outbursts (custom), Energy (custom)
categories = [
{
"id": 1, "name": "Flow", "isSystem": True, "systemKey": "flow",
"displayOrder": 0, "iconName": "water", "colorToken": "primary",
"categoryType": "numeric_slider",
"numericMin": 1, "numericMax": 4, "allowDecimals": False,
"numericUnit": "",
"scaleLabels": "1=Spotting\n2=Light\n3=Medium\n4=Heavy",
"isArchived": False, "allowMultiple": False,
"showInLogPeriod": False, "trackAgainstTime": True,
"values": ["Spotting", "Light", "Medium", "Heavy"],
},
{
"id": 2, "name": "Symptoms", "isSystem": True, "systemKey": "symptoms",
"displayOrder": 1, "iconName": "meditation", "colorToken": "secondary",
"categoryType": "default",
"numericMin": 0, "numericMax": 10, "allowDecimals": False,
"numericUnit": "", "scaleLabels": "",
"isArchived": False, "allowMultiple": False,
"showInLogPeriod": False, "trackAgainstTime": False,
"values": [
"Cramps", "Headache", "Bloating",
"Fatigue", "Back Pain", "Bleeding (non-period)",
],
},
{
"id": 3, "name": "Mood", "isSystem": False, "systemKey": "",
"displayOrder": 2, "iconName": "psychology", "colorToken": "tertiary",
"categoryType": "default",
"numericMin": 0, "numericMax": 10, "allowDecimals": False,
"numericUnit": "", "scaleLabels": "",
"isArchived": False, "allowMultiple": True,
"showInLogPeriod": True, "trackAgainstTime": True,
"values": ["Happy", "Sad", "Anxious", "Calm", "Irritable", "Content", "Grumpy"],
},
{
"id": 6, "name": "Pads Used", "isSystem": False, "systemKey": "",
"displayOrder": 3, "iconName": "healing", "colorToken": "FFF7A7DD",
"categoryType": "increment",
"numericMin": 0, "numericMax": 10, "allowDecimals": False,
"numericUnit": "", "scaleLabels": "",
"isArchived": False, "allowMultiple": False,
"showInLogPeriod": True, "trackAgainstTime": True,
"values": [],
},
{
"id": 7, "name": "Outbursts", "isSystem": False, "systemKey": "",
"displayOrder": 4, "iconName": "bolt", "colorToken": "FFE53935",
"categoryType": "numeric_free",
"numericMin": 0, "numericMax": 20, "allowDecimals": False,
"numericUnit": "explosions", "scaleLabels": "",
"isArchived": False, "allowMultiple": True,
"showInLogPeriod": True, "trackAgainstTime": True,
"values": [],
},
{
"id": 8, "name": "Pads bled through", "isSystem": False, "systemKey": "",
"displayOrder": 5, "iconName": "medication", "colorToken": "secondary",
"categoryType": "increment",
"numericMin": 0, "numericMax": 10, "allowDecimals": False,
"numericUnit": "", "scaleLabels": "",
"isArchived": False, "allowMultiple": False,
"showInLogPeriod": True, "trackAgainstTime": False,
"values": [],
},
{
"id": 10, "name": "Brain Fog", "isSystem": False, "systemKey": "",
"displayOrder": 6, "iconName": "cloud", "colorToken": "FF8A8A8A",
"categoryType": "numeric_slider",
"numericMin": 1, "numericMax": 3, "allowDecimals": False,
"numericUnit": "",
"scaleLabels": "1=Good\n2=Confused\n3=What's my name again?",
"isArchived": False, "allowMultiple": True,
"showInLogPeriod": True, "trackAgainstTime": True,
"values": [],
},
{
"id": 11, "name": "Energy", "isSystem": False, "systemKey": "",
"displayOrder": 7, "iconName": "fitness", "colorToken": "FFFF9800",
"categoryType": "numeric_free",
"numericMin": 0, "numericMax": 100, "allowDecimals": False,
"numericUnit": "%", "scaleLabels": "",
"isArchived": False, "allowMultiple": False,
"showInLogPeriod": True, "trackAgainstTime": True,
"values": [],
},
]
# ── Periods ──────────────────────────────────────────────────────────
# Generate realistic ~28–32 day cycles across the 3-month window.
# Each row: (cycle_length, period_duration, overall_flow, symptoms_list)
cycle_specs = [
(29, 6, "Heavy", ["Cramps", "Headache"]),
(28, 5, "Medium", ["Cramps", "Bloating", "Fatigue"]),
(31, 7, "Heavy", ["Back Pain", "Headache", "Bloating"]),
(27, 4, "Light", ["Fatigue"]),
]
# Flow by day-of-period (1-indexed); default Spotting after day 7
flow_by_day = {1: "4", 2: "4", 3: "4", 4: "3", 5: "2", 6: "1", 7: "1"}
# Pads used per flow level (string, since it's increment/numeric)
pads_by_flow = {"4": 4, "3": 3, "2": 2, "1": 1}
bled_by_flow = {"4": 2, "3": 1, "2": 0, "1": 0}
periods = []
period_days = set() # all dates covered by any period
tracking_logs = {cat["id"]: [] for cat in categories}
period_start = window_start
spec_idx = 0
pid = 1
while period_start <= today:
cycle_len, duration, flow_label, symptoms = cycle_specs[spec_idx % len(cycle_specs)]
period_end_d = period_start + timedelta(days=duration - 1)
ongoing = period_end_d > today
periods.append({
"id": pid,
"startDate": period_start.isoformat(),
"endDate": None if ongoing else period_end_d.isoformat(),
"flowLevel": flow_label,
"notes": "",
"symptoms": symptoms,
})
log_end = today if ongoing else period_end_d
d = period_start
day_num = 1
while d <= log_end:
period_days.add(d)
flow_val = flow_by_day.get(day_num, "1")
# Flow (numeric_slider: log as numeric string "1"–"4")
tracking_logs[1].append({"date": d.isoformat(), "values": [flow_val], "notes": ""})
# Pads Used (increment: count as string)
base = pads_by_flow.get(flow_val, 1)
pads = max(0, base + (day_num % 3) - 1)
tracking_logs[6].append({"date": d.isoformat(), "values": [str(pads)], "notes": ""})
# Pads bled through (increment): mostly on heavy days
bled = bled_by_flow.get(flow_val, 0)
if day_num % 2 == 0:
bled = max(0, bled - 1)
tracking_logs[8].append({"date": d.isoformat(), "values": [str(bled)], "notes": ""})
d += timedelta(days=1)
day_num += 1
pid += 1
spec_idx += 1
period_start += timedelta(days=cycle_len)
# ── Symptoms (default, allowMultiple=False in system category) ───────
# Log on first 2 period days and 3 pre-menstrual days.
for p in periods:
ps = date.fromisoformat(p["startDate"])
pe = date.fromisoformat(p["endDate"]) if p["endDate"] else today
# Pre-menstrual (3 days before start)
for offset in (3, 2, 1):
pms = ps - timedelta(days=offset)
if pms >= window_start:
tracking_logs[2].append({
"date": pms.isoformat(),
"values": ["Bloating", "Cramps"],
"notes": "",
})
# During period: symptoms on days 1–3
syms = p["symptoms"]
d = ps
for day_num in range(1, 4):
if d > pe:
break
tracking_logs[2].append({
"date": d.isoformat(),
# Day 1: all symptoms; Day 2: first two; Day 3: first only
"values": syms[:max(1, len(syms) - day_num + 1)],
"notes": "",
})
d += timedelta(days=1)
# ── Mood (default, allowMultiple=True) ───────────────────────────────
single_moods = ["Happy", "Calm", "Content", "Sad", "Anxious", "Irritable", "Grumpy"]
multi_moods = [
["Happy", "Calm"],
["Sad", "Anxious"],
["Irritable", "Grumpy"],
["Content", "Happy"],
]
d = window_start
idx = 0
while d <= today:
if idx % 2 == 0: # log every other day
if d in period_days:
moods = multi_moods[2] if idx % 4 < 2 else ["Grumpy"]
elif idx % 7 < 2:
moods = multi_moods[0]
elif idx % 7 < 4:
moods = [single_moods[idx % len(single_moods)]]
else:
moods = multi_moods[idx % len(multi_moods)]
tracking_logs[3].append({"date": d.isoformat(), "values": moods, "notes": ""})
d += timedelta(days=1)
idx += 1
# ── Outbursts (numeric_free, allowMultiple=True) ─────────────────────
# Higher counts during period; low counts outside.
d = window_start
idx = 0
while d <= today:
if d in period_days and idx % 2 == 0:
val = 3 + (idx % 5) # 3–7 on period days
tracking_logs[7].append({"date": d.isoformat(), "values": [str(val)], "notes": ""})
elif idx % 5 == 0 and d not in period_days:
val = idx % 3 # 0–2 outside period
tracking_logs[7].append({"date": d.isoformat(), "values": [str(val)], "notes": ""})
d += timedelta(days=1)
idx += 1
# ── Brain Fog (numeric_slider 1–3, allowMultiple=True) ───────────────
# Every 3 days; max fog during period.
d = window_start
idx = 0
while d <= today:
if idx % 3 == 0:
if d in period_days:
fog = "3"
elif idx % 9 < 3:
fog = "1"
elif idx % 9 < 6:
fog = "2"
else:
fog = "3"
tracking_logs[10].append({"date": d.isoformat(), "values": [fog], "notes": ""})
d += timedelta(days=1)
idx += 1
# ── Energy (numeric_free, allowMultiple=False) ────────────────────────
# Daily; dips during period, peaks mid-cycle.
d = window_start
idx = 0
while d <= today:
if d in period_days:
energy = 20 + (idx % 20) # 20–39 %
elif idx % 14 in (6, 7): # mid-cycle high
energy = 85 + (idx % 15) # 85–99 %
else:
energy = 50 + (idx % 35) # 50–84 %
energy = min(energy, 100)
tracking_logs[11].append({"date": d.isoformat(), "values": [str(energy)], "notes": ""})
d += timedelta(days=1)
idx += 1
# ── Assemble tracking array ───────────────────────────────────────────
cat_by_id = {c["id"]: c for c in categories}
tracking = [
{
"id": cid,
"name": cat_by_id[cid]["name"],
"archived": False,
"type": cat_by_id[cid]["categoryType"],
"logs": logs,
}
for cid, logs in tracking_logs.items()
if logs
]
# ── Pinned stats that exercise chart types ────────────────────────────
# (stored as a JSON string, matching the export format)
pinned = [
{
"id": "1__TIME_SERIES_MONTH",
"label": "Flow",
"categoryId1": 1,
"timeRangeType": "MONTH",
"chartType": "TIME_SERIES",
},
{
"id": "2__PIE_ALL_TIME",
"label": "Symptoms",
"categoryId1": 2,
"timeRangeType": "ALL_TIME",
"chartType": "PIE",
},
{
"id": "6_8_DUAL_TIME_SERIES_MONTH",
"label": "Pads Used vs Pads bled through",
"categoryId1": 6,
"categoryId2": 8,
"timeRangeType": "MONTH",
"chartType": "DUAL_TIME_SERIES",
},
{
"id": "11__TIME_SERIES_MONTH",
"label": "Energy",
"categoryId1": 11,
"timeRangeType": "MONTH",
"chartType": "TIME_SERIES",
},
]
export = {
"version": 3,
"exportedAt": today.isoformat(),
"fullBackup": True,
"dateRange": {
"from": window_start.isoformat(),
"to": today.isoformat(),
},
"categories": categories,
"pinnedStats": json.dumps(pinned),
"periods": periods,
"tracking": tracking,
}
filename = f"goflo_test_data_{today.isoformat()}.json"
with open(filename, "w") as fh:
json.dump(export, fh, indent=2)
# Summary
print(f"Window : {window_start} → {today}")
print(f"Periods: {len(periods)}")
for cat in categories:
logs = tracking_logs[cat["id"]]
print(f" [{cat['categoryType']:16s}] {cat['name']:20s} {len(logs):3d} logs")
print(f"Output : {filename}")
PYEOF
- name: Upload test data artifact
uses: actions/upload-artifact@v4
with:
name: goflo-test-data-${{ inputs.reference_date || 'today' }}
path: goflo_test_data_*.json
retention-days: 30