# App measurement map: Acme Fit (fictional example)

Acme Fit is an invented subscription fitness app used to show the template filled in. Every name, ID, domain and number is fictional. Owner: growth lead. Last reviewed: 2026-10-17.

## Apps and platforms

- iOS app: `com.example.acmefit`, minimum supported version 17.0. OS mix checked 2026-10-15: 96% on 17.4 or later, 90% on 18 or later, 84% on 18.4 or later, 58% on 26.2 or later, 0% on 27.2 or later.
- Android app: `com.example.acmefit`, minimum supported version Android 9, Google Play only.
- Platform split of new users: 70% iOS, 30% Android.
- Release cadence: fortnightly.

## Decisions and governing reports

```yaml
- decision: Raise or cut Apple Ads keyword bids each morning.
  owner: performance marketer
  cadence: daily
  governing_report: Apple Ads campaign report joined to AdServices records on the backend
  metric: cost_per_trial_start_adservices
  date_basis: first launch date of the AdServices record; spend on Apple Ads report date; both in UTC
  maturity_rule: same-day for installs; trial starts final after 24 hours
  diagnostics_only: [MMP Apple Ads aggregated iOS report]
  reconciliation_tolerance: "within 20% of MMP Apple Ads installs: median weekly gap 8% plus twice its standard deviation of 6%, over 10 weeks"

- decision: Move weekly budget between Apple Ads, the social network and Google App campaigns on iOS.
  owner: growth lead
  cadence: weekly
  governing_report: MMP SKAdNetwork and AdAttributionKit postback report, first postbacks only, installs counted with the version-aware first-time download rule
  metric: cost_per_trial_start_ios_first_postback
  metric_definition: spend divided by winning first postbacks whose fine value is 40 or more or whose coarse value is high (the trial mapping); postbacks with no value are not trials, and their share is reported beside the metric per network
  date_basis: MMP modelled install date (the MMP's documented offset subtracted from postback-received date); spend joined on the same modelled date
  maturity_rule: modelled install-date cohorts younger than 5 days excluded (about 4 days for the latest first postback plus a 1-day team buffer for MMP processing)
  diagnostics_only: [social network modelled report, Google Ads app conversions, re-engagement postbacks]
  reconciliation_tolerance: explained, not forced
  notes: Apple Ads on iOS 26.1 and earlier signs with SKAdNetwork 1 to 3, so those installs have no second or third window, are click-through only, and carry the placement rather than the campaign. On 26.2 and later the MMP's bulletin says Apple Ads postbacks come through AdAttributionKit; later-window and view-through availability stays to be confirmed from received AdAttributionKit copies. Apple Ads is compared with the other networks on first postbacks, clicks and placement level only.

- decision: Move weekly budget between the social network and Google App campaigns on Android.
  owner: growth lead
  cadence: weekly
  governing_report: MMP attribution (Install Referrer and network claims)
  metric: cost_per_trial_start_android_d3
  date_basis: MMP install date (first open), UTC
  maturity_rule: cohorts younger than 3 days excluded
  diagnostics_only: [network reports]
  reconciliation_tolerance: "within 15%: median weekly gap 5% plus twice its standard deviation of 5%, over 10 weeks"

- decision: Decide quarterly whether paid social adds subscribers at all.
  owner: head of growth
  cadence: quarterly
  governing_report: geo holdout test result
  metric: incremental_paid_subscribers
  date_basis: test calendar dates
  maturity_rule: test runs 6 weeks plus 2 weeks for renewals
  diagnostics_only: [all attribution reports]
  reconciliation_tolerance: not applicable

- decision: Fix onboarding steps that lose new users.
  owner: product manager
  cadence: fortnightly
  governing_report: product analytics funnel by acquisition cohort, consented users
  metric: first_workout_completed_rate_d1
  date_basis: first open date, UTC
  maturity_rule: cohorts younger than 2 days excluded
  diagnostics_only: [none]
  reconciliation_tolerance: not applicable
  notes: acquisition source reaches product analytics through the MMP's partner integration, with the analytics ID passed to the MMP before it starts. On iOS, acquisition cohorts cover only users with a user-level source (ATT authorized, AdServices, link-attributed); the coverage share (41% in September) is shown beside the funnel.

- decision: Report payback and LTV to finance.
  owner: finance analyst
  cadence: monthly
  governing_report: warehouse ledger built from App Store Server Notifications V2 and Play RTDN, proceeds from store financial reports
  metric: net_proceeds_per_paid_subscriber_m3
  date_basis: transaction date, UTC
  maturity_rule: month closes 10 days after month end
  diagnostics_only: [MMP revenue, GA4 revenue]
  reconciliation_tolerance: MMP gross matches ledger gross exactly for the same transaction references, in the original currency, test purchases excluded; every unmatched reference is listed
```

## Measurement paths

iOS:

- ATT prompt at the end of the goal-setting screen, prompt version `att_v3`. History: `att_v2` sat after the first completed workout and was retired on 2026-09-15. Opt-in rate is authorized answers divided by authorized plus denied answers to first prompts, per prompt version. Additional Information taps and EU re-prompts will be reported separately once users reach iOS 27.2.
- SKAdNetwork and AdAttributionKit both on. The MMP SDK is the only conversion value updater; the schema lives in the MMP dashboard, so each change is recorded with its save timestamp as `cv_schema_changed_at`.
- Trials and purchases are verified on the subscription backend. The backend tells the app, and the app logs a revenue-free `trial_verified` SDK event, which the MMP schema maps to fine value 40 and coarse `high`. The MMP confirmed in writing on 2026-08-20 that S2S events do not drive conversion values on Acme's plan.
- Watch: share of winning first postbacks at or above the trial value, per release. It held at 11% to 13% across the last four releases.
- Both postback copy keys point to the MMP's documented endpoint. Re-engagement copies on; re-engagement postbacks are reported as their own metric and never counted as installs. Redownloads are a separate line.
- Evidence: a received SKAdNetwork copy and a received AdAttributionKit copy from release 4.12.0, dated 2026-10-03, including an Apple Ads AdAttributionKit copy from a 26.2 device.
- Crowd anonymity: each network's digit mapping is recorded. In September, 22% of the social network's winning first postbacks had only 2 digits or no value, under the 30% consolidation threshold.
- Apple Ads: the backend exchanges the AdServices token within an hour of first launch and stores the record.
- Web-to-app: example.com uses App Store campaign links (`ct` per landing page) for owned traffic; paid web ads are not run on iOS.

Android:

- The MMP SDK reads Install Referrer; the app stores `install_referrer_status` from the MMP callback on the install record.
- `AD_ID` declared in the merged manifest; `ad_id_status` stored on the install record at first launch.
- Web-to-app: Play links from example.com carry `referrer` values from an allow-list of campaign keys.
- Privacy status checked 2026-10-17: Privacy Sandbox on Android deprecated, retirement announced; no Attribution Reporting API work planned.

## SDK start order

- In-house CMP writing TCF strings; consent regions are the EEA and the UK, decided by the CMP's region check. One store binary per platform ships worldwide.
- Firebase static defaults in `Info.plist` and the manifest: `analytics_storage` and `ad_storage` denied, `ad_user_data` and `ad_personalization` set to `eu_consent_policy`. Outside consent regions the app grants all four with `setConsent` as soon as the region check returns; inside, it waits for the CMP answer.
- CMP and ATT: if the CMP answer denies ad consent, the ATT prompt is skipped; a denial in either means no IDFA use. Legal basis confirmed by the privacy owner on 2026-09-10.
- MMP consent via TCF collection, started only after the CMP stores the answer.
- ATT wait: production p90 time from first launch to the first ATT answer was 410 seconds with `att_v2` (after the first workout), above the 300-second ceiling the team set because the MMP documents no maximum. The prompt moved to the end of the goal-setting screen (`att_v3`); two weeks of production data give a p90 of 95 seconds with 6% of users leaving before the prompt; the wait is set to 120 seconds.

## Identity

- User ID: backend account ID (`usr_` plus a random string), set at signup and login in analytics, the MMP and the messaging SDK; cleared at logout in all three.
- Install-to-user mapping table stores account ID, platform, MMP install ID, analytics app instance ID and first-seen time. Renewals are sent against the install that started the subscription.
- Acquisition source in product analytics: one attributed test install on release 4.12.0 landed on the same analytics profile as its later events.
- S2S: the backend sends events only after the app has reported the MMP install ID, and holds events from the first 5 minutes after install until 5 minutes have passed, because the MMP may mark events that arrive before it has processed the install as organic. It stores occurrence and delivery time. The MMP keeps occurrence time only for events received by 02:00 UTC the next day, so the backend alerts when any event from the previous UTC day is unsent at 01:00 UTC.

## Revenue

- Verified purchases, renewals and refunds come from the subscription backend, which receives App Store Server Notifications V2 and Play RTDN and calls the Play Developer API for state.
- The backend is the only revenue sender to the MMP and to GA4. The app sends `paywall_viewed`, `purchase_started` and the revenue-free `trial_verified` only; Firebase automatic `in_app_purchase` is kept as a diagnostic and excluded from revenue reports.
- Test purchases are excluded before any send: Apple notifications with `environment` `Sandbox`, and Play subscriptions with a `testPurchase` object.
- Events carry gross price and currency. Weekly ROAS uses provisional net from a dated fee-rate table; finance trues it up monthly from store financial reports and reports the variance.
- Refunds are sent as `subscription_refunded` with negative revenue and the original transaction reference.

## Deep links

- Link domain `go.example.com`, owned by Acme, hosted by the link vendor with a documented exit plan.
- Firebase Dynamic Links remaining: 0 (checked 2026-10-10 across email templates, QR print files and help articles).
- Route contract tested on release 4.12.0 (iOS 26.1 and 18.6, Android 16 and 14): all six app states pass from the default browser, email click tracking, a pasted URL, the same-domain page and QR scans. From the social network's in-app browser, links open the web page; the link vendor's fallback banner then opens the app, recorded as the accepted fallback.

## MMP decision

- Decision: buy now, 2026-08-01, approved by the head of growth. Decided at step 1 of the rule: two paid networks on iOS beyond Apple Ads need postbacks, and deferred deep links carry referral installs.
- Build comparison: the in-house build, costed from the inventory at 900 hours a year at 90 USD (81,000 USD), against the first full year of the MMP at 19,400 USD (a 14,000 USD quote plus 60 internal hours at 90 USD). The cost test was not needed, because a trigger fired.
- The chosen vendor demonstrated Apple Ads through AdAttributionKit on an iOS 26.2 device before signing.
- Reopen when: renewal notice date, or the second iOS network is dropped.

## Sources checked

- Apple, Apple Ads, Google Play, Firebase and MMP pages listed in the skill's `sources.yaml`, checked 2026-10-17.
