# Affiliate HubSpot Cache Architecture

**Last Updated:** 2026-03-25

Schema and data flow for `affiliate_hubspot_cache.json`. The sync writes this file; all display APIs read from it.

## File Location

Resolved by `findReadableAffiliateCacheFile()` / `findWritableAffiliateCacheFile()` in [affiliate-paths.php](../../v2/includes/affiliate-paths.php). Priority:

1. `v2/data/affiliate_hubspot_cache.json`
2. `wp-content/uploads/affiliate-partners/affiliate_hubspot_cache.json`
3. `writable/affiliate_hubspot_cache.json`
4. `data/affiliate_hubspot_cache.json`
5. `/tmp/affiliate_hubspot_cache.json` (fallback)

## Cache Schema

```json
{
  "version": "1.0",
  "last_sync": "2026-02-12T17:00:34+00:00",
  "partners": {
    "AP-XXX": {
      "partner_id": "AP-XXX",
      "name": "Partner Name",
      "email": "email@example.com",
      "level": "starter",
      "mrr": { "total_mrr": 0, "active_mrr": 0, ... },
      "contact_count": 3,
      "deal_count": 3
    }
  },
  "leads": {
    "AP-XXX": [
      { "id": "...", "properties": { "email": "...", "company": "...", ... } }
    ]
  },
  "deals": {
    "AP-XXX": [
      { "id": "...", "properties": { "dealname": "...", "dealstage": "...", ... } }
    ]
  },
  "mrr_summary": {
    "AP-XXX": {
      "total_mrr": 87.8,
      "active_mrr": 47.8,
      "total_deals": 3,
      ...
    }
  }
}
```

## Data Flow

| Step | Actor | Action |
|------|-------|--------|
| 1 | Sync (cron or manual) | Calls HubSpot API: `getContactsByAffiliatePartner()`, `getDealsByAffiliatePartner()` per partner |
| 2 | Sync | Calculates MRR, level; writes `leads`, `deals`, `mrr_summary`, `partners` to cache |
| 3 | Display APIs | Read cache via `loadCachedHubSpotData()`; format and return JSON |

**No HubSpot API calls during display.** User navigation (Dashboard → Leads → Earnings → Levels) triggers only cache reads.

## Who Consumes What

| API | Uses |
|-----|------|
| affiliate-dashboard-data | leads, deals |
| affiliate-leads | leads |
| affiliate-earnings | deals |
| affiliate-levels | leads, deals, mrr_summary |
| affiliate-leaderboard | mrr_summary |
| affiliate-admin-list | leads, deals, mrr_summary |
| affiliate-admin-program-metrics | leads, deals (program-wide aggregates; trends from timestamps on cached records) |

**Trends / Monatsreihen:** APIs wie `affiliate-admin-program-metrics` gruppieren Leads und Abschlüsse nach Monat anhand von Referral-/Erstellungs- bzw. Abschlussdaten im **aktuellen** Cache. Es gibt keine Zeitreihe vergangener Cache-Stände.

## Contact UTM Properties

The sync fetches UTM properties on contacts for campaign reporting. HubSpot property names (internal API):

| UTM Parameter | HubSpot Property |
|---------------|------------------|
| utm_source   | `source__c`      |
| utm_medium   | `utm_medium__c`  |
| utm_campaign | `utm_campaign__c`|
| utm_content  | `content__c`     |
| utm_term     | `utm_term__c`    |

These are requested in `getContactsByAffiliatePartner()` and exposed via `formatLeadForAPI()` as `utm_source`, `utm_medium`, `utm_campaign`, `utm_content`, `utm_term`.

## Sync Pagination

The sync fetches **all pages** for each partner. HubSpot Search API returns up to 100 records per page; pagination uses `after` from `paging.next`. Partners with 100+ contacts or deals receive full data.

## Related Documentation

- [ARCHITECTURE.md](ARCHITECTURE.md) – Cache-First Architecture subsection
- [CRON_SYNC_RUNBOOK.md](CRON_SYNC_RUNBOOK.md) – Sync schedule and troubleshooting
- [DATA_GLOSSARY.md](DATA_GLOSSARY.md) – Term definitions (Leads, Deals, MRR, etc.)
