feat: Sprint 3 — automation, intelligence, skill marketplace

Auto-Staffing Suggestions (A6):
- generateAutoSuggestions() ranks top-3 resources on demand creation
- Uses existing staffing engine (skill 40%, availability 30%, cost 20%, util 10%)
- Creates in-app notification with match scores for managers
- Triggered after createDemandRequirement and partial fillDemandRequirement

Vacation Conflict Detection (A7):
- checkVacationConflicts() warns when >50% chapter absent on same days
- Returns warnings array in approve/batchApprove responses (advisory, non-blocking)
- Creates VACATION_CONFLICT_WARNING notification for approver

Weekly Chargeability Alerts (A10):
- checkChargeabilityAlerts() finds resources >15pp below target
- Cron endpoint: GET /api/cron/chargeability-alerts
- Duplicate-safe by resourceId + month composite key

Rate Card Auto-Apply (A11):
- lookupRate() finds best matching rate card line (weighted scoring)
- Auto-fills demand line rates in estimate create/updateDraft when rates are 0
- Marks auto-filled lines with metadata.autoAppliedRateCard
- New lookupDemandLineRate query for on-demand UI lookups

Public Holiday Auto-Import (A12):
- autoImportPublicHolidays() generates holidays by resource federal state
- Cron endpoint: GET /api/cron/public-holidays?year=2027
- Duplicate-safe, uses existing getPublicHolidays() from shared

Skill Marketplace MVP (G6):
- New page: /analytics/skill-marketplace with 3 sections
- Skill Search: filter by name, proficiency, availability, sortable results
- Skill Gap Heat Map: supply vs demand per skill, shortage/surplus indicators
- Skill Distribution: top-20 horizontal bar chart (reuses SkillDistributionChart)
- New getSkillMarketplace query in resource router
- Sidebar nav link under Analytics for ADMIN/MANAGER/CONTROLLER

Co-Authored-By: claude-flow <ruv@ruv.net>
This commit is contained in:
2026-03-19 21:39:05 +01:00
parent 6e5b9ec85b
commit 6f34659587
16 changed files with 1906 additions and 4 deletions
+11
View File
@@ -31,6 +31,7 @@ import { findUniqueOrThrow } from "../db/helpers.js";
import { anonymizeResource, getAnonymizationDirectory } from "../lib/anonymization.js";
import { checkBudgetThresholds } from "../lib/budget-alerts.js";
import { emitAllocationCreated, emitAllocationDeleted, emitAllocationUpdated, emitNotificationCreated } from "../sse/event-bus.js";
import { generateAutoSuggestions } from "../lib/auto-staffing.js";
import { invalidateDashboardCache } from "../lib/cache.js";
import { createTRPCRouter, managerProcedure, protectedProcedure, requirePermission } from "../trpc.js";
import { PROJECT_BRIEF_SELECT, RESOURCE_BRIEF_SELECT, ROLE_BRIEF_SELECT } from "../db/selects.js";
@@ -495,6 +496,9 @@ export const allocationRouter = createTRPCRouter({
// eslint-disable-next-line @typescript-eslint/no-explicit-any
void checkBudgetThresholds(ctx.db as any, demandRequirement.projectId);
// Fire-and-forget: compute and notify top-3 staffing suggestions
// eslint-disable-next-line @typescript-eslint/no-explicit-any
void generateAutoSuggestions(ctx.db as any, demandRequirement.id);
return demandRequirement;
}),
@@ -631,6 +635,13 @@ export const allocationRouter = createTRPCRouter({
// eslint-disable-next-line @typescript-eslint/no-explicit-any
void checkBudgetThresholds(ctx.db as any, result.assignment.projectId);
// If there are still unfilled slots, refresh suggestions for remaining demand
if (result.updatedDemandRequirement.headcount > 0
&& result.updatedDemandRequirement.status !== "COMPLETED") {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
void generateAutoSuggestions(ctx.db as any, result.updatedDemandRequirement.id);
}
return result;
}),