Core Concepts

Skill-Scoring System

The 5-tier evidence model HUBCV uses to score every skill — the same way across jobs, courses, events, and projects.

One evidence model, four posting kinds

Every skill's score comes from the same 5-tier weighted formula (Completion 1/0.1, Assessment 0.01, Task 0.01, Attendance 0.001, Community 0.0001 — reserved for phase 2), computed live in lib/skills.ts's computeSkillScores. What differs by posting kind is where the underlying evidence rows live, not how they're weighted.

KindCompletionAssessment/TaskAttendance
CoursecourseEnrollment.statusgradedItem (kind: test/assignment)attendanceWeek
Job / Event / ProjectpostingParticipant.statuspostingTask (kind: exam/task)postingAttendance

Courses keep their existing tables — nothing about existing grades or attendance was migrated, just reweighted under the new tiers. Job, event, and project Positions had no equivalent to gradedItem/attendanceWeek at all before this, so postingTask and postingAttendance are new, scoped to a user's specific postingParticipant assignment rather than a whole posting.

Assessment and Task are not flat per-item credit — each item's contribution is (score / max) × its tier weight, so a failed or ungraded item contributes close to nothing, not the full weight. A perfect score contributes the full 0.01.

postingRole and postingParticipant

A Hub admin defines named positions on a posting — for example "Winner" on a hackathon, "Contributor" on a project, "Lead Software Engineer" on a job listing — each carrying a skills[] list and an optional capacity. Assigning a real user to that position creates a verified activityRecord crediting exactly those skills and shows up immediately in that user's dynamic resume. Marking the assignment "completed" (postingParticipant.status) is what triggers Completion-tier credit — being merely active in a position doesn't, since Completion specifically means the user finished their involvement.

lib/actions/postingRoles.ts
export async function assignParticipant(orgId: string, postingRoleId: string, userId: string) {
  await requireOrgAdmin(orgId);
  // ...creates a verified activityRecord with the role's skills,
  // inserts the postingParticipant row that links user to role
}
This is the same underlying mechanism for Event, Project, and Job rooms — the PostingInfo component in components/app/ChatroomClient.tsx is one shared UI reused across all three, not three separate implementations. It shows a roster, the defined positions with their skill tags, and — for a Hub admin — an inline control to assign or remove someone.

Undoing it

removeParticipant reverses the assignment cleanly: deletes the linked activityRecord and removes the postingParticipant row. Nothing about it is soft-deleted or archived — the resume reflects the current state, not history.

There's no admin-facing UI yet to mark a position/enrollment complete, create a postingTask, or record postingAttendance — those tables and the status field exist and are fully wired into scoring, but today they're populated by seed data only. Real authoring UI for managers/faculty is planned follow-on work.

Where you can see it

  • The Activity page's Timeline, Skills, and Reports tabs — the dedicated view for browsing every piece of evidence and its rolled-up scores; see Activity Tracking.
  • Inside the relevant Event/Project/Job chat room's info panel, with an assign control if you're a Hub admin.
  • On the posting's public Hub detail page, read-only — a roster and the defined positions with their skill tags, no assign control there.
  • In the resume, as a normal activity-record entry under Projects & Social Activity or Experience, and in the Skills section's per-skill score.

Score-band labels

A raw skill score is also mapped to a 5-band label via proficiencyLabel() in lib/skills.ts: Learning (below 20), Beginner (20-39), Amateur (40-59), Advanced (60-79), Pro (80+). The same thresholds drive the Activity page's Skills-tab progress bars and the Reports tab's proficiency breakdown.