@@ -14,7 +14,90 @@ import * as Types from '../types';
1414// EXPLANATION GENERATOR
1515// ============================================================================
1616
17+ export interface ExplanationStrategy {
18+ matches ( dominantSignal : string ) : boolean ;
19+ extract (
20+ rankingSignal : any ,
21+ userProfile : Types . UserProfile ,
22+ similarUsers ?: string [ ]
23+ ) : {
24+ primaryReason : string ;
25+ supportingSignals : string [ ] ;
26+ featureAttribution : Array < { feature : string ; importance : number ; contribution : string } > ;
27+ } ;
28+ }
29+
30+ export class CollaborativeSignalStrategy implements ExplanationStrategy {
31+ matches ( signal : string ) : boolean { return signal === 'collaborativeSignal' ; }
32+ extract ( rankingSignal : any , _userProfile : Types . UserProfile , similarUsers ?: string [ ] ) {
33+ const result = { primaryReason : '' , supportingSignals : [ ] as string [ ] , featureAttribution : [ ] as any [ ] } ;
34+ if ( similarUsers && similarUsers . length > 0 ) {
35+ result . primaryReason = `Users like you enjoyed this content` ;
36+ result . supportingSignals . push ( `Liked by ${ similarUsers . length } similar learners` ) ;
37+ result . featureAttribution . push ( {
38+ feature : 'user_similarity' ,
39+ importance : rankingSignal . collaborativeSignal ,
40+ contribution : `Based on similar learning patterns` ,
41+ } ) ;
42+ }
43+ return result ;
44+ }
45+ }
46+
47+ export class ContentSignalStrategy implements ExplanationStrategy {
48+ matches ( signal : string ) : boolean { return signal === 'contentSignal' ; }
49+ extract ( rankingSignal : any , userProfile : Types . UserProfile ) {
50+ const result = { primaryReason : '' , supportingSignals : [ ] as string [ ] , featureAttribution : [ ] as any [ ] } ;
51+ result . primaryReason = `Matches your interests` ;
52+ const topics = Array . from ( userProfile . features . topicAffinities . keys ( ) ) . slice ( 0 , 2 ) ;
53+ result . supportingSignals . push ( `Related to your interest in ${ topics . join ( ' and ' ) } ` ) ;
54+ result . featureAttribution . push ( {
55+ feature : 'topic_match' ,
56+ importance : rankingSignal . contentSignal ,
57+ contribution : `Content topic alignment with your profile` ,
58+ } ) ;
59+ return result ;
60+ }
61+ }
62+
63+ export class LearningPathSignalStrategy implements ExplanationStrategy {
64+ matches ( signal : string ) : boolean { return signal === 'learningPathSignal' ; }
65+ extract ( rankingSignal : any ) {
66+ return {
67+ primaryReason : `Recommended based on your learning path` ,
68+ supportingSignals : [ `Prerequisite for your next goal` ] ,
69+ featureAttribution : [ {
70+ feature : 'learning_path_fit' ,
71+ importance : rankingSignal . learningPathSignal ,
72+ contribution : `Aligns with recommended progression` ,
73+ } ]
74+ } ;
75+ }
76+ }
77+
78+ export class QualitySignalStrategy implements ExplanationStrategy {
79+ matches ( signal : string ) : boolean { return signal === 'qualitySignal' ; }
80+ extract ( rankingSignal : any ) {
81+ return {
82+ primaryReason : `High-quality content` ,
83+ supportingSignals : [ `Highly rated by other learners` ] ,
84+ featureAttribution : [ {
85+ feature : 'content_quality' ,
86+ importance : rankingSignal . qualitySignal ,
87+ contribution : `Strong engagement and completion metrics` ,
88+ } ]
89+ } ;
90+ }
91+ }
92+
1793export class ExplanationGenerator {
94+ private strategies : ExplanationStrategy [ ] = [
95+ new CollaborativeSignalStrategy ( ) ,
96+ new ContentSignalStrategy ( ) ,
97+ new LearningPathSignalStrategy ( ) ,
98+ new QualitySignalStrategy ( ) ,
99+ ] ;
100+
18101 /**
19102 * Generate explanation for a recommendation
20103 */
@@ -82,54 +165,11 @@ export class ExplanationGenerator {
82165 supportingSignals : string [ ] ;
83166 featureAttribution : Array < { feature : string ; importance : number ; contribution : string } > ;
84167 } {
85- const result = {
86- primaryReason : '' ,
87- supportingSignals : [ ] as string [ ] ,
88- featureAttribution : [ ] as Array < { feature : string ; importance : number ; contribution : string } > ,
89- } ;
90-
91- switch ( dominantSignal ) {
92- case 'collaborativeSignal' :
93- if ( ! similarUsers || similarUsers . length === 0 ) break ; // Guard clause
94- result . primaryReason = `Users like you enjoyed this content` ;
95- result . supportingSignals . push ( `Liked by ${ similarUsers . length } similar learners` ) ;
96- result . featureAttribution . push ( {
97- feature : 'user_similarity' ,
98- importance : rankingSignal . collaborativeSignal ,
99- contribution : `Based on similar learning patterns` ,
100- } ) ;
101- break ;
102- case 'contentSignal' :
103- result . primaryReason = `Matches your interests` ;
104- const topics = Array . from ( userProfile . features . topicAffinities . keys ( ) ) . slice ( 0 , 2 ) ;
105- result . supportingSignals . push ( `Related to your interest in ${ topics . join ( ' and ' ) } ` ) ;
106- result . featureAttribution . push ( {
107- feature : 'topic_match' ,
108- importance : rankingSignal . contentSignal ,
109- contribution : `Content topic alignment with your profile` ,
110- } ) ;
111- break ;
112- case 'learningPathSignal' :
113- result . primaryReason = `Recommended based on your learning path` ;
114- result . supportingSignals . push ( `Prerequisite for your next goal` ) ;
115- result . featureAttribution . push ( {
116- feature : 'learning_path_fit' ,
117- importance : rankingSignal . learningPathSignal ,
118- contribution : `Aligns with recommended progression` ,
119- } ) ;
120- break ;
121- case 'qualitySignal' :
122- result . primaryReason = `High-quality content` ;
123- result . supportingSignals . push ( `Highly rated by other learners` ) ;
124- result . featureAttribution . push ( {
125- feature : 'content_quality' ,
126- importance : rankingSignal . qualitySignal ,
127- contribution : `Strong engagement and completion metrics` ,
128- } ) ;
129- break ;
168+ const strategy = this . strategies . find ( s => s . matches ( dominantSignal ) ) ;
169+ if ( strategy ) {
170+ return strategy . extract ( rankingSignal , userProfile , similarUsers ) ;
130171 }
131-
132- return result ;
172+ return { primaryReason : '' , supportingSignals : [ ] , featureAttribution : [ ] } ;
133173 }
134174
135175 /**
0 commit comments