F011 - AI-Powered Coach Matching System
Objective
Implement an intelligent coach-manuscript matching system that pairs authors with the most suitable writing coaches based on manuscript analysis, genre expertise, coaching style preferences, and success metrics within the MyStoryFlow platform.
Quick Implementation
Using MyStoryFlow Components
- User matching service from
@mystoryflow/matching - Coach availability from
@mystoryflow/scheduling - Review system from
@mystoryflow/reviews - Communication platform from
@mystoryflow/messaging
New Requirements
- Coach profiling and expertise mapping
- AI-powered compatibility scoring
- Multi-factor matching algorithm
- Success prediction model
- Dynamic re-matching system
MVP Implementation
1. Coach Matching Engine
// packages/coach-matching/src/services/matching-engine.ts
import { AIService } from '@mystoryflow/manuscript-analysis'
import { Logger } from '@mystoryflow/logger'
import { EmbeddingsService } from '@mystoryflow/ai-core'
import { SchedulingService } from '@mystoryflow/scheduling'
export class CoachMatchingEngine {
private aiService: AIService
private embeddings: EmbeddingsService
private scheduling: SchedulingService
private logger: Logger
constructor() {
this.aiService = new AIService()
this.embeddings = new EmbeddingsService()
this.scheduling = new SchedulingService()
this.logger = new Logger('CoachMatchingEngine')
}
async findOptimalMatches(params: {
manuscriptId: string
userId: string
analysis: ManuscriptAnalysis
preferences: AuthorPreferences
requirements: CoachingRequirements
}): Promise<CoachMatches> {
const { manuscriptId, userId, analysis, preferences, requirements } = params
// Get available coaches
const availableCoaches = await this.getAvailableCoaches(requirements)
// Generate manuscript embedding
const manuscriptEmbedding = await this.generateManuscriptEmbedding(analysis)
// Calculate compatibility scores
const scoredCoaches = await Promise.all(
availableCoaches.map(async coach => {
const score = await this.calculateCompatibilityScore({
coach,
manuscriptAnalysis: analysis,
manuscriptEmbedding,
authorPreferences: preferences,
requirements
})
return { coach, score }
})
)
// Rank and filter matches
const topMatches = this.rankMatches(scoredCoaches)
.filter(match => match.score.overall > 0.7)
.slice(0, 5)
// Enhance with detailed match reasons
const enhancedMatches = await this.enhanceMatches(topMatches, analysis)
// Predict success likelihood
const matchesWithPredictions = await this.addSuccessPredictions(
enhancedMatches,
userId
)
return {
matches: matchesWithPredictions,
alternativeOptions: this.generateAlternativeOptions(scoredCoaches),
matchingInsights: this.generateMatchingInsights(matchesWithPredictions)
}
}
private async calculateCompatibilityScore(params: {
coach: Coach
manuscriptAnalysis: ManuscriptAnalysis
manuscriptEmbedding: number[]
authorPreferences: AuthorPreferences
requirements: CoachingRequirements
}): Promise<CompatibilityScore> {
const { coach, manuscriptAnalysis, manuscriptEmbedding, authorPreferences } = params
// Calculate sub-scores
const expertiseScore = await this.calculateExpertiseMatch(
coach,
manuscriptAnalysis
)
const styleScore = await this.calculateStyleMatch(
coach,
manuscriptEmbedding
)
const availabilityScore = this.calculateAvailabilityMatch(
coach,
params.requirements
)
const personalityScore = await this.calculatePersonalityMatch(
coach,
authorPreferences
)
const successScore = await this.calculateHistoricalSuccess(
coach,
manuscriptAnalysis.genre
)
// Weighted combination
const weights = {
expertise: 0.35,
style: 0.25,
availability: 0.15,
personality: 0.15,
success: 0.10
}
const overall =
expertiseScore * weights.expertise +
styleScore * weights.style +
availabilityScore * weights.availability +
personalityScore * weights.personality +
successScore * weights.success
return {
overall,
breakdown: {
expertise: expertiseScore,
style: styleScore,
availability: availabilityScore,
personality: personalityScore,
historicalSuccess: successScore
},
strengths: this.identifyMatchStrengths({ expertiseScore, styleScore, personalityScore }),
concerns: this.identifyMatchConcerns({ expertiseScore, styleScore, availabilityScore })
}
}
private async calculateExpertiseMatch(
coach: Coach,
analysis: ManuscriptAnalysis
): Promise<number> {
// Genre expertise
const genreMatch = coach.expertise.genres.includes(analysis.genre) ? 1.0 : 0.5
// Specific skill matches
const neededSkills = this.identifyNeededSkills(analysis)
const skillMatches = neededSkills.filter(skill =>
coach.expertise.specializations.includes(skill)
).length / neededSkills.length
// Experience level match
const experienceMatch = this.matchExperienceLevel(
coach.experience,
analysis.complexity
)
// Certification relevance
const certificationBonus = coach.certifications
.filter(cert => this.isRelevantCertification(cert, analysis))
.length * 0.05
return Math.min(
1.0,
genreMatch * 0.4 +
skillMatches * 0.3 +
experienceMatch * 0.2 +
certificationBonus + 0.1
)
}
private async calculateStyleMatch(
coach: Coach,
manuscriptEmbedding: number[]
): Promise<number> {
// Get coach's writing style embedding from their bio and sample edits
const coachStyleEmbedding = await this.embeddings.generateEmbeddings([
coach.bio,
...coach.sampleEdits.map(edit => edit.content)
])
// Calculate cosine similarity
const similarity = this.cosineSimilarity(
manuscriptEmbedding,
coachStyleEmbedding[0]
)
// Adjust based on coaching approach
const approachMultiplier = coach.coachingStyle === 'adaptive' ? 1.1 : 1.0
return Math.min(1.0, similarity * approachMultiplier)
}
private async calculatePersonalityMatch(
coach: Coach,
preferences: AuthorPreferences
): Promise<number> {
let score = 0.5 // Base score
// Communication style match
if (coach.communicationStyle === preferences.preferredCommunicationStyle) {
score += 0.2
}
// Feedback style match
if (coach.feedbackStyle === preferences.preferredFeedbackStyle) {
score += 0.15
}
// Session format preferences
const formatMatch = coach.availableFormats.filter(format =>
preferences.preferredFormats.includes(format)
).length / preferences.preferredFormats.length
score += formatMatch * 0.15
return Math.min(1.0, score)
}
private identifyNeededSkills(analysis: ManuscriptAnalysis): string[] {
const skills: string[] = []
// Based on weaknesses
if (analysis.scores.characterDevelopment < 0.7) {
skills.push('character-development', 'character-arcs')
}
if (analysis.scores.dialogue < 0.7) {
skills.push('dialogue-writing', 'voice-development')
}
if (analysis.scores.plotStructure < 0.7) {
skills.push('plot-structure', 'story-architecture')
}
if (analysis.scores.pacing < 0.7) {
skills.push('pacing', 'scene-structure')
}
// Genre-specific skills
const genreSkills = this.getGenreSpecificSkills(analysis.genre)
skills.push(...genreSkills)
return [...new Set(skills)]
}
}2. Coach Profile Enhancement
// packages/coach-matching/src/services/coach-profiler.ts
export class CoachProfiler {
private aiService: AIService
private analyticsService: AnalyticsService
async buildEnhancedProfile(coachId: string): Promise<EnhancedCoachProfile> {
const baseProfile = await this.getBaseProfile(coachId)
const performanceData = await this.getPerformanceData(coachId)
const clientFeedback = await this.getClientFeedback(coachId)
// Analyze coaching samples
const styleAnalysis = await this.analyzeCoachingStyle(
baseProfile.sampleEdits,
baseProfile.testimonials
)
// Extract expertise signals
const expertiseSignals = await this.extractExpertiseSignals(
baseProfile,
performanceData
)
// Calculate success metrics
const successMetrics = this.calculateSuccessMetrics(
performanceData,
clientFeedback
)
// Generate coaching philosophy embedding
const philosophyEmbedding = await this.generatePhilosophyEmbedding(
baseProfile.coachingPhilosophy,
styleAnalysis
)
return {
...baseProfile,
enhancedData: {
styleProfile: styleAnalysis,
expertiseDepth: expertiseSignals,
successMetrics,
philosophyEmbedding,
strengthAreas: this.identifyStrengthAreas(performanceData),
idealClientProfile: await this.generateIdealClientProfile(
successMetrics,
clientFeedback
),
coachingPatterns: this.analyzeCoachingPatterns(performanceData)
}
}
}
private async analyzeCoachingStyle(
sampleEdits: SampleEdit[],
testimonials: Testimonial[]
): Promise<CoachingStyleProfile> {
// Analyze editing patterns
const editAnalysis = await this.aiService.analyzeManuscript(
JSON.stringify(sampleEdits),
'coaching-style',
{
analysisType: 'editing-patterns',
outputFormat: 'structured'
}
)
// Extract key characteristics
return {
primaryApproach: editAnalysis.approach, // 'nurturing', 'direct', 'collaborative'
feedbackTone: editAnalysis.tone, // 'encouraging', 'constructive', 'challenging'
focusAreas: editAnalysis.commonFocusAreas,
editingDepth: editAnalysis.depth, // 'surface', 'structural', 'comprehensive'
communicationStyle: this.extractCommunicationStyle(testimonials),
coachingTechniques: editAnalysis.techniques,
adaptability: this.assessAdaptability(sampleEdits)
}
}
private calculateSuccessMetrics(
performance: PerformanceData,
feedback: ClientFeedback[]
): CoachSuccessMetrics {
const satisfactionScore = feedback.reduce(
(sum, f) => sum + f.satisfaction,
0
) / feedback.length
const completionRate = performance.completedProjects /
performance.totalProjects
const improvementMetrics = this.calculateAverageImprovement(
performance.clientOutcomes
)
const repeatClientRate = performance.repeatClients /
performance.uniqueClients
return {
overallSuccess: this.calculateWeightedSuccess({
satisfaction: satisfactionScore,
completion: completionRate,
improvement: improvementMetrics.average,
retention: repeatClientRate
}),
clientSatisfaction: satisfactionScore,
projectCompletion: completionRate,
averageImprovement: improvementMetrics,
clientRetention: repeatClientRate,
specialtySuccess: this.calculateSpecialtySuccess(performance),
timeline: {
avgResponseTime: performance.avgResponseHours,
avgProjectDuration: performance.avgProjectDays,
punctualityScore: performance.onTimeDelivery
}
}
}
}3. Success Prediction Model
// packages/coach-matching/src/models/success-predictor.ts
export class SuccessPredictor {
private model: PredictionModel
private historicalData: HistoricalDataService
async predictMatchSuccess(
match: CoachMatch,
authorProfile: AuthorProfile,
manuscriptAnalysis: ManuscriptAnalysis
): Promise<SuccessPrediction> {
// Gather feature vectors
const features = await this.extractFeatures({
coachProfile: match.coach.enhancedProfile,
authorProfile,
manuscriptAnalysis,
compatibilityScore: match.score
})
// Get historical similar matches
const similarMatches = await this.findSimilarHistoricalMatches(features)
// Make prediction
const prediction = await this.model.predict(features)
// Calculate confidence based on similar match data
const confidence = this.calculateConfidence(similarMatches, features)
// Generate specific success factors
const successFactors = this.identifySuccessFactors(
features,
similarMatches
)
// Identify potential challenges
const challenges = this.identifyPotentialChallenges(
match,
authorProfile,
manuscriptAnalysis
)
return {
likelihood: prediction.probability,
confidence,
successFactors,
challenges,
recommendedApproach: this.generateRecommendedApproach(
successFactors,
challenges
),
expectedOutcomes: this.projectOutcomes(
prediction,
manuscriptAnalysis
),
timelineEstimate: this.estimateTimeline(
manuscriptAnalysis.wordCount,
match.coach.enhancedProfile.successMetrics.timeline
)
}
}
private async extractFeatures(params: {
coachProfile: EnhancedCoachProfile
authorProfile: AuthorProfile
manuscriptAnalysis: ManuscriptAnalysis
compatibilityScore: CompatibilityScore
}): Promise<FeatureVector> {
const { coachProfile, authorProfile, manuscriptAnalysis, compatibilityScore } = params
return {
// Coach features
coachExperience: coachProfile.experience.years,
coachSpecialtyMatch: compatibilityScore.breakdown.expertise,
coachSuccessRate: coachProfile.enhancedData.successMetrics.overallSuccess,
coachWorkload: coachProfile.currentWorkload,
// Author features
authorExperience: authorProfile.writingExperience,
authorMotivation: authorProfile.motivationLevel,
authorTimeCommitment: authorProfile.availableHoursPerWeek,
authorLearningStyle: this.encodeLearningStyle(authorProfile.learningStyle),
// Manuscript features
manuscriptQuality: manuscriptAnalysis.scores.overall,
manuscriptComplexity: manuscriptAnalysis.complexity,
improvementPotential: manuscriptAnalysis.improvementPotential,
genreAlignment: this.calculateGenreAlignment(
coachProfile.expertise.genres,
manuscriptAnalysis.genre
),
// Match features
styleCompatibility: compatibilityScore.breakdown.style,
personalityMatch: compatibilityScore.breakdown.personality,
scheduleAlignment: compatibilityScore.breakdown.availability,
// Interaction features
communicationMatch: this.calculateCommunicationMatch(
coachProfile.enhancedData.styleProfile.communicationStyle,
authorProfile.communicationPreferences
),
expectationAlignment: this.calculateExpectationAlignment(
authorProfile.goals,
coachProfile.enhancedData.idealClientProfile
)
}
}
private identifySuccessFactors(
features: FeatureVector,
historicalMatches: HistoricalMatch[]
): SuccessFactor[] {
const factors: SuccessFactor[] = []
// High compatibility areas
if (features.styleCompatibility > 0.8) {
factors.push({
factor: 'Strong style alignment',
impact: 'high',
description: 'Coach\'s editing style aligns well with manuscript voice'
})
}
if (features.coachSpecialtyMatch > 0.85) {
factors.push({
factor: 'Deep genre expertise',
impact: 'high',
description: 'Coach has extensive experience in this specific genre'
})
}
// Historical success patterns
const successfulSimilar = historicalMatches.filter(m => m.outcome.success)
if (successfulSimilar.length > 5) {
factors.push({
factor: 'Proven track record',
impact: 'medium',
description: `${successfulSimilar.length} similar successful matches`
})
}
return factors
}
}4. Dynamic Matching API
// apps/story-analyzer/src/app/api/coaches/match/route.ts
import { NextRequest, NextResponse } from 'next/server'
import { CoachMatchingEngine } from '@mystoryflow/coach-matching'
import { withAuth } from '@mystoryflow/auth'
import { trackEvent } from '@mystoryflow/analytics'
export async function POST(req: NextRequest) {
try {
const session = await withAuth(req)
if (!session) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
}
const {
manuscriptId,
preferences,
requirements,
urgency = 'normal'
} = await req.json()
// Get manuscript analysis
const analysis = await getManuscriptAnalysis(manuscriptId, session.user.id)
if (!analysis) {
return NextResponse.json(
{ error: 'Manuscript analysis required' },
{ status: 400 }
)
}
// Get author profile
const authorProfile = await getAuthorProfile(session.user.id)
// Find matches
const matchingEngine = new CoachMatchingEngine()
const matches = await matchingEngine.findOptimalMatches({
manuscriptId,
userId: session.user.id,
analysis,
preferences: {
...authorProfile.preferences,
...preferences
},
requirements: {
...requirements,
urgency
}
})
// Add success predictions
const predictor = new SuccessPredictor()
const matchesWithPredictions = await Promise.all(
matches.matches.map(async match => ({
...match,
prediction: await predictor.predictMatchSuccess(
match,
authorProfile,
analysis
)
}))
)
// Track matching event
await trackEvent({
userId: session.user.id,
event: 'coach_matching_completed',
properties: {
manuscriptId,
matchCount: matchesWithPredictions.length,
topMatchScore: matchesWithPredictions[0]?.score.overall,
urgency
}
})
return NextResponse.json({
matches: matchesWithPredictions,
alternativeOptions: matches.alternativeOptions,
insights: matches.matchingInsights
})
} catch (error) {
console.error('Coach matching failed:', error)
return NextResponse.json(
{ error: 'Failed to find coach matches' },
{ status: 500 }
)
}
}
// Re-match endpoint for when initial match doesn't work out
export async function PUT(req: NextRequest) {
try {
const session = await withAuth(req)
if (!session) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
}
const {
manuscriptId,
previousMatchId,
feedback,
newPreferences
} = await req.json()
// Learn from feedback
await updateMatchingPreferences(session.user.id, previousMatchId, feedback)
// Get updated analysis and preferences
const analysis = await getManuscriptAnalysis(manuscriptId, session.user.id)
const updatedPreferences = await mergePreferences(
session.user.id,
newPreferences,
feedback
)
// Find new matches excluding previous
const matchingEngine = new CoachMatchingEngine()
const matches = await matchingEngine.findOptimalMatches({
manuscriptId,
userId: session.user.id,
analysis,
preferences: updatedPreferences,
requirements: {
excludeCoaches: [previousMatchId],
learningFromFeedback: feedback
}
})
return NextResponse.json({
matches: matches.matches,
adjustmentsApplied: feedback.reasons
})
} catch (error) {
console.error('Re-matching failed:', error)
return NextResponse.json(
{ error: 'Failed to find new matches' },
{ status: 500 }
)
}
}5. Coach Performance Tracking
// packages/coach-matching/src/services/performance-tracker.ts
export class CoachPerformanceTracker {
async updateCoachMetrics(
coachId: string,
projectId: string,
outcome: ProjectOutcome
): Promise<void> {
// Calculate improvement metrics
const improvementScore = this.calculateImprovement(
outcome.beforeScores,
outcome.afterScores
)
// Update coach statistics
await this.updateCoachStats(coachId, {
projectsCompleted: 1,
avgImprovement: improvementScore,
clientSatisfaction: outcome.clientFeedback.satisfaction,
onTimeDelivery: outcome.deliveredOnTime,
genrePerformance: {
[outcome.genre]: improvementScore
}
})
// Update matching algorithm weights
await this.updateMatchingWeights(coachId, outcome)
// Generate performance insights
const insights = await this.generatePerformanceInsights(
coachId,
projectId,
outcome
)
// Notify coach of performance update
await this.notifyCoachOfPerformance(coachId, insights)
}
private async updateMatchingWeights(
coachId: string,
outcome: ProjectOutcome
): Promise<void> {
// Identify successful patterns
const successPatterns = this.identifySuccessPatterns(outcome)
// Update coach expertise weights
if (outcome.success) {
await this.reinforceExpertiseAreas(coachId, successPatterns)
} else {
await this.adjustExpertiseWeights(coachId, outcome.challenges)
}
// Update ideal client profile
await this.refineIdealClientProfile(coachId, outcome)
}
private identifySuccessPatterns(outcome: ProjectOutcome): SuccessPattern[] {
const patterns: SuccessPattern[] = []
// Analyze which areas improved most
const improvements = Object.entries(outcome.afterScores).map(
([dimension, afterScore]) => ({
dimension,
improvement: afterScore - (outcome.beforeScores[dimension] || 0)
})
).sort((a, b) => b.improvement - a.improvement)
// Top improvements indicate coach strengths
patterns.push(...improvements.slice(0, 3).map(imp => ({
type: 'dimension-improvement',
dimension: imp.dimension,
strength: imp.improvement
})))
// Analyze successful techniques
if (outcome.techniquesUsed) {
patterns.push(...outcome.techniquesUsed
.filter(t => t.effectiveness > 0.8)
.map(t => ({
type: 'technique-success',
technique: t.name,
strength: t.effectiveness
}))
)
}
return patterns
}
}Database Schema
-- Enhanced coach profiles
CREATE TABLE coaches.enhanced_profiles (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
coach_id UUID REFERENCES coaches.profiles(id),
style_profile JSONB,
expertise_depth JSONB,
success_metrics JSONB,
philosophy_embedding vector(1536),
strength_areas TEXT[],
ideal_client_profile JSONB,
coaching_patterns JSONB,
last_updated TIMESTAMP DEFAULT NOW(),
INDEX idx_enhanced_coach (coach_id),
INDEX idx_philosophy_embedding USING ivfflat (philosophy_embedding vector_cosine_ops)
);
-- Coach-manuscript matches
CREATE TABLE coaches.matches (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
manuscript_id UUID REFERENCES analyzer.manuscripts(id),
author_id UUID REFERENCES auth.users(id),
coach_id UUID REFERENCES coaches.profiles(id),
compatibility_score JSONB,
match_reasons JSONB,
success_prediction JSONB,
status VARCHAR(50) DEFAULT 'proposed',
created_at TIMESTAMP DEFAULT NOW(),
INDEX idx_matches_manuscript (manuscript_id),
INDEX idx_matches_author (author_id),
INDEX idx_matches_coach (coach_id),
INDEX idx_matches_score ((compatibility_score->>'overall')::numeric DESC)
);
-- Match feedback and learning
CREATE TABLE coaches.match_feedback (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
match_id UUID REFERENCES coaches.matches(id),
author_id UUID REFERENCES auth.users(id),
feedback_type VARCHAR(50),
satisfaction_score INTEGER CHECK (satisfaction_score BETWEEN 1 AND 5),
reasons JSONB,
outcome JSONB,
created_at TIMESTAMP DEFAULT NOW(),
INDEX idx_feedback_match (match_id)
);
-- Coach performance metrics
CREATE TABLE coaches.performance_metrics (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
coach_id UUID REFERENCES coaches.profiles(id),
period_start DATE,
period_end DATE,
projects_completed INTEGER,
avg_improvement DECIMAL(3,2),
client_satisfaction DECIMAL(3,2),
on_time_delivery DECIMAL(3,2),
genre_performance JSONB,
success_patterns JSONB,
UNIQUE(coach_id, period_start, period_end),
INDEX idx_performance_coach (coach_id)
);
-- Success prediction model data
CREATE TABLE coaches.match_outcomes (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
match_id UUID REFERENCES coaches.matches(id),
project_id UUID,
before_scores JSONB,
after_scores JSONB,
improvement_metrics JSONB,
client_feedback JSONB,
coach_feedback JSONB,
success BOOLEAN,
completed_at TIMESTAMP,
INDEX idx_outcomes_match (match_id),
INDEX idx_outcomes_success (success)
);MVP Acceptance Criteria
- AI-powered coach matching algorithm
- Multi-factor compatibility scoring
- Enhanced coach profiling
- Success prediction model
- Dynamic re-matching system
- Performance tracking
- API endpoints for matching
- Feedback learning system
- Alternative options generation
Post-MVP Enhancements
- Real-time coach availability updates
- Group coaching match optimization
- Personality assessment integration
- Video introduction matching
- Trial session scheduling
- Multi-coach team matching
- Mentorship program matching
- AI-powered chemistry prediction
Implementation Time
- Matching Engine: 1.5 days
- Coach Profiler: 1 day
- Success Predictor: 1 day
- API Integration: 0.5 days
- Performance Tracking: 0.5 days
- Testing: 1 day
- Total: 5.5 days
Dependencies
- F007 - Manuscript Analysis (for matching criteria)
- F009 - Scoring System (for quality metrics)
- F010 - Recommendations (for coaching focus areas)
- Coach profiles and availability data
Next Feature
After completion, proceed to F012-ADVANCED-ANALYTICS for deep analytical insights.