← All privacy impact assessments
PIA: Counting how features are used
Status: Assessed before code, 27 October 2026, and approved with conditions the same day. The counting is built but switched off. It stays off until the privacy policy version that describes it has reached members' phones, and it never counts a day before it was switched on. Written: 27 October 2026 · Last revised: 28 October 2026 Nothing here is legal advice. Children's Code citations are to the exposure draft; section numbers may move on registration (due 10 December 2026).
1. Purpose and scope
We want to know whether each part of the app is used, so we can decide what to improve: are people sending messages, adding tasks, logging feeds, adding photos, asking the assistant. Almost all of that is already in our database as records with a time, an author and a group. This assessment covers one weekly job, inside our database, that adds those records up into totals that name nobody, and keeps the totals for 24 months.
In scope: what is counted, from which records, who is left out, how small totals are hidden, who can read the totals, and how long they are kept.
Out of scope, and not built: anything on the phone. The app records no screen views, taps, time spent or how often it is opened for this, and no analytics software is added to it. A dashboard outside the database console would be a new reader and needs its own assessment first. Following one group or member over time (cohort retention) is not part of this.
2. Data involved
The job reads records that already exist, for their existence, time, author, group and a few fixed-list columns. It writes only totals.
| Kind | Whose | Children's? | Sensitive? | Free text? |
|---|---|---|---|---|
Messages (group_messages): sender, group, time; system messages left out |
Members' | No: under-18s' rows are skipped | No | None read; message text is sealed and never opened |
Tasks (group_tasks: how it was added, e.g. by hand or from a card) and completions (task_completions, top-level only) |
Members' | An adult's task for a child is counted as adult activity, with nothing saying it was for a child | No | None read: no title, note or assignee |
| Calendar events and event invitations: existence, author, group, time | Members' | As above | No | None read: no title, place or note |
Baby tracker entries (baby_logs.log_type), feed sessions, book reads: existence and time |
The adult who logged it; about an infant | Yes, about an infant | Yes: an entry type can be health information. The six health types (medicine, temperature, immunisation, vomit, nasal aspiration, growth) are merged into one health total |
None read: no amount, dose, measurement, note or baby |
| Photos and albums added: existence and time | Members' | No | No | None read: no caption, picture or comment |
Assistant turns (chat_messages): role and time only |
Members' | Children have no assistant | No | None read; conversation text is sealed |
Assistant cards (action_stacks): type and status |
Members' | No | No | The card's contents are never read |
Notifications sent (push_deliveries): delivery status |
The recipient's | A child's notifications are skipped | No | No token or error text |
| Voice turns, inbox items a member shared from the app (status), ceremonies, game events (kind) | Members' | Skipped for under-18s. Items forwarded by email aren't counted at all, because we can't tell from the record whether the sender is an adult | No | No transcript, email, address or thread text |
| Optional switches: the latest consent act per purpose, and tools switched on | Members' | Skipped for under-18s; child-account consents are never counted | No | None |
| Group details for breakdowns: privacy level (Device-only or not), standing or event group, number of adult members in bands of 1, 2–3, 4–6, 7+ | Groups' | Children never count toward a band | No | The group's name and its free-text "type" are never read |
What is stored is a table of totals, platform_usage_daily: a day or week, a metric name, a
breakdown name and value from a fixed list, a number, and whether the number was hidden. There is
no column for a person, a group, an account, a time of day or any text a person wrote. The database
refuses any name or value outside the fixed list.
Strictly necessary (s 38(2)(b)): no children's personal information is collected. Nothing done from the account of someone under 18 is counted, and adults' entries about an infant are counted only as how many of each kind were made in a week, across groups.
Not collected, and could have been: what anybody wrote; what screens anyone opens, how often or for how long; who was active (distinct counts are worked out inside one database statement and discarded); anything per member or per group; ages, assignees, babies, or whether a group has children; spend; our server logs.
3. Flows and locations
| Data | Device | Supabase (Sydney, ap-southeast-2) | GCP (australia-southeast1) | Model provider | Region |
|---|---|---|---|---|---|
| Source records | Already synced as today | Read in place by the job | Not involved | Never | Sydney |
| Totals | Never sent to phones | Stored in platform_usage_daily |
Not involved | Never | Sydney |
- The job runs inside the database, scheduled by the database's own scheduler. Nothing leaves Sydney and nothing reaches a model.
- No list of who was active is written anywhere: not to a table, a temporary table or a log. The job returns only how many rows it wrote, and its two possible errors are fixed codes that carry no number, name or identifier, because the scheduler keeps whatever a job says.
- Retention: totals are kept for 24 months and then deleted by a scheduled job that runs with the counting itself; database backups add up to 90 days, as the privacy policy already says. Deleting an account does not remove totals, because a total holds nothing that points at the account.
- Device-only groups are counted. Device-only is a promise about content and models: content is sealed and nothing reaches a model. Counting records reads no sealed column and sends nothing anywhere. The Device-only total itself will almost always be hidden (fewer than 3 groups), and with it the matching total for every other level, so neither can be worked out from the other.
4. Legal basis and consents
No consent purpose is used. Learning whether features are used, to decide what to improve, is a secondary purpose closely related to running the service; for baby-tracker entries, which can be health information, counting how often each kind of entry is made, to improve the tracker, is directly related to the tracker's own purpose. Members are told through the privacy policy, in the version that describes the counting, before the first count is made, and the privacy-level screen says at every level, Device-only included, that we count how often features are used and never what anyone wrote.
There is no per-member opt-out. Each total names nobody, and an opt-out would need a per-member flag that the job reads, which is more personal information than the totals themselves.
Children: nothing a child does is counted, so the Children's Privacy Policy needs no change.
5. Risks and mitigations
| Risk | Who is harmed | Mitigation | Structural or conventional? | Residual |
|---|---|---|---|---|
| With about 100 testers, a total describes one household we know: 340 messages in the "7+ members" band, or 12 medicine entries, all from one family | That family | A total is kept only if it is at least 10, comes from at least 3 groups, and no group gave more than half; otherwise it is stored as hidden, with no number | Structural: inside the only writer, and the table refuses a number below 10 | Low |
| A hidden total is worked out by subtracting the others from a headline figure | The group behind the hidden total | Within a breakdown, hidden totals come in twos or more: if only one would be hidden, the next-smallest is hidden too. Each breakdown exists at one grain only (daily headlines, weekly breakdowns). For a figure counted both daily and weekly (tasks added, assistant turns, notifications sent, members active, groups active), a week's seven daily totals and its weekly breakdowns are either all kept or all hidden: one hidden day or one hidden weekly total hides the whole week of that figure, so nothing hidden can be found by subtracting what is kept. Figures counted only daily (messages, people and groups messaging, tasks done) have no weekly breakdown and are not affected. Daily totals are written only with their week, once a week, after this hiding, so none is ever visible before its week is settled | Structural: inside the writer, and the table refuses a breakdown at the wrong grain | Low |
| People in no group are counted as if they made up groups, so a total "from 3 groups" comes from none | People in no group | A person in no group is put in one "unknown" bucket that never counts toward the 3 groups, but does count when we check that no one contributor gave more than half | Structural: inside the writer | Low |
| A 16-year-old's activity, or a child's with no birth month, is counted or revealed | The child | The job counts a row only when its author, and any account it is about, is an adult person: not under 18 by birth month, not linked to a guardian as the child with no month, not a known child with no month, and not the shared display or a profile account. No breakdown reveals that a group has a child | Structural: inside the writer, tested for each case | Low |
| The job reads or echoes something a person wrote | The writer | The job reads only the columns listed in §2; a test reads its definition and fails on any content column | Conventional, test-pinned | Low |
| An app, a server function or a stolen service key reads the totals | Everyone | No role the app or our servers use holds any access to the table, not even the service role | Structural: no grant | Low |
| Counting starts before members are told | Every member | It ships switched off; it writes nothing until a setting is given a start date, and never for a day before that date | Structural: the off switch is checked first | Low |
| Figures leave our console and are combined with what we know about testers | Members we know | Only stored totals, as stored, leave the console; they are never shown in the app or to any member, sold or used for advertising | Conventional | Medium (accepted, §8) |
| A new metric or breakdown is added later without review | Members | Adding a metric, breakdown, grain or source, or lowering a threshold, is a revision of this assessment and a security check before code; the database refuses names outside the fixed list | Structural for the list; conventional for the review | Low |
6. Structural versus conventional
Structural:
- No access for any app or server role, the service role included; the table and its switch can be read only by the database's own owner role.
- The table's checks: no column for a person, group, time of day or text; a number is never below 10; a hidden total has no number; every name and value is from the fixed list, at the grain the list allows.
- Hiding small totals, and pairing hidden totals, inside the only writer.
- Leaving out under-18s, guardian-linked children with no month, known children and the shared display, inside the only writer.
- The 24-month deletion, scheduled together with the job.
- The off switch, checked before anything else.
Conventional, some of it pinned by tests:
- No content column read by the job. A test reads the job's definition and fails on any content column name or on the first-use record; that is a fence, not proof, because the job runs with the access to read any table.
- The saved questions we use read the totals table alone and join nothing.
- What leaves our console is stored totals, as stored, never re-derived or combined with what we know about particular testers.
- Review before a new metric.
7. Best interests of the child
Nothing a child does is counted, and a child never counts toward a figure of active people or a group-size band. The one place children appear at all is indirectly, in adults' entries about an infant in the baby tracker, which are counted as how many of each kind were made in a week across groups, with the health types merged. That gives a child nothing and costs them nothing: no record of theirs is created or kept, and the totals are too coarse to say anything about any infant. The safer design, leaving the baby tracker out entirely, was not chosen because it would leave us unable to tell whether the tracker parents rely on is used, and the weekly, merged, suppressed total carries no information about a particular child.
8. Residual risk we accept
- The computation reads members' records. The job runs inside the database with the access to read any table. What it reads is limited by its definition, which a test checks, not by the database.
- We know many of the testers. A published total plus personal knowledge can still suggest who is active ("only four groups, and I know three"). The thresholds and our reading rules limit this; they do not remove it.
- Little survives at beta scale. With about 100 members, most breakdowns and most baby-tracker totals will be hidden most weeks. For a figure counted both daily and weekly, that also hides its daily totals for the week, since the week is kept or hidden as a whole; figures counted only daily are not affected. We accept that rather than loosening the thresholds.
- Future metrics or readers. Each new figure or new place the figures are shown changes this analysis, which is why each one comes back to this assessment first.
9. Review trigger
- A metric, breakdown, grain or source is added, or a threshold lowered.
- The totals are shown anywhere other than our database console, including a dashboard.
- A stored total is found to hold something it shouldn't: a value derived from content, an identifier, under-18 activity or a small number. Then the counting is switched off that day, the totals are deleted, a note is added here, and the fix lands before it is switched back on.
- The counting is switched on (a note here with the date).
10. Checklist against exposure draft s 38(2)
| s 38(2) | Requires | Answered in |
|---|---|---|
| (a) | nature, scope, context, flow and purposes | §§1–3 |
| (b) | why collecting children's personal information is strictly necessary | §2: none is collected |
| (c) | lawful and fair means | §4 |
| (d) | best interests of the child | §7 |
| (e) | how we comply with the Code and the Act | §4 and §6 |
| (f) | risk of harm to children | §5 |
11. Sources
- The usage-counts migration in
supabase/migrations/: the table, the switch, the fixed list, the job and its schedules, with every metric defined in its header. __tests__/platform_usage_counts.test.ts: who is left out, the thresholds at each boundary, the pairing, the off switch, the fixed list, the grains, the access refusals, the content-column scan and the 24-month deletion.docs/runbooks/usage-analytics.md: the metric list, the fixed list and our saved questions.- Office of the Australian Information Commissioner, Children's Online Privacy Code exposure draft (2026), ss 10, 38, 39; Privacy Act 1988 (Cth), Australian Privacy Principles 5 and 6.