@@ -201,14 +201,22 @@ def perform_sync_from_riot
201201 league_data = riot_service . get_league_entries_by_puuid ( puuid : @target . riot_puuid , region : region )
202202 mastery_data = riot_service . get_champion_mastery ( puuid : @target . riot_puuid , region : region )
203203
204+ pool = extract_champion_pool ( mastery_data )
205+ perf = @target . recent_performance || { }
206+ tier = league_data [ :solo_queue ] &.dig ( :tier ) || @target . current_tier
207+ strengths = derive_strengths ( perf , pool , @target . role , tier )
208+ weaknesses = derive_weaknesses ( perf , pool , @target . role , tier )
209+
204210 @target . update! (
205211 # riot_summoner_id is no longer returned by Riot API
206212 summoner_name : "#{ account_data [ :game_name ] } ##{ account_data [ :tag_line ] } " ,
207213 current_tier : league_data [ :solo_queue ] &.dig ( :tier ) ,
208214 current_rank : league_data [ :solo_queue ] &.dig ( :rank ) ,
209215 current_lp : league_data [ :solo_queue ] &.dig ( :lp ) ,
210- champion_pool : extract_champion_pool ( mastery_data ) ,
211- performance_trend : calculate_performance_trend ( league_data )
216+ champion_pool : pool ,
217+ performance_trend : calculate_performance_trend ( league_data ) ,
218+ strengths : strengths ,
219+ weaknesses : weaknesses
212220 )
213221
214222 watchlist = @target . scouting_watchlists . find_by ( organization : current_organization )
@@ -240,7 +248,11 @@ def apply_filters(targets)
240248 end
241249
242250 def apply_basic_filters ( targets )
243- targets = targets . by_role ( params [ :role ] ) if params [ :role ] . present?
251+ # role param is comma-separated lowercase: "mid,top" → ["mid", "top"]
252+ if params [ :role ] . present?
253+ roles = params [ :role ] . split ( ',' ) . map ( &:strip ) . reject ( &:blank? )
254+ targets = targets . by_role ( roles ) if roles . any?
255+ end
244256 targets = targets . by_status ( params [ :status ] ) if params [ :status ] . present?
245257 targets = targets . by_region ( params [ :region ] ) if params [ :region ] . present?
246258
@@ -256,18 +268,19 @@ def apply_basic_filters(targets)
256268 end
257269
258270 def apply_age_range_filter ( targets )
259- return targets unless params [ :age_range ] . present? && params [ :age_range ] . is_a? ( Array )
271+ min_age = params [ :age_min ] . presence &.to_i
272+ max_age = params [ :age_max ] . presence &.to_i
273+ return targets unless min_age && max_age
260274
261- min_age , max_age = params [ :age_range ]
262- min_age && max_age ? targets . where ( age : min_age ..max_age ) : targets
275+ targets . where ( age : min_age ..max_age )
263276 end
264277
265278 def apply_rank_range_filter ( targets )
266- return targets unless params [ :rank_range ] . present?
279+ min_lp = params [ :lp_min ] . presence &.to_i
280+ max_lp = params [ :lp_max ] . presence &.to_i
281+ return targets unless min_lp && max_lp
267282
268- # Rank range filtering by LP
269- min_lp , max_lp = params [ :rank_range ]
270- min_lp && max_lp ? targets . where ( current_lp : min_lp ..max_lp ) : targets
283+ targets . where ( current_lp : min_lp ..max_lp )
271284 end
272285
273286 def apply_search_filter ( targets )
@@ -367,33 +380,69 @@ def target_params
367380 )
368381 end
369382
370- # Extract top champions from mastery data
383+ # Thresholds calibrated by tier. Mirrors RosterManagementService#tier_thresholds.
384+ # JSONB from DB returns string keys, so we use with_indifferent_access throughout.
385+ def tier_thresholds ( tier )
386+ case tier &.upcase
387+ when 'CHALLENGER' , 'GRANDMASTER' , 'MASTER'
388+ { wr_strength : 53 , wr_weakness : 49 , kda_strength : 4.5 , kda_weakness : 3.0 ,
389+ cs_strength : 9.0 , cs_weakness : 7.5 , vision_strength : 45 , vision_weakness : 28 }
390+ when 'DIAMOND' , 'EMERALD'
391+ { wr_strength : 54 , wr_weakness : 47 , kda_strength : 4.0 , kda_weakness : 2.5 ,
392+ cs_strength : 8.5 , cs_weakness : 7.0 , vision_strength : 42 , vision_weakness : 24 }
393+ else
394+ { wr_strength : 55 , wr_weakness : 45 , kda_strength : 3.5 , kda_weakness : 2.0 ,
395+ cs_strength : 8.0 , cs_weakness : 6.0 , vision_strength : 40 , vision_weakness : 20 }
396+ end
397+ end
398+
399+ def derive_strengths ( perf , pool , role , tier = nil )
400+ return [ ] if perf . blank?
401+
402+ p = perf . with_indifferent_access
403+ t = tier_thresholds ( tier )
404+ strengths = [ ]
405+ strengths << 'Consistency' if p [ :win_rate ] . to_f >= t [ :wr_strength ]
406+ strengths << 'Mechanical skill' if p [ :avg_kda ] . to_f >= t [ :kda_strength ]
407+ strengths << 'CS discipline' if non_support? ( role ) && p [ :avg_cs_per_min ] . to_f >= t [ :cs_strength ]
408+ strengths << 'Map awareness' if vision_role? ( role ) && p [ :avg_vision_score ] . to_f >= t [ :vision_strength ]
409+ strengths << 'Team fighting' if p [ :avg_kill_participation ] . to_f >= 65.0
410+ strengths << 'Champion pool depth' if pool . size >= 6
411+ strengths
412+ end
413+
414+ def derive_weaknesses ( perf , pool , role , tier = nil )
415+ return [ ] if perf . blank?
416+
417+ p = perf . with_indifferent_access
418+ t = tier_thresholds ( tier )
419+ weaknesses = [ ]
420+ weaknesses << 'Inconsistent performance' if p [ :games_played ] . to_i >= 10 && p [ :win_rate ] . to_f < t [ :wr_weakness ]
421+ weaknesses << 'Death management' if p [ :avg_kda ] . to_f . positive? && p [ :avg_kda ] . to_f < t [ :kda_weakness ]
422+ weaknesses << 'CS discipline' if non_support? ( role ) && p [ :avg_cs_per_min ] . to_f . positive? && p [ :avg_cs_per_min ] . to_f < t [ :cs_weakness ]
423+ weaknesses << 'Vision control' if vision_role? ( role ) && p [ :avg_vision_score ] . to_f . positive? && p [ :avg_vision_score ] . to_f < t [ :vision_weakness ]
424+ weaknesses << 'Limited champion pool' if pool . size < 3
425+ weaknesses
426+ end
427+
428+ def non_support? ( role )
429+ role . to_s != 'support'
430+ end
431+
432+ def vision_role? ( role )
433+ %w[ support jungle ] . include? ( role . to_s )
434+ end
435+
436+ # Extract top champions from mastery data using DataDragonService for full champion coverage.
437+ # Falls back to "Champion_<id>" only when Data Dragon is unreachable.
371438 def extract_champion_pool ( mastery_data )
372439 return [ ] if mastery_data . blank?
373440
374- # Get top 10 champions by mastery points
375- mastery_data . first ( 10 ) . map do |mastery |
376- champion_id_to_name ( mastery [ :champion_id ] )
377- end . compact
378- end
379-
380- # Simple champion ID to name mapping (top champions)
381- def champion_id_to_name ( champion_id )
382- # This is a simplified mapping - in production you'd want a complete mapping
383- # or fetch from Data Dragon API
384- champion_map = {
385- 1 => 'Annie' , 2 => 'Olaf' , 3 => 'Galio' , 4 => 'Twisted Fate' ,
386- 5 => 'Xin Zhao' , 6 => 'Urgot' , 7 => 'LeBlanc' , 8 => 'Vladimir' ,
387- 9 => 'Fiddlesticks' , 10 => 'Kayle' , 11 => 'Master Yi' , 12 => 'Alistar' ,
388- 13 => 'Ryze' , 14 => 'Sion' , 15 => 'Sivir' , 16 => 'Soraka' ,
389- 17 => 'Teemo' , 18 => 'Tristana' , 19 => 'Warwick' , 20 => 'Nunu' ,
390- 21 => 'Miss Fortune' , 22 => 'Ashe' , 23 => 'Tryndamere' , 24 => 'Jax' ,
391- 25 => 'Morgana' , 26 => 'Zilean' , 27 => 'Singed' , 28 => 'Evelynn' ,
392- 29 => 'Twitch' , 30 => 'Karthus' , 31 => 'Cho\'Gath' , 32 => 'Amumu' ,
393- 33 => 'Rammus' , 34 => 'Anivia' , 35 => 'Shaco' , 36 => 'Dr. Mundo'
394- # Add more as needed or fetch from Data Dragon
395- }
396- champion_map [ champion_id ] || "Champion_#{ champion_id } "
441+ id_map = DataDragonService . new . champion_id_map
442+
443+ mastery_data . first ( 10 ) . filter_map do |mastery |
444+ id_map [ mastery [ :champion_id ] . to_i ]
445+ end
397446 end
398447
399448 # Calculate performance trend based on win/loss ratio
0 commit comments