Add personio-headcount skill (migrated from plugin)

This commit is contained in:
2026-03-05 13:56:00 +00:00
parent 8cc66c699a
commit 16a3b9e2ee
+302
View File
@@ -0,0 +1,302 @@
---
name: personio-headcount
description: >
Retrieves and reports HelloPrint's current headcount from Personio via the N8N Personio MCP tool.
Maintains a local anonymized snapshot (no PII — no names or personal details) so that repeat
queries are instant and only delta/incremental fetches are needed. Can generate professional
PDF workforce reports with team/department breakdowns, joiner/leaver analysis, and net movement.
Use this skill whenever someone asks about headcount, employee count, active employees, FTE count,
team size, staffing numbers, Personio data, workforce metrics, workforce report, HR report,
or anything related to how many people work at HelloPrint. Also trigger when someone mentions
"personio headcount", "HR numbers", "who's active", "onboarding count", "leavers", "attrition",
"joiners", "departures", "workforce insights", "headcount report", "team size report",
or wants a breakdown by status, department, team, office, or start date. Even casual questions
like "how many people do we have?" should trigger this skill.
---
# Personio Headcount Skill
This skill gives you instant access to HelloPrint's workforce numbers by maintaining a local
anonymized snapshot of Personio data. It connects to Personio via the **"N8N Personio"** MCP
server (URL: `https://helloprint-staging.app.n8n.cloud/mcp/personio`).
## How It Works
There are three modes:
1. **Snapshot mode** (fast) — uses cached data to answer headcount questions instantly
2. **Refresh mode** — fetches latest from Personio via `Call_Personio_Get_Employees_` MCP tool in batches of 50
3. **Report mode** — generates a professional PDF workforce insights report
The skill stores anonymized employee records — only IDs, status, team, department, office, and
dates. Never names, emails, or personal information.
### Data Location
```
<skill-directory>/data/headcount_snapshot.json — anonymized employee records
<skill-directory>/data/team_mapping.json — team_id ↔ team name lookup (derived)
<skill-directory>/data/department_mapping.json — department_id ↔ department name + team groupings (derived)
<skill-directory>/data/office_mapping.json — office_id with headcounts (derived)
<skill-directory>/data/team-mapping.csv — SOURCE OF TRUTH for team ID → name
<skill-directory>/data/department-mapping.csv — SOURCE OF TRUTH for department ID → name
<skill-directory>/scripts/build_snapshot.py — merge/deduplicate employee data, rebuild mappings
<skill-directory>/scripts/build_report.py — generate workforce insights PDF report
```
The two CSV files (`team-mapping.csv` and `department-mapping.csv`) are the **single source of
truth** for human-readable names. The JSON mapping files are derived from these CSVs combined with
headcount data from the snapshot. When rebuilding mappings, always read names from the CSVs.
Read the snapshot first. If it exists and `last_refreshed` is recent enough for the user's needs,
just report from the cached data. If it's stale or the user explicitly asks for fresh data,
run a refresh. Always load the team mapping alongside the snapshot to resolve team names.
### 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
The team mapping at `data/team_mapping.json` maps real 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
}
]
}
```
Use `team_id` in the snapshot to look up the human-readable name from the mapping.
### Department Mapping Structure
The department mapping at `data/department_mapping.json` 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
The office mapping at `data/office_mapping.json` tracks headcount per office location:
```json
{
"total_offices": 8,
"offices": [
{
"office_id": "501083",
"active_headcount": 120,
"total_records": 500
}
]
}
```
## Answering Headcount Questions
When the user asks about headcount:
1. **Read the snapshot** from `data/headcount_snapshot.json`
2. **Read the team mapping** from `data/team_mapping.json`
3. **Check freshness** — if `last_refreshed` is today, use it directly. If older, mention the
date and offer to refresh.
4. **Report the answer** — the "current headcount" is the count of employees with `status = "ACTIVE"`.
Common questions and how to derive them:
- **"Current headcount"** → count where status = ACTIVE
- **"Total employees in system"** → total_records
- **"How many people left?"** → count where status = INACTIVE
- **"Who's onboarding?"** → count where status = ONBOARDING
- **"People on leave"** → count where status = LEAVE
- **"Headcount by team"** → group ACTIVE employees by `team_id`, look up names from team_mapping.json
- **"Headcount by department"** → group ACTIVE employees by `department_id`, use department_mapping.json for team groupings
- **"Headcount by office"** → group ACTIVE employees by `office_id`
- **"How big is team X?"** → find team by name in mapping, count ACTIVE employees with that team_id
- **"New hires this year"** → ACTIVE employees where start_date >= 2026-01-01
## Refreshing the Data
When fresh data is needed, fetch from Personio via the **N8N Personio** MCP tool.
### Full Refresh Process
Use the `Call_Personio_Get_Employees_` MCP tool from the "N8N Personio" server:
```
Step 1: Fetch first page
Call_Personio_Get_Employees_ with input: {"limit": 50}
Step 2: Parse result
The result is a JSON array containing one object with:
- employees: array of employee objects
- totalCount: number of records returned
- nextCursor: pagination cursor string
- hasMore: boolean
Step 3: If hasMore is true, fetch next page
Call_Personio_Get_Employees_ with input: {"limit": 50, "cursor": "<nextCursor>"}
Step 4: Repeat until hasMore is false
```
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)
For efficiency, you don't always need a full refresh. The incremental approach:
1. Read the existing snapshot
2. Fetch all pages from Personio (the API doesn't support a "modified since" filter yet)
3. Compare each fetched employee against the snapshot by `personio_id`:
- If ID exists → update the record (status, team_id, etc. may have changed)
- If ID is new → add it
- IDs in snapshot but not in fresh data → keep them (they're historical)
4. Recalculate the summary counts
5. Update `last_refreshed` timestamp
6. Rebuild team_mapping.json counts (active_headcount and total_records per team)
7. Save all files
The helper script at `scripts/build_snapshot.py` automates the merge logic. After collecting
all employee batches into a list, run:
```bash
python3 <skill-dir>/scripts/build_snapshot.py \
--existing <skill-dir>/data/headcount_snapshot.json \
--new-data /tmp/fresh_employees.json \
--output <skill-dir>/data/headcount_snapshot.json
```
### Important: Stripping PII
Before storing ANY data, remove all personally identifiable information:
- Remove `first_name` and `last_name`
- Remove `email`
- Keep only: personio_id, status, team_id, department_id, office_id, start_date, end_date
The `personio_id` is an internal system identifier, not PII — it's needed for deduplication
during incremental updates.
## MCP Tool Reference
**MCP Server:** n8n-personio (local config in `claude_desktop_config.json`)
**URL:** `https://helloprint-staging.app.n8n.cloud/mcp/personio`
**Auth:** Bearer token
**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": "<nextCursor>"}`
### Authentication
The N8N MCP Server Trigger uses **Bearer token authentication**. This is configured in the local
`claude_desktop_config.json` under the MCP server name `n8n-personio`. The Bearer token must
match the one set in N8N's MCP Server Trigger node.
Note: Cowork's built-in MCP connector UI does not support Bearer or Header Auth — the local
config approach via `claude_desktop_config.json` is the working method.
## Generating a PDF Report
When the user asks for a workforce report, headcount report, or PDF report, use the
`build_report.py` script to generate a professional PDF.
### Running the Report Script
```bash
pip install reportlab --break-system-packages
python3 <skill-dir>/scripts/build_report.py \
--data-dir <skill-dir>/data \
--output /path/to/output/helloprint_workforce_report.pdf
```
### What the Report Contains
The PDF report has 6 pages:
1. **Cover page** — KPI cards showing active employees, onboarding, on leave, and inactive counts
2. **Section 1: Headcount by Team** — all active teams ranked by headcount with percentages
3. **Section 2: Headcount by Department** — departments with team listings
4. **Section 3: New Joiners** — monthly breakdown since January 2025, with team attribution
5. **Section 4: Departures** — monthly breakdown since January 2025, with team attribution
6. **Section 5: Net Workforce Movement** — monthly joiners vs leavers with cumulative net change
Each section includes insight paragraphs highlighting trends (peak hiring months, largest teams,
year-start turnover patterns).
### XML Escaping
The report script uses `xml_escape()` to handle special characters in team and department names
(e.g., "M&A" → "M&amp;A"). This is necessary because reportlab's Paragraph class uses an XML
parser internally. The `make_table()` helper automatically escapes all cell content.
### Customizing the Report
The script is parameterized via `--data-dir` and `--output`. The color scheme uses HelloPrint
branding (HP_BLUE `#1a3a5c`, HP_LIGHT_BLUE `#2980b9`). To change the date range for joiners/
leavers analysis, modify the `datetime(2025, 1, 1)` threshold in `build_report()`.
## Output Guidelines
When reporting headcount:
- Lead with the headline number: **"Current active headcount: X"**
- Mention the snapshot date so the user knows how fresh the data is
- Offer a breakdown if relevant (by status, team, department, office)
- If the data is more than 1 day old, proactively offer to refresh