Process guide

Managing Employee Profiles (HR / manager)

CV upload via AI extractor + profile management for HR and managers, with visible audit and diff review.

Managing employee profiles (HR / manager)

Last updated: 2026-07-03 Owner: Product Writer Audience: hr_admin, tenant admin, owner, manager

Purpose

Explain how HR and managers can manage the Professional profile of other tenant employees — upload CVs, build profiles step by step, update specific fields — with audit visible to the target user for GDPR transparency.

Prerequisites

  • platform/tenant role enabled:
  • owner, tenant admin, hr_admin with permission tenant.hr.data.manage → tenant-wide scope
  • manager → own direct reports scope (manager_user_id relation in organization_user_details)
  • for CV upload: AI extractor service configured at platform level (platform_ai_settings.cv_extractor_provider, default haiku)
  • for audit persistence: migration 0072_profile_audit_columns.sql applied on the project

Key concepts

  • HR scope: anyone with tenant.hr.data.manage sees all tenant employees and can edit their professional profile
  • Manager scope: managers see and manage only their direct reports (Direct badge in the picker list)
  • Visible audit: every edit records last_edited_by_user_id, last_edited_at, last_edit_source (self/hr_admin/manager/system). The target user sees who/when modified what
  • PII boundary: HR and manager can edit firstName, lastName, headline, experiences, skills. They cannot modify email, phone, date of birth, address, IBAN, VAT number (reserved for the subject)
  • Contract data (payroll): HR and manager maintain badge number, fiscal code (checksum-validated), hire/termination dates, contract type, CCNL + level + classification, weekly hours, part-time % and workplace — legal basis: payroll obligation. Badge number and fiscal code appear in the payroll consultant reports
  • CV import: uploading a CV file (PDF/DOCX/TXT/MD) triggers the AI extractor which produces a structured draft reviewable field by field (inline diff review). Only confirmed fields are saved

Basic steps

  1. Edit fields directly and click Save personal data for the basics
  2. For CV import: see dedicated section below

CV upload (import-from-cv)

  1. Confirmation toast with detail: personal data saved, N experiences applied, M experiences failed (missing skills — complete via self-service wizard)

CV archive and global CV library (Storage 01)

From the HORIS_HR_CV_STORAGE_01 programme (2026-05-15) every uploaded CV is permanently archived in the tenant. Two access points:

"Archived CVs" section in the target profile

Above the blue Upload CV for pre-fill panel you find the list of CVs already uploaded for the target. Each row shows:

  • Original filename + size (KB)
  • Status badge: Uploaded (manual upload without apply), Extracted (parsed but not applied yet), Applied (data confirmed in profile), Archived
  • Upload date + author (self/HR admin/manager)
  • Open button → opens the PDF in a new tab via signed URL (TTL 5 minutes)
  • Delete button → audit-tracked soft delete (HR admin / owner / self only)

Global CV library `/workspace/hr/cv-library`

Button 📚 Global CV library → top-right on the /workspace/hr/people page. View reserved to HR admin / owner / admin (manager → 403 with dedicated message). Features:

  • Paginated table (default 25 rows) with all live tenant CVs
  • Combinable filters: filename query, status, date range From / To
  • Top stats: total live CVs, uploaded last 7 days, current page
  • Per-row actions: Open (signed URL), Profile → (deep link to the target profile already selected), Delete

Auto-archive in the CV import flow

When an HR/manager uploads a CV from the blue Upload CV for pre-fill panel:

  1. The file is parsed (parseCvBuffer) and sent to the AI extractor
  2. In parallel the original PDF is stored in the Supabase bucket hr-cvs with status='extracted' and a green badge ✓ Original CV archived appears in the modal
  3. If the user confirms Apply selected, the record is updated to status='applied' with applied_by_user_id audit
  4. If the bucket upload fails (storage down), the modal shows ⚠ CV archive failed but extraction and apply continue fail-soft

Permission summary

  • Self: sees and deletes only own CVs (future: UI inside /workspace/profile)
  • Manager: sees and deletes only direct report CVs from the target profile section (NO global CV library)
  • HR admin / owner / admin: sees and deletes ALL tenant CVs from the profile section + the global CV library

Bulk CV import — `/workspace/hr/cv-bulk` (Bulk 03)

From the HORIS_HR_CV_INGESTION_03_BULK programme (2026-05-18) HR admin can upload up to 50 CVs in a single flow + optional CSV mapping filename,target_email, and run extraction + apply in batch.

Access: 📤 Bulk CV import → button top-right on /workspace/hr/people. Reserved to HR admin / owner / admin (manager → access-denied page).

4-step flow

  1. Apply — blue progress bar with concurrency 3. For each selected row the system calls the profile-service helpers (reuses the identical PII boundary of the single-upload flow). At completion: 4 stat cards (total, applied, failed, job status) + per-row list with apply_summary.

Constraints

  • Existing tenant users only: bulk import does NOT create new users. For candidate pre-onboarding CVs see future programme PRE_ONBOARDING_01.
  • Max 50 files per job (configurable via HR_CV_BULK_MAX_FILES_PER_JOB).
  • MIME whitelist: same as single-upload (PDF/DOC/DOCX/TXT).
  • Audit-tracked per row: applied_by_user_id, applied_at, apply_summary (jsonb with per-step counters + errors).
  • Fail-soft per row: one failed row does not block the rest of the job; final job status is partially_applied if there are failures, completed if all good.

Performance

  • Client-orchestrated concurrency of 3 parallel calls on both steps (extraction + apply). No server-side endpoint processes N files in series → no Vercel 60s timeout.
  • 20 CVs ≈ 30-50 seconds end-to-end (review pause included).

Audit visibility on target user side

The target user Bob can see via API:

  • /api/people/<bob>/profile returns each experience/skill with last_edited_by_user_id, last_edited_at, last_edit_source
  • A future UI on /workspace/profile self will show a banner "Profile updated by {{HR name}} on {{date}} via {{source}}"

For now the self banner is part of post-program backlog (requires refactor of the 2671-line self wizard).

AI extractor provider configuration

The CV extraction provider is configurable via SQL on platform_ai_settings:

```sql -- Default (Haiku 4.5, ~$0.01-0.02 per CV, latency ~2-4s) update public.platform_ai_settings set cv_extractor_provider = NULL where scope = 'global'; -- or explicit update public.platform_ai_settings set cv_extractor_provider = 'haiku' where scope = 'global';

-- Quality upgrade (Sonnet 4.6, ~$0.03-0.05 per CV, marginally more accurate on complex CVs) update public.platform_ai_settings set cv_extractor_provider = 'sonnet' where scope = 'global'; ```

Costs are monitorable from Workspace › Platform › AI Usage (filter by kind=cv_extract or model=claude-haiku-4-5).

Expected result

  • HR/manager can manage other employees' professional profile without accessing personal contacts
  • CV upload → structured draft in 2-4 seconds
  • Field-by-field diff review eliminates risk of overwriting existing data without consent
  • Full audit trail for GDPR + accountability
  • Cost telemetry available in the AI Usage dashboard

Skill catalog matching (W02 02_UX_POLISH)

Skills extracted from the CV are automatically matched against the tenant skill catalog (hr_skills + hr_skill_catalog_extensions). For each skill:

  • Confidence badge:
  • High (green, >=95%): exact match on name or alias
  • Medium (blue, 75-95%): substring match (e.g. "Java" matches "Java SE 21")
  • Low (amber, 55-75%): fuzzy match via Levenshtein (typo tolerance, e.g. "Pyhton" → "Python")
  • No match (grey): skill not in catalog — create it from tenant admin panel first
  • Candidate dropdown: top 5 candidates ranked by confidence; you can switch to the correct candidate
  • Auto-selection: skills with confidence >=85% are pre-selected for Apply
  • "Verify" warning shown next to candidates < 85% to draw attention
  • Apply of selected skills → POST /api/people/[userId]/profile/skills with audit last_edit_source="hr_admin" or "manager"

Example: the CV proposes "Spring Boot, JS, Microsoft Office". The matcher finds:

  • Spring Boot → exact match 1.0 vs Spring Boot (catalog)
  • JS → alias match 0.92 vs JavaScript (alias=["JS","ECMAScript"])
  • Microsoft Office → exact match 1.0 vs Microsoft Office

All pre-selected with High badge. Click Save and close (5) → 5 skills applied to the target profile with visible audit.

Persistent diagnostic banner (W01+W04 02_UX_POLISH)

After every Apply, a color-coded banner appears bottom-right of the page:

  • Green (success): all operations OK
  • Amber (warning): mixed operations — some OK, some skipped/failed (e.g. experiences without catalog skills)
  • Red (error): critical error (e.g. permission, network)

The banner shows:

  • Title with summary ("Applied: 3 personal data fields + 5 skills + 0 experiences")
  • Detail with total duration ms + main failure reason
  • Steps list each with icon ✓ / ✕ / —
  • ISO timestamp
  • button for manual dismiss

Refresh resilience: the banner survives page refresh (10-minute window). Read from sessionStorage["horis.hr-people.lastApply"]. You can reload the page after apply and still see the result.

Residual limitations (post 02_UX_POLISH)

  1. CV experiences with empty skillIds (residual limit): the service-layer requires at least 1 skillId per experience. Experiences extracted from CV are NOT automatically associated to skills (experience↔skill linking requires more complex mapping). Workaround: extracted skills ARE saved to the profile as standalone (hr_user_skills), and HR completes the experience↔skill linking via the subject's self-service wizard post-save. Future programme: automatic experience↔skill linking via LLM context.
  2. Languages auto-save: extracted but not persisted. Backlog.
  3. Skills with no catalog match: skills the matcher doesn't find in tenant catalog are shown with No match badge and disabled checkbox. HR must create the skill from admin panel (hr_skill_catalog_extensions) before being able to apply it. Future programme: "Create skill in tenant catalog" inline workflow from review modal.
  4. ~~Inline add experience/skill UI on HR side: placeholders.~~ RESOLVED 2026-05-15: the + Add experience and + Add skill buttons now open functional HORIS-canonical modals with live skill catalog search + client-side validation + auto-linked experience↔skill.
  5. ~~OCR for scanned PDFs: not supported.~~ RESOLVED 2026-05-18 (HORIS_HR_OCR_01): PDFs without selectable text are OCR-decoded via Claude Haiku 4.5 vision as automatic fallback in parseCvBuffer. Transparent to the user. Disable via env HORIS_OCR_FALLBACK_ENABLED=false.
  6. Candidate pre-onboarding: the flow operates only on existing tenant members. Pre-hire candidate profile is out of scope (future programme).
  7. Mass upload: bulk CSV + N CV upload not available (future programme HORIS_HR_CV_INGESTION_03_BULK).
  8. Audit banner on self user side `/workspace/profile`: the target user today sees audit info via API. Dedicated UI banner inside the self-service wizard requires monolith refactor — post-programme backlog.

Common errors

  • 403 Forbidden / Missing permission
  • cause: role without tenant.hr.data.manage, or manager trying to edit a non-direct
  • solution: ask owner to assign the permission, or check manager scope
  • "PDF unreadable, attach .txt or .md"
  • cause: scanned PDF without selectable text (image)
  • solution: convert to text manually
  • "N experiences failed (missing skills)"
  • cause: extractor pulls roleTitle/description but the service-layer requires at least 1 linked skill
  • solution: HR completes skill linking via the subject's self-service wizard
  • Email/phone not updated even when in the CV
  • cause: PII is reserved for the subject; HR/manager cannot edit
  • solution: the subject updates their own personal data from their profile
  • AI provider not responding / empty extractor
  • cause: anthropic_api_key not configured, or provider in fail-soft
  • solution: check platform_ai_settings.anthropic_api_key + cv_extractor_provider; check Workspace › Platform › AI Usage for error rate

Related guides

FAQ

Can I see who modified Bob's profile?

Yes. Every experience and skill has audit fields last_edited_by_user_id, last_edited_at, last_edit_source. Visible in the HR editor + sticky banner.

Can HR edit Bob's email?

No. Email, phone, date of birth and address are reserved for the subject (contractual PII). Only Bob can modify them from his own Professional profile.

Can I use GPT/another LLM instead of Haiku?

In this version the provider is Anthropic (Haiku 4.5 default, Sonnet 4.6 optional). Switching to OpenAI/another provider requires additional development not included in this programme.

What happens if I upload a CV that was previously uploaded?

The system re-extracts and shows the diff review vs current profile. You decide what to apply. No automatic duplicates risk.

Are employees notified when I edit their profile?

For the MVP version there is no push notification. The target user sees the audit info when opening their own profile. Email/in-app notification is part of post-programme backlog.

Related guides