# Data Schemas ## Snapshot Structure ```json { "last_refreshed": "2026-03-04T19:00:00Z", "total_records": 755, "summary": { "ACTIVE": 177, "INACTIVE": 574, "ONBOARDING": 1, "LEAVE": 3 }, "employees": [ { "personio_id": "35653993", "status": "ONBOARDING", "team_id": "417186", "department_id": "1291054", "office_id": "501083", "start_date": "2026-05-01", "end_date": null } ] } ``` ## Team Mapping Structure Maps Personio team IDs to human-readable names: ```json { "total_teams": 65, "teams": [ { "team_id": "4400959", "team_name": "AI & Automation", "active_headcount": 6, "total_records": 7 } ] } ``` ## Department Mapping Structure Groups teams under departments with real names from `department-mapping.csv`: ```json { "total_departments": 19, "departments": [ { "department_id": "1291054", "department_name": "Finance & Business Navigation", "active_headcount": 11, "total_records": 45, "team_ids": ["417186", "417184", "3442693", "3443053", "572110"], "teams": ["Data", "Finance & Administration", "M&A", "Pricing", "Rev Ops"] } ] } ``` ## Office Mapping Structure ```json { "total_offices": 8, "offices": [ { "office_id": "501083", "active_headcount": 120, "total_records": 500 } ] } ``` ## MCP Tool Reference **Tool:** `Call_Personio_Get_Employees_` (full ID: `mcp__n8n-personio__Call_Personio_Get_Employees_`) | Parameter | Type | Default | Description | |-----------|--------|---------|--------------------------------------| | limit | number | 50 | Records per page (use 50 for balance)| | cursor | string | "" | Pagination cursor from previous page | Pass parameters as a JSON string in the `input` field: - First page: `{"limit": 50}` - Subsequent pages: `{"limit": 50, "cursor": ""}` ## Employee Record Fields (from API) Each employee record from the MCP tool contains: - `id` → store as `personio_id` - `status` → ACTIVE, INACTIVE, ONBOARDING, or LEAVE - `team_id` → Personio team ID (look up name in team_mapping.json) - `department_id` → Personio department ID - `office_id` → Personio office ID - `start_date` → employment start - `end_date` → contract end date (null if ongoing) - `first_name`, `last_name`, `email` → **NEVER STORE THESE** — strip before saving ## Incremental Refresh (Delta Update) 1. Read existing snapshot 2. Fetch all pages from Personio (no "modified since" filter available) 3. Compare each employee by `personio_id`: - ID exists → update the record (status, team_id, etc. may have changed) - ID is new → add it - ID in snapshot but not in fresh data → keep (historical) 4. Recalculate summary counts 5. Update `last_refreshed` timestamp 6. Rebuild team_mapping.json counts 7. Save all files ## PDF Report Contents The PDF report has 6 pages: 1. **Cover page** — KPI cards (active, onboarding, on leave, inactive) 2. **Headcount by Team** — all active teams ranked by headcount with percentages 3. **Headcount by Department** — departments with team listings 4. **New Joiners** — monthly breakdown since Jan 2025, with team attribution 5. **Departures** — monthly breakdown since Jan 2025, with team attribution 6. **Net Workforce Movement** — joiners vs leavers with cumulative net change ### XML Escaping The report uses `xml_escape()` for special characters in names (e.g., "M&A" → "M&A"). This is required because reportlab's Paragraph class uses an XML parser internally.