Skip to main content

#1082 Implementation Summary

What Was Implementedโ€‹

Backend (api/)โ€‹

  1. Database Migration (1780300000002-UpdateExpertApplicationForAI.ts)

    • Added new columns for #1082 spec: current_role, years_experience, impact_statement, ai_score_breakdown (JSONB), qualification_threshold, reapply_eligible_at
    • Changed ai_score from int to float (0-1 range)
    • Added unique index on email for duplicate detection
    • Added index for rejected applications (90-day cooldown queries)
  2. Entity Update (entities/expert-application.entity.ts)

    • Extended ExpertApplicationStatus enum with new states: qualified, invited, interview_completed
    • Added types: YearsExperience, PrimaryDomain, AIScoreBreakdown
    • Added fields: currentRole, yearsExperience, impactStatement, aiScoreBreakdown, qualificationThreshold, reapplyEligibleAt
  3. DTO Update (dto/expert-application.dto.ts)

    • CreateExpertApplicationDto: 8 required fields with validation per #1082 spec
    • LinkedIn OR resume URL validation (at least one required)
    • Impact statement: 300 char max with MinLength(10)
    • CheckApplicationStatusDto: new DTO for status lookup by email
    • Response types defined
  4. Service Update (marketing.service.ts)

    • checkApplicationStatus(): duplicate detection + re-application gating (90-day cooldown)
    • createExpertApplication(): creates application with new fields, checks duplicates
    • triggerAIScreeningStub(): STUB - logs intent, BLOCKED pending Eusden/ops confirmation
    • runAIScreening(): admin method to manually trigger AI scoring
    • updateQualificationThreshold(): admin-configurable threshold
    • Constants for AI weights (40% seniority, 40% domain, 20% impact) and default threshold (0.70)
  5. Controller Update (marketing.controller.ts)

    • POST /marketing/experts/application: create application (5 req/min limit)
    • GET /marketing/experts/application/status: check status by email (30 req/min)
    • GET /marketing/experts/application/:id: get public-safe application details (60 req/min)

Frontend (frontend/)โ€‹

  1. Application Form Component (experts/apply/_components/expert-application-form.tsx)

    • Single-page form with all 8 fields
    • Client-side validation matching DTO rules
    • LinkedIn OR resume URL validation
    • Impact statement: 300 char counter
    • Duplicate detection via pre-flight check to status endpoint
    • Re-application gating: shows reapply date if rejected < 90 days
    • Confirmation screen with immediate feedback (no spinner wait)
    • Mobile-responsive design
  2. Page Component (experts/apply/page.tsx)

    • Metadata for SEO
    • Dark gradient background
    • Public route (no auth required)

What Is BLOCKED / Pendingโ€‹

1. AI Scoring Criteria (Waiting for Eusden/ops)โ€‹

Location: marketing.service.ts:triggerAIScreeningStub()

The AI scoring implementation is stubbed. Before enabling:

  • Confirm weights: seniority 40%, domain 40%, impact 20%
  • Confirm threshold: 0.70 default
  • Implement actual LLM call with structured output
  • Calculate composite score: seniority*0.4 + domain*0.4 + impact*0.2
  • Store breakdown in aiScoreBreakdown (JSONB)
  • Generate reasoning text in aiReasoning

2. Resume File Upload (Needs S3/R2 Integration)โ€‹

Location: Frontend form, resumeUrl field

Currently using URL input. For full implementation:

  • Add S3/R2 bucket for resume storage
  • Implement presigned URL upload flow
  • Add virus scanning (ClamAV or similar)
  • Store resumeUrl as CDN URL

3. Email Flows (Depends on #1083 #1084)โ€‹

Location: marketing.service.ts

  • Qualified โ†’ Tavus interview email with link (#1083)
  • Waitlisted โ†’ Waitlist confirmation email (#1084)
  • Email templates and sending logic

4. Tavus Integration (#1083)โ€‹

Location: marketing.service.ts:promoteExpertApplication(), state transitions

When AI score >= threshold:

  • Trigger Tavus conversation creation
  • Update status to invited
  • Store tavusConversationId, tavusConversationUrl
  • Handle webhook conversation.ended โ†’ interview_completed

5. Async Job Queue (Optional)โ€‹

Current: AI stub logs intent synchronously Future: Move AI scoring to background job (BullMQ/SQS)

Testing Checklistโ€‹

  • Submit form with all 8 fields โ†’ creates application with status pending
  • Submit duplicate email โ†’ shows status check message
  • Submit with rejected status < 90 days โ†’ shows reapply date
  • LinkedIn OR resume validation โ†’ must have at least one
  • Character limits โ†’ currentRole 80, impactStatement 300
  • Mobile layout โ†’ works at 375px width
  • API rate limiting โ†’ 5 req/min for submit

Database Queriesโ€‹

-- Check for duplicate
SELECT * FROM expert_applications WHERE LOWER(email) = LOWER($1) ORDER BY created_at DESC LIMIT 1;

-- Re-application gating (rejected within 90 days)
SELECT * FROM expert_applications
WHERE email = $1 AND status = 'rejected'
AND updated_at > NOW() - INTERVAL '90 days';

-- List pending applications for AI processing
SELECT * FROM expert_applications
WHERE status = 'pending' AND ai_score IS NULL;

State Machineโ€‹

[pending]
โ†“ AI screening runs (BLOCKED: stub only)
โ”œโ”€ score >= 0.70 โ†’ [qualified]
โ”‚ โ†“ Tavus invite (BLOCKED: #1083)
โ”‚ โ†’ [invited]
โ”‚ โ†“ Interview complete
โ”‚ โ†’ [interview_completed]
โ”‚ โ†“ Admin approval
โ”‚ โ†’ [approved]
โ””โ”€ score < 0.70 โ†’ [waitlisted]
โ†“ Admin decline
โ†’ [rejected]
โ†“ 90-day cooldown
โ†’ reapply_eligible_at set
  • #1081: Expert Landing Page โœ… (already implemented, links to /experts/apply)
  • #1083: Tavus Video Interview โ† BLOCKS qualified flow
  • #1084: Waitlist Flow โ† BLOCKS waitlisted emails