Skip to content

feat(analytics): wire Province and Sector filters on Skills Analytics dashboard#191

Open
wilfredeveloper wants to merge 2 commits into
mainfrom
feat/core-355-skills-analytics-province-sector-filters
Open

feat(analytics): wire Province and Sector filters on Skills Analytics dashboard#191
wilfredeveloper wants to merge 2 commits into
mainfrom
feat/core-355-skills-analytics-province-sector-filters

Conversation

@wilfredeveloper

Copy link
Copy Markdown
Collaborator

Jira

https://tabiya-tech.atlassian.net/browse/CORE-355

Summary

  • Province and Sector dropdowns in the SkillsAnalytics component were inert placeholders (value="", no onChange, no options). This PR wires them end-to-end.
  • Both filters now trigger API refetches with the correct query params, and the backend scopes aggregations to the matching student population.
  • Province filter is admin-only (ignored for institution staff who are already scoped by JWT).
  • Sector filter works for all roles, using in-memory TEVETA data to map hub sector names (Agriculture, Energy, Hospitality, Mining, Water) → programme names → PLAIN_PERSONAL_DATA.data.programme_name.

Changes

Frontend (admin-frontend/)

  • moduleFilterOptions.ts — adds MODULE_FILTER_SECTORS (5 TEVETA sectors)
  • SkillsAnalytics.tsx — adds province/sector state; populates and wires both dropdowns
  • useSkillGapStats / useSkillsSupplyStats — accept province and sector; added to useEffect dep array
  • AnalyticsService.ts — appends province and sector to URL params for both endpoints

Backend (backend/)

  • analytics/user_filter.py (new) — shared helpers: resolve_user_ids_for_institution, resolve_user_ids_for_province, resolve_user_ids_for_sector, intersect_user_id_sets
  • analytics/skill_gap/routes.py — adds province and sector Query params; intersection logic
  • analytics/skills_supply/routes.py — same

Tooling

  • scripts/seed_analytics_test_data.py (new) — seeds 6 test users for manual filter verification

Test plan

Automated

# Backend
cd backend
poetry run pytest -k "skill_gap or skills_supply" -v

# Frontend type-check
cd admin-frontend
yarn compile

Manual (on dev with the seed script)

Seed test data:

cd backend
poetry run python scripts/seed_analytics_test_data.py

This seeds 6 users across all 5 sectors and 4 provinces. Expected results:

Filter Expected users / behaviour
No filters All 6 users visible in both charts
Province = Lusaka Students from Lusaka only
Province = Copperbelt Students from Copperbelt only
Sector = Agriculture Students enrolled in Agriculture-priority programmes
Sector = Energy Students enrolled in Energy-priority programmes
Sector = Hospitality Students enrolled in Tourism-priority programmes
Province = Copperbelt + Sector = Agriculture Intersection — 1 user
Province = Lusaka + Sector = Mining No overlap — empty state shown

Cleanup when done:

poetry run python scripts/seed_analytics_test_data.py --cleanup

Edge cases verified

  • Empty intersection (province + sector with no shared users) → charts show the existing "coming soon" empty state, no crash
  • Institution staff role: province param is silently ignored by backend; sector filter still applies on top of institution scope

… dashboard

Connects the previously-inert Province and Sector dropdowns in the
SkillsAnalytics admin component to actual API params on both the
frontend and backend.

Frontend:
- SkillsAnalytics.tsx: adds province/sector useState; populates dropdowns
  from MODULE_FILTER_LOCATIONS and new MODULE_FILTER_SECTORS; wires onChange
- useSkillGapStats / useSkillsSupplyStats: accept province and sector params;
  both added to useEffect dependency array so charts refetch on change
- AnalyticsService: appends province and sector to URLSearchParams for both
  skill-gap-stats and skills-supply-stats endpoints
- moduleFilterOptions.ts: adds MODULE_FILTER_SECTORS (Agriculture, Energy,
  Hospitality, Mining, Water)

Backend:
- analytics/user_filter.py (new): shared helpers for resolving user_id sets
  by institution, province, and sector; sector resolution uses in-memory
  TEVETA data (priority_sectors) to map hub names to programme names, then
  matches against PLAIN_PERSONAL_DATA.data.programme_name
- skill_gap/routes.py + skills_supply/routes.py: add province and sector
  Query params; intersect all active filter sets before scoping aggregation;
  province filter is admin-only (ignored for institution staff)

Tooling:
- scripts/seed_analytics_test_data.py: seeds 6 test users across all 5 sectors
  and 4 provinces for manual filter verification; run with --cleanup to remove

CORE-355
@rav3n11

rav3n11 commented May 19, 2026

Copy link
Copy Markdown
Collaborator

@wilfredeveloper there are conflicts that need to be resolved before we can merge this, please rebase this branch on main?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants