STORY-F-019: Industry profiles seed (labour_hire + construction) + ETS cache¶
Epic: Compliance Seed Priority: Must Have Story Points: 2 Status: Not Started Assigned To: Unassigned Created: 2026-04-17 Sprint: 4
User Story¶
As an Org Admin (future) and as a developer (now),
I want two real industry profiles seeded (labour_hire + construction) referencing credential_types from F-015, with an ETS cache for hot-path reads,
so that Phase 0's deliverable is genuinely multi-industry-ready (ADR per brainstorm-05) and subsequent Scout+Verify sprints have real industry config to reason over.
Description¶
Background¶
Brainstorm-05 + ADR (multi-industry design) commits to industries-as-configuration, not code. Ten profiles total (labour_hire, construction, mining, logistics, defence, retail, white_collar, traffic, civil, engineering) eventually; Phase 0 lands the two most relevant to ASG: labour_hire (meta-industry, typically combined) + construction.
This completes the Phase 0 compliance foundation. The remaining 8 profiles land as each relevant Migration Phase picks them up (e.g. mining + defence come when ASG starts serving those verticals).
Scope¶
In scope:
- Migration creating
compliance.industry_profilestable perdata.mdspec: - id UUID, slug VARCHAR UNIQUE, display_name, description, required_credential_types JSONB ([credential_type_ids]), recommended_credential_types JSONB, enabled_modules JSONB (["safety","fatigue","clearance"]), onboarding_steps JSONB, compliance_rules JSONB, terminology JSONB, dashboard_config JSONB, report_templates JSONB, metadata JSONB, timestamps, soft delete — platform-wide reference data (no org_id per
data.mdreference-data pattern) compliance.org_industry_profilespivot — (org_id, industry_profile_id) composite PK, activated_at, custom_overrides JSONB- Seed file
priv/repo/seeds/compliance_industry_profiles.exs: - labour_hire — slug:
labour_hire, required_creds:[right_to_work_*, first_aid_provide, wwcc], recommended:[white_card], enabled_modules:[](meta — no module-specific enablement), compliance_rules:{"consent_required": true, "national_police_check": true}, terminology:{"worker": "candidate"}(labour hire norm), onboarding_steps:["right_to_work_verify", "national_police_check", "first_aid_verify"] - construction — slug:
construction, required_creds:[white_card, first_aid_provide], recommended:[height_safety, working_at_heights, confined_space, ewp, telehandler, forklift], enabled_modules:["safety", "assets"], compliance_rules:{"swms_required": true, "site_induction_required": true, "ppe_hi_vis_required": true}, terminology:{"worker": "operative", "shift": "deployment"}, onboarding_steps:["right_to_work_verify", "white_card_verify", "site_induction", "ppe_issuance"], dashboard_config:{"widgets": ["active_worksites_map", "credential_expiry_heatmap"]} FinnestCompliance.IndustryProfileEcto schema (reference-data allowlist)FinnestCompliance.OrgIndustryProfileEcto schemaFinnestCompliance.IndustryProfilescontext:get_by_slug/1,list/0,activate_for_org/2,deactivate_for_org/2,profiles_for_org/1,merged_requirements/1(union of required_credentials across an org's active profiles per brainstorm-05 "composable profiles")FinnestCompliance.IndustryProfileCacheETS GenServer — mirrorsCredentialCachepattern from F-015; refreshes onindustry_profile_updatedevent- Integration:
FinnestCompliance.check/2(skeleton from F-015) extended to read active industry profiles for the worker's org and determine which credentials are required — still returns:compliantplaceholder for now; real gated-write logic lands in Scout+Verify sprints - Architecture test: IndustryProfile is reference-data (no org_id); OrgIndustryProfile has composite PK including org_id (expected exception to standard
id UUID PKpattern — document in allowlist)
Out of scope:
- Remaining 8 industry profiles (mining, logistics, defence, retail, traffic, civil, engineering, white_collar) — land as relevant phases pick them up
- Full dashboard widget implementation (Scout+Verify sprints for analytics work)
- Industry-adaptive UI theming (brainstorm-09 vision — Migration Phase 2 or 3)
FinnestCompliance.check/2real gating logic (Scout+Verify sprints)- Admin UI for industry profile management (ties to F-020 admin console work, but not in Phase 0)
Technical Notes¶
- Composable profiles per brainstorm-05: an org can activate both
labour_hire+construction; required_credentials = UNION. This is implemented inmerged_requirements/1. - Seed idempotent: upsert by
slug; safe to re-run required_credential_typesstored as array of credential_type UUIDs — resolve at seed time by looking up F-015 seeds- Architecture test
industry_profile_composability_test.exs: activate labour_hire + construction → merged_requirements contains UNION (no duplicates) - Terminology JSONB — when rendering UI for a given org, look up active profiles' terminology and merge (later profile overrides earlier) → pass as locale-like context. Phase 0 only scaffolds; no UI uses it yet.
- Module namespace: modules live under
FinnestCompliance.*(flat — notFinnest.Compliance.*). Same pattern as F-015 per Sprint 3 D4 resolution (Boundary library constraint).
Dependencies¶
- Blocked by: STORY-F-015 (credential_types seed — profiles reference credential IDs)
Acceptance Criteria¶
- Migration creates
compliance.industry_profiles+compliance.org_industry_profiles; reversible - Seed inserts 2 profiles (
labour_hire,construction); idempotent on re-run -
FinnestCompliance.IndustryProfiles.get_by_slug("construction")returns construction profile with correct required/recommended credentials -
IndustryProfiles.activate_for_org(org, profile)creates pivot row; emitsindustry_profile_activatedevent -
IndustryProfiles.merged_requirements(org)with both profiles active returns UNION of credentials (no duplicates) -
IndustryProfileCacheETS loads on boot; cache hit <1ms - Each seeded profile references valid credential_type UUIDs (FK integrity via runtime lookup)
- Architecture test: reference-data allowlist applies; queries don't require tenant context
-
FinnestCompliance.check/2skeleton extends to use profile data (returns:compliantplaceholder still, but reads from real profiles) -
mix format,credo --strict,dialyzer,mix boundaryall green
Testing Requirements¶
- Unit: seed runs cleanly; repeated seed no-op
- Unit: context functions (get_by_slug, activate_for_org, merged_requirements) happy + error paths
- Integration: activate both profiles for an org → merged_requirements returns UNION; deactivate one → re-query returns only the other's credentials
- Cache: populate + invalidate + re-populate test
References¶
../architecture/data.md§compliance schema, §industry_profiles../brainstorms/brainstorm-05-multi-industry-design.md§Pillar 2 Industry Profiles, §Composable profiles../brainstorms/brainstorm-11-traffio-laravel-migration-naming.md§Traffic Management Industry Profile (as reference shape)../adrs/adr-011-F-compliance-auto-blocking.md