-
Notifications
You must be signed in to change notification settings - Fork 440
feat: Add interview-scorecard-calibrator kit #333
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
Swaraj07082
wants to merge
5
commits into
Lamatic:main
Choose a base branch
from
Swaraj07082:feat/interview-scorecard-calibrator
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
aa7ee44
feat: Add interview-scorecard-calibrator kit
Swaraj07082 c7f02a2
feat: Update next.config and orchestrate logic for calibrate-scorecar…
Swaraj07082 d4ae24c
feat: Upgrade Node.js version and enhance UI components in interview-…
Swaraj07082 345c1ea
refactor: Convert UI components to forward refs and enhance error han…
Swaraj07082 c73f86f
refactor: Improve accessibility and error handling in scorecard form …
Swaraj07082 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| # Flow Configurations | ||
| CALIBRATE_SCORECARD_FLOW_ID=your-lamatic-flow-uuid-here | ||
|
|
||
| # API Credentials | ||
| LAMATIC_API_KEY=your-lamatic-client-api-key-here | ||
| LAMATIC_PROJECT_ID=your-lamatic-project-id-here | ||
| LAMATIC_API_URL=https://your-organization-project-dev.lamatic.dev |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| .lamatic/ | ||
| node_modules/ | ||
| .next/ | ||
| .env | ||
| .env.local |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,110 @@ | ||
| # Interview Scorecard Calibrator | ||
|
|
||
| Reconcile multi-interviewer panel feedback into a calibrated hiring scorecard. | ||
|
|
||
| ## Problem | ||
|
|
||
| After a panel interview, hiring managers receive fragmented notes — different formats, uneven scoring rigor, and conflicting opinions. Reconciling them into a fair hire/no-hire decision is slow and inconsistent. | ||
|
|
||
| ## Solution | ||
|
|
||
| Paste a role rubric and interviewer notes. The Lamatic flow returns: | ||
|
|
||
| - Per-competency calibrated scores with evidence | ||
| - Interviewer disagreement map | ||
| - Hire / lean-hire / lean-no / no-hire recommendation + confidence | ||
| - Follow-up questions for missing evidence | ||
| - Internal decision-summary email draft | ||
| - A markdown hiring-committee brief | ||
|
|
||
| ## Why this kit is unique | ||
|
|
||
| Unlike resume screeners (`hiring`, `hiring-copilot-agent`, `candidate-screener`) or candidate self-reflection coaches (`interview-reflection-coach`), this kit focuses on **post-panel calibration** for hiring managers. | ||
|
|
||
| ## Providers & Prerequisites | ||
|
|
||
| - Lamatic account + deployed flow (import or rebuild from this kit) | ||
| - LLM provider configured in Lamatic Studio (Gemini / OpenAI / etc.) | ||
| - Node.js 20.9+ | ||
|
|
||
| ## Setup in Lamatic Studio | ||
|
|
||
| 1. Create a project in [studio.lamatic.ai](https://studio.lamatic.ai) | ||
| 2. Recreate the flow from `flows/calibrate-scorecard.ts` (or import if your Studio supports kit import) | ||
| 3. Attach prompts from `prompts/`, model configs from `model-configs/`, and the normalize script from `scripts/` | ||
| 4. Bind a text generation model on both LLM nodes | ||
| 5. Deploy the flow and copy the **Flow ID** | ||
|
|
||
| ## Run locally | ||
|
|
||
| ```bash | ||
| cd kits/interview-scorecard-calibrator/apps | ||
| cp .env.example .env.local | ||
| # fill in LAMATIC_API_URL, LAMATIC_PROJECT_ID, LAMATIC_API_KEY, CALIBRATE_SCORECARD_FLOW_ID | ||
| npm install | ||
| npm run dev | ||
| ``` | ||
|
|
||
| Open [http://localhost:3000](http://localhost:3000). | ||
|
|
||
| ## Environment variables | ||
|
|
||
| Copy either kit-root `.env.example` or `apps/.env.example` into `apps/.env.local`. | ||
|
|
||
| | Variable | Description | | ||
| |---|---| | ||
| | `LAMATIC_API_URL` | Lamatic API endpoint | | ||
| | `LAMATIC_PROJECT_ID` | Project ID | | ||
| | `LAMATIC_API_KEY` | API key | | ||
| | `CALIBRATE_SCORECARD_FLOW_ID` | Deployed flow ID for `calibrate-scorecard` | | ||
|
|
||
| ## Interviewer notes format | ||
|
|
||
| Provide **at least two** interviewer entries, separated by: | ||
|
|
||
| - a line containing only `---`, and/or | ||
| - headings like `Interviewer 1:` / `Interviewer 2 (Bob, Eng Manager):` | ||
|
|
||
| Requests with fewer than two interviewer blocks are rejected before the model runs. | ||
|
|
||
| ## Sample input | ||
|
|
||
| **Job title:** Senior Backend Engineer | ||
| **Level:** L5 | ||
|
|
||
| **Rubric:** | ||
| ``` | ||
| System Design (High): scalable services, tradeoffs, reliability | ||
| Coding (High): correctness, clarity, edge cases | ||
| Ownership (Medium): end-to-end delivery, communication | ||
| ``` | ||
|
|
||
| **Interviewer notes:** | ||
| ``` | ||
| Interviewer 1 (Alice, Staff Eng): | ||
| Strong system design around caching and failover. Coding solid but a bit slow. Ownership examples were concrete. Score: Design 4, Coding 3, Ownership 4. | ||
|
|
||
| --- | ||
|
|
||
| Interviewer 2 (Bob, Eng Manager): | ||
| Design felt hand-wavy on consistency. Coding was clean. Concerned about stakeholder communication. Score: Design 2, Coding 4, Ownership 2. | ||
| ``` | ||
|
|
||
| ## Deploy | ||
|
|
||
| Use the Vercel deploy link in `lamatic.config.ts` (`links.deploy`) with `root-directory=kits/interview-scorecard-calibrator/apps`. | ||
|
|
||
| ## Project structure | ||
|
|
||
| ``` | ||
| kits/interview-scorecard-calibrator/ | ||
| ├── lamatic.config.ts | ||
| ├── agent.md | ||
| ├── README.md | ||
| ├── constitutions/default.md | ||
| ├── flows/calibrate-scorecard.ts | ||
| ├── prompts/ | ||
| ├── model-configs/ | ||
| ├── scripts/ | ||
| └── apps/ # Next.js UI | ||
| ``` | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,39 @@ | ||
| # Interview Scorecard Calibrator | ||
|
|
||
| ## Overview | ||
| This kit helps hiring managers reconcile fragmented multi-interviewer feedback into a calibrated competency scorecard, disagreement map, hire/no-hire recommendation, and an internal decision-summary email draft. It is a single Lamatic flow invoked by the included Next.js app. | ||
|
|
||
| ## Purpose | ||
| After panel interviews, notes arrive in different formats with uneven scoring rigor. Manual reconciliation is slow and inconsistent. This agent turns a shared rubric plus interviewer notes into a structured decision aid so committees can focus on evidence and disagreements instead of formatting. | ||
|
|
||
| ## Flows | ||
|
|
||
| ### `calibrate-scorecard` | ||
|
|
||
| - Trigger: on-demand API execution from the kit UI or any Lamatic SDK caller | ||
| - Inputs: | ||
| - `job_title` (required) | ||
| - `level` (optional) | ||
| - `rubric` (required) — competency list with weights/expectations | ||
| - `interviewer_notes` (required) — notes from 2–6 interviewers | ||
| - Pipeline: | ||
| 1. **Normalize Inputs** (`codeNode`) — validates and packages context | ||
| 2. **Calibrate Scorecard** (`InstructorLLMNode`) — structured JSON scorecard | ||
| 3. **Compose Brief** (`LLMNode`) — markdown hiring-committee brief | ||
| 4. **API Response** — returns `scorecard` + `brief` | ||
| - Outputs: | ||
| - `scorecard` — competencies, disagreements, recommendation, confidence, follow-ups, email draft | ||
| - `brief` — readable markdown summary for the hiring committee | ||
|
|
||
| ## Guardrails | ||
| - Decision aid only — humans make the final hiring call | ||
| - Do not fabricate interview evidence | ||
| - Avoid protected-class inferences | ||
| - Keep candidate/interviewer content confidential | ||
| - See `constitutions/default.md` | ||
|
|
||
| ## Environment | ||
| - `LAMATIC_API_URL` | ||
| - `LAMATIC_PROJECT_ID` | ||
| - `LAMATIC_API_KEY` | ||
| - `CALIBRATE_SCORECARD_FLOW_ID` |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| # Flow Configurations | ||
| CALIBRATE_SCORECARD_FLOW_ID=your-lamatic-flow-uuid-here | ||
|
|
||
| # API Credentials | ||
| LAMATIC_API_KEY=your-lamatic-client-api-key-here | ||
| LAMATIC_PROJECT_ID=your-lamatic-project-id-here | ||
| LAMATIC_API_URL=https://your-organization-project-dev.lamatic.dev |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,23 @@ | ||
| /node_modules | ||
| /.pnp | ||
| .pnp.* | ||
| .yarn/* | ||
| !.yarn/patches | ||
| !.yarn/plugins | ||
| !.yarn/releases | ||
| !.yarn/versions | ||
| /coverage | ||
| /.next/ | ||
| /out/ | ||
| /build | ||
| .DS_Store | ||
| *.pem | ||
| npm-debug.log* | ||
| yarn-debug.log* | ||
| yarn-error.log* | ||
| .pnpm-debug.log* | ||
| .env* | ||
| !.env.example | ||
| .vercel | ||
| *.tsbuildinfo | ||
| next-env.d.ts |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| # Interview Scorecard Calibrator App | ||
|
|
||
| Next.js UI for the Interview Scorecard Calibrator kit. | ||
|
|
||
| ```bash | ||
| cp .env.example .env.local | ||
| npm install | ||
| npm run dev | ||
| ``` | ||
|
|
||
| Required env vars are documented in the parent [README.md](../README.md). |
128 changes: 128 additions & 0 deletions
128
kits/interview-scorecard-calibrator/apps/actions/orchestrate.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,128 @@ | ||
| "use server"; | ||
|
|
||
| import { getLamaticClient } from "@/lib/lamatic-client"; | ||
| import { parseScorecard, splitInterviewerNotes, type Scorecard } from "@/lib/scorecard"; | ||
| import kitConfig from "../../lamatic.config"; | ||
|
|
||
| export type CalibrateInput = { | ||
| jobTitle: string; | ||
| level: string; | ||
| rubric: string; | ||
| interviewerNotes: string; | ||
| }; | ||
|
|
||
| export type CalibrateResult = { | ||
| success: boolean; | ||
| data?: { | ||
| scorecard: Scorecard; | ||
| brief: string; | ||
| raw: unknown; | ||
| }; | ||
| error?: string; | ||
| }; | ||
|
|
||
| /** Resolve the deployed calibrate-scorecard flow ID from kit-root lamatic.config. */ | ||
| function getCalibrateFlowId() { | ||
| const step = kitConfig.steps.find((s) => s.id === "calibrate-scorecard"); | ||
| if (!step?.envKey) { | ||
| throw new Error( | ||
| 'lamatic.config has no step "calibrate-scorecard" with an envKey', | ||
| ); | ||
| } | ||
|
|
||
| const flowId = process.env[step.envKey]; | ||
| if (!flowId) { | ||
| throw new Error( | ||
| `${step.envKey} is not set. Set ${step.envKey} in the application environment.`, | ||
| ); | ||
| } | ||
| return flowId; | ||
| } | ||
|
|
||
| /** Parse a JSON string when possible; otherwise return the original value. */ | ||
| function parseMaybeJson(value: unknown) { | ||
| if (typeof value !== "string") return value; | ||
| try { | ||
| return JSON.parse(value); | ||
| } catch { | ||
| return value; | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Invokes the Lamatic calibrate-scorecard flow and validates the structured response. | ||
| */ | ||
| export async function calibrateScorecard( | ||
| input: CalibrateInput, | ||
| ): Promise<CalibrateResult> { | ||
| try { | ||
| const jobTitle = input.jobTitle.trim(); | ||
| const rubric = input.rubric.trim(); | ||
| const interviewerNotes = input.interviewerNotes.trim(); | ||
| const level = input.level.trim(); | ||
|
|
||
| if (!jobTitle) throw new Error("Job title is required"); | ||
| if (!rubric) throw new Error("Competency rubric is required"); | ||
| if (!interviewerNotes) throw new Error("Interviewer notes are required"); | ||
|
|
||
| const interviewerBlocks = splitInterviewerNotes(interviewerNotes); | ||
| if (interviewerBlocks.length < 2) { | ||
| throw new Error( | ||
| "Provide notes from at least two interviewers, separated by '---' or 'Interviewer N:' headings", | ||
| ); | ||
| } | ||
|
|
||
| const flowId = getCalibrateFlowId(); | ||
| const lamaticClient = getLamaticClient(); | ||
| const resData = await lamaticClient.executeFlow(flowId, { | ||
| job_title: jobTitle, | ||
| level: level || "Unspecified", | ||
| rubric, | ||
| interviewer_notes: interviewerNotes, | ||
| }); | ||
|
|
||
| const result = | ||
| (resData as { result?: Record<string, unknown> })?.result || resData; | ||
| const scorecardRaw = parseMaybeJson( | ||
| (result as Record<string, unknown>)?.scorecard ?? | ||
| (result as Record<string, unknown>)?.answer ?? | ||
| result, | ||
| ); | ||
| const scorecard = parseScorecard(scorecardRaw); | ||
| if (!scorecard) { | ||
| throw new Error( | ||
| "Flow returned an invalid scorecard payload. Check the deployed flow output schema.", | ||
| ); | ||
| } | ||
|
|
||
| const brief = | ||
| typeof (result as Record<string, unknown>)?.brief === "string" | ||
| ? ((result as Record<string, unknown>).brief as string) | ||
| : ""; | ||
|
|
||
| return { | ||
| success: true, | ||
| data: { | ||
| scorecard, | ||
| brief, | ||
| raw: resData, | ||
| }, | ||
| }; | ||
| } catch (error) { | ||
| let errorMessage = "Unknown error occurred"; | ||
| if (error instanceof Error) { | ||
| errorMessage = error.message; | ||
| if (error.message.includes("fetch failed")) { | ||
| errorMessage = | ||
| "Network error: Unable to connect to Lamatic. Check your internet connection and API URL."; | ||
| } else if (error.message.toLowerCase().includes("api key")) { | ||
| errorMessage = "Authentication error: Please check your Lamatic API key."; | ||
| } | ||
| } | ||
|
|
||
| return { | ||
| success: false, | ||
| error: errorMessage, | ||
| }; | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,55 @@ | ||
| @import "tailwindcss"; | ||
| @plugin "@tailwindcss/typography"; | ||
|
|
||
| :root { | ||
| --background: #f4f7fb; | ||
| --foreground: #0f172a; | ||
| --panel: #ffffff; | ||
| --accent: #0f766e; | ||
| --accent-soft: #ccfbf1; | ||
| --border: #dbe3ee; | ||
| --muted: #64748b; | ||
| --input: #e2e8f0; | ||
| --ring: #0f766e; | ||
| --primary: #0f766e; | ||
| --primary-foreground: #ffffff; | ||
| --secondary: #f1f5f9; | ||
| --secondary-foreground: #334155; | ||
| --destructive: #e11d48; | ||
| --rec-hire-bg: #d1fae5; | ||
| --rec-hire-fg: #065f46; | ||
| --rec-hire-border: #a7f3d0; | ||
| --rec-lean-hire-bg: #ccfbf1; | ||
| --rec-lean-hire-fg: #115e59; | ||
| --rec-lean-hire-border: #99f6e4; | ||
| --rec-lean-no-bg: #fef3c7; | ||
| --rec-lean-no-fg: #78350f; | ||
| --rec-lean-no-border: #fde68a; | ||
| --rec-no-hire-bg: #ffe4e6; | ||
| --rec-no-hire-fg: #9f1239; | ||
| --rec-no-hire-border: #fecdd3; | ||
| --rec-fallback-bg: #f1f5f9; | ||
| --rec-fallback-fg: #334155; | ||
| --rec-fallback-border: #e2e8f0; | ||
| } | ||
|
|
||
| @theme inline { | ||
| --color-background: var(--background); | ||
| --color-foreground: var(--foreground); | ||
| --font-sans: var(--font-source-sans); | ||
| --font-display: var(--font-fraunces); | ||
| } | ||
|
|
||
| body { | ||
| background: | ||
| radial-gradient(circle at top left, rgba(15, 118, 110, 0.12), transparent 32%), | ||
| radial-gradient(circle at top right, rgba(14, 165, 233, 0.1), transparent 28%), | ||
| linear-gradient(180deg, #eef5f8 0%, var(--background) 48%, #e8eef5 100%); | ||
| color: var(--foreground); | ||
| font-family: var(--font-sans), "Segoe UI", sans-serif; | ||
| min-height: 100vh; | ||
| } | ||
|
|
||
| .display-font { | ||
| font-family: var(--font-display), Georgia, serif; | ||
| } |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.