diff --git a/README.md b/README.md index 0c6580c..64400f9 100644 --- a/README.md +++ b/README.md @@ -234,6 +234,46 @@ raising a budget on a campaign that is awaiting approval returns `409`, because it would change the figure the approver is reviewing. Reject it back to draft, edit, and resubmit. +### Analytics + +Insights are stored in `maskanx_campaign_insights` and read from there, so +the dashboard stays fast and keeps working while Meta is rate-limiting. Only +`POST /campaigns/{id}/insights/refresh` goes out to Meta. + +| Route | Returns | +| --- | --- | +| `GET /campaigns/analytics/dashboard` | Yesterday and last-7-days spend, leads and cost per lead, plus the most and least efficient campaign | +| `GET /campaigns/{id}/insights` | A daily series and the total for one campaign | +| `GET /campaigns/{id}/insights?breakdown=age` | The same metrics split by one breakdown | +| `POST /campaigns/{id}/insights/refresh` | Re-fetches from Meta now | + +Three things about how this works are worth knowing before changing it: + +**Insights are re-fetched, not appended.** Meta revises a day's figures for +about 28 days as conversions are attributed late, so each sync re-reads a +trailing window and upserts on +`(level, object_id, date, breakdown_key, breakdown_value)`. Appending would +double spend the first time a day was refreshed. Window: +`MASKANX_INSIGHTS_WINDOW_DAYS`, default 28 — shortening it freezes late +conversions out permanently. + +**Breakdowns multiply rows, not columns.** The same spend appears in every +breakdown's rows, so they are tagged with a `breakdown_key` and every +aggregate filters to `breakdown_key = ''`. A query that summed across +breakdowns would count the same spend once per age bracket. + +**Both dashboard windows end yesterday.** Today's figures are partial; +including them would make every "vs. last week" comparison read as a +collapse each morning. + +Costs are always computed from summed totals, never averaged from per-day +costs — the average of daily costs per lead is not the cost per lead over the +period. Cost fields are `null`, not `0`, when there is nothing to divide by. + +Sync interval: `MASKANX_INSIGHTS_SYNC_SECONDS`, default 3600. `0` disables +the loop; the dashboard then only updates when a campaign is refreshed by +hand. + ### Live smoke test The unit suite runs entirely against a fake transport: it proves the code