2.0.0 Alpha: Data Refinery
This commit is contained in:
@@ -65,8 +65,8 @@ class CompositeProcessor(BaseFeatureProcessor):
|
||||
# Classify tier based on overall score
|
||||
features['tier_classification'] = CompositeProcessor._classify_tier(features['score_overall'])
|
||||
|
||||
# Percentile rank (placeholder - requires all players)
|
||||
features['tier_percentile'] = min(features['score_overall'], 100.0)
|
||||
# Filled by L3_Builder after every eligible player has been calculated.
|
||||
features['tier_percentile'] = None
|
||||
|
||||
return features
|
||||
|
||||
@@ -266,13 +266,13 @@ class CompositeProcessor(BaseFeatureProcessor):
|
||||
STABILITY Score (0-100) | 8%
|
||||
"""
|
||||
# Extract features
|
||||
volatility = features.get('meta_rating_volatility', 0.0)
|
||||
loss_rating = features.get('meta_loss_rating', 0.0)
|
||||
consistency = features.get('meta_rating_consistency', 0.0)
|
||||
tilt_resilience = features.get('int_pressure_tilt_resistance', 0.0)
|
||||
map_stable = features.get('meta_map_stability', 0.0)
|
||||
elo_stable = features.get('meta_elo_tier_stability', 0.0)
|
||||
recent_form = features.get('meta_recent_form_rating', 0.0)
|
||||
volatility = features.get('meta_rating_volatility') or 0.0
|
||||
loss_rating = features.get('meta_loss_rating') or 0.0
|
||||
consistency = features.get('meta_rating_consistency') or 0.0
|
||||
tilt_resilience = features.get('int_pressure_tilt_resistance') or 0.0
|
||||
map_stable = features.get('meta_map_stability') or 0.0
|
||||
elo_stable = features.get('meta_elo_tier_stability') or 0.0
|
||||
recent_form = features.get('meta_recent_form_rating') or 0.0
|
||||
|
||||
# Normalize
|
||||
# Volatility: Reverse score. 100 - (Vol * 220)
|
||||
@@ -281,8 +281,8 @@ class CompositeProcessor(BaseFeatureProcessor):
|
||||
loss_score = min((loss_rating / 1.00) * 100, 100)
|
||||
cons_score = min((consistency / 70) * 100, 100)
|
||||
tilt_score = min((tilt_resilience / 0.80) * 100, 100)
|
||||
map_score = min((map_stable / 0.25) * 100, 100)
|
||||
elo_score = min((elo_stable / 0.48) * 100, 100)
|
||||
map_score = max(0, min(100, 100 - (map_stable / 0.25) * 100))
|
||||
elo_score = max(0, min(100, 100 - (elo_stable / 0.48) * 100))
|
||||
recent_score = min((recent_form / 1.15) * 100, 100)
|
||||
|
||||
# Weighted Sum
|
||||
@@ -337,12 +337,12 @@ class CompositeProcessor(BaseFeatureProcessor):
|
||||
PACE Score (0-100) | 5%
|
||||
"""
|
||||
# Extract features
|
||||
early_kill_pct = features.get('int_timing_early_kill_share', 0.0)
|
||||
aggression = features.get('int_timing_aggression_index', 0.0)
|
||||
trade_speed = features.get('int_trade_response_time', 0.0)
|
||||
trade_kill = features.get('int_trade_kill_count', 0)
|
||||
teamwork = features.get('int_teamwork_score', 0.0)
|
||||
first_contact = features.get('int_timing_first_contact_time', 0.0)
|
||||
early_kill_pct = features.get('int_timing_early_kill_share') or 0.0
|
||||
aggression = features.get('int_timing_aggression_index') or 0.0
|
||||
trade_speed = features.get('int_trade_response_time') or 0.0
|
||||
trade_kill = features.get('int_trade_kill_count') or 0
|
||||
teamwork = features.get('int_teamwork_score') or 0.0
|
||||
first_contact = features.get('int_timing_first_contact_time') or 0.0
|
||||
|
||||
# Normalize
|
||||
early_score = min((early_kill_pct / 0.44) * 100, 100)
|
||||
@@ -353,7 +353,7 @@ class CompositeProcessor(BaseFeatureProcessor):
|
||||
if trade_speed > 0.01:
|
||||
trade_speed_score = min((2.0 / trade_speed) * 100, 100)
|
||||
else:
|
||||
trade_speed_score = 100 # Instant trade
|
||||
trade_speed_score = 0
|
||||
|
||||
trade_kill_score = min((trade_kill / 650) * 100, 100)
|
||||
teamwork_score = min((teamwork / 29) * 100, 100)
|
||||
@@ -362,13 +362,7 @@ class CompositeProcessor(BaseFeatureProcessor):
|
||||
if first_contact > 0.01:
|
||||
first_contact_score = min((30 / first_contact) * 100, 100)
|
||||
else:
|
||||
first_contact_score = 0 # If 0, probably no data, safe to say 0? Or 100?
|
||||
# 0 first contact time means instant damage.
|
||||
# But "30 / Contact" means smaller contact time gives higher score.
|
||||
# If contact time is 0, score explodes.
|
||||
# Realistically first contact time is > 0.
|
||||
# I will clamp it.
|
||||
first_contact_score = 100 # Assume very fast
|
||||
first_contact_score = 0
|
||||
|
||||
# Weighted Sum
|
||||
pace_score = (
|
||||
@@ -416,5 +410,5 @@ def _get_default_composite_features() -> Dict[str, Any]:
|
||||
'score_pace': 0.0,
|
||||
'score_overall': 0.0,
|
||||
'tier_classification': 'Beginner',
|
||||
'tier_percentile': 0.0,
|
||||
'tier_percentile': None,
|
||||
}
|
||||
|
||||
@@ -466,7 +466,8 @@ class IntelligenceProcessor(BaseFeatureProcessor):
|
||||
- int_pos_spatial_iq_score
|
||||
- int_pos_avg_distance_from_teammates
|
||||
|
||||
Note: Simplified implementation - full version requires DBSCAN clustering
|
||||
Only geometry-independent values are calculated here. Metrics that
|
||||
require map boundaries, paths or teammate positions remain NULL.
|
||||
"""
|
||||
cursor = conn_l2.cursor()
|
||||
|
||||
@@ -481,26 +482,23 @@ class IntelligenceProcessor(BaseFeatureProcessor):
|
||||
has_position_data = cursor.fetchone()[0] > 0
|
||||
|
||||
if not has_position_data:
|
||||
# Return placeholder values if no position data
|
||||
return {
|
||||
'int_pos_site_a_control_rate': 0.0,
|
||||
'int_pos_site_b_control_rate': 0.0,
|
||||
'int_pos_mid_control_rate': 0.0,
|
||||
'int_pos_favorite_position': 'unknown',
|
||||
'int_pos_position_diversity': 0.0,
|
||||
'int_pos_rotation_speed': 0.0,
|
||||
'int_pos_map_coverage': 0.0,
|
||||
'int_pos_lurk_tendency': 0.0,
|
||||
'int_pos_site_anchor_score': 0.0,
|
||||
'int_pos_entry_route_diversity': 0.0,
|
||||
'int_pos_retake_positioning': 0.0,
|
||||
'int_pos_postplant_positioning': 0.0,
|
||||
'int_pos_spatial_iq_score': 0.0,
|
||||
'int_pos_avg_distance_from_teammates': 0.0,
|
||||
'int_pos_site_a_control_rate': None,
|
||||
'int_pos_site_b_control_rate': None,
|
||||
'int_pos_mid_control_rate': None,
|
||||
'int_pos_favorite_position': None,
|
||||
'int_pos_position_diversity': None,
|
||||
'int_pos_rotation_speed': None,
|
||||
'int_pos_map_coverage': None,
|
||||
'int_pos_lurk_tendency': None,
|
||||
'int_pos_site_anchor_score': None,
|
||||
'int_pos_entry_route_diversity': None,
|
||||
'int_pos_retake_positioning': None,
|
||||
'int_pos_postplant_positioning': None,
|
||||
'int_pos_spatial_iq_score': None,
|
||||
'int_pos_avg_distance_from_teammates': None,
|
||||
}
|
||||
|
||||
# Simplified position analysis (proper implementation needs clustering)
|
||||
# Calculate basic position variance as proxy for mobility
|
||||
cursor.execute("""
|
||||
SELECT
|
||||
AVG(attacker_pos_x) as avg_x,
|
||||
@@ -515,34 +513,24 @@ class IntelligenceProcessor(BaseFeatureProcessor):
|
||||
pos_row = cursor.fetchone()
|
||||
position_count = pos_row[3] if pos_row[3] else 1
|
||||
|
||||
# Position diversity based on unique grid cells visited
|
||||
position_diversity = min(position_count / 50.0, 1.0) # Normalize to 0-1
|
||||
|
||||
# Map coverage (simplified)
|
||||
map_coverage = position_diversity
|
||||
|
||||
# Site control rates CANNOT be calculated without map-specific geometry data
|
||||
# Each map (Dust2, Mirage, Nuke, etc.) has different site boundaries
|
||||
# Would require: CREATE TABLE map_boundaries (map_name, site_name, min_x, max_x, min_y, max_y)
|
||||
# Commenting out these 3 features:
|
||||
# - int_pos_site_a_control_rate
|
||||
# - int_pos_site_b_control_rate
|
||||
# - int_pos_mid_control_rate
|
||||
|
||||
return {
|
||||
'int_pos_site_a_control_rate': 0.33, # Placeholder
|
||||
'int_pos_site_b_control_rate': 0.33, # Placeholder
|
||||
'int_pos_mid_control_rate': 0.34, # Placeholder
|
||||
'int_pos_favorite_position': 'mid',
|
||||
'int_pos_site_a_control_rate': None,
|
||||
'int_pos_site_b_control_rate': None,
|
||||
'int_pos_mid_control_rate': None,
|
||||
'int_pos_favorite_position': None,
|
||||
'int_pos_position_diversity': round(position_diversity, 3),
|
||||
'int_pos_rotation_speed': 50.0,
|
||||
'int_pos_rotation_speed': None,
|
||||
'int_pos_map_coverage': round(map_coverage, 3),
|
||||
'int_pos_lurk_tendency': 0.25,
|
||||
'int_pos_site_anchor_score': 50.0,
|
||||
'int_pos_lurk_tendency': None,
|
||||
'int_pos_site_anchor_score': None,
|
||||
'int_pos_entry_route_diversity': round(position_diversity, 3),
|
||||
'int_pos_retake_positioning': 50.0,
|
||||
'int_pos_postplant_positioning': 50.0,
|
||||
'int_pos_retake_positioning': None,
|
||||
'int_pos_postplant_positioning': None,
|
||||
'int_pos_spatial_iq_score': round(position_diversity * 100, 2),
|
||||
'int_pos_avg_distance_from_teammates': 500.0,
|
||||
'int_pos_avg_distance_from_teammates': None,
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
@@ -706,20 +694,20 @@ def _get_default_intelligence_features() -> Dict[str, Any]:
|
||||
'int_pressure_big_moment_score': 0.0,
|
||||
'int_pressure_tilt_resistance': 0.0,
|
||||
# Position Mastery (14)
|
||||
'int_pos_site_a_control_rate': 0.0,
|
||||
'int_pos_site_b_control_rate': 0.0,
|
||||
'int_pos_mid_control_rate': 0.0,
|
||||
'int_pos_favorite_position': 'unknown',
|
||||
'int_pos_position_diversity': 0.0,
|
||||
'int_pos_rotation_speed': 0.0,
|
||||
'int_pos_map_coverage': 0.0,
|
||||
'int_pos_lurk_tendency': 0.0,
|
||||
'int_pos_site_anchor_score': 0.0,
|
||||
'int_pos_entry_route_diversity': 0.0,
|
||||
'int_pos_retake_positioning': 0.0,
|
||||
'int_pos_postplant_positioning': 0.0,
|
||||
'int_pos_spatial_iq_score': 0.0,
|
||||
'int_pos_avg_distance_from_teammates': 0.0,
|
||||
'int_pos_site_a_control_rate': None,
|
||||
'int_pos_site_b_control_rate': None,
|
||||
'int_pos_mid_control_rate': None,
|
||||
'int_pos_favorite_position': None,
|
||||
'int_pos_position_diversity': None,
|
||||
'int_pos_rotation_speed': None,
|
||||
'int_pos_map_coverage': None,
|
||||
'int_pos_lurk_tendency': None,
|
||||
'int_pos_site_anchor_score': None,
|
||||
'int_pos_entry_route_diversity': None,
|
||||
'int_pos_retake_positioning': None,
|
||||
'int_pos_postplant_positioning': None,
|
||||
'int_pos_spatial_iq_score': None,
|
||||
'int_pos_avg_distance_from_teammates': None,
|
||||
# Trade Network (8)
|
||||
'int_trade_kill_count': 0,
|
||||
'int_trade_kill_rate': 0.0,
|
||||
|
||||
@@ -60,10 +60,11 @@ class MetaProcessor(BaseFeatureProcessor):
|
||||
|
||||
# Get recent matches for volatility
|
||||
cursor.execute("""
|
||||
SELECT rating
|
||||
FROM fact_match_players
|
||||
WHERE steam_id_64 = ?
|
||||
ORDER BY match_id DESC
|
||||
SELECT p.rating
|
||||
FROM fact_match_players p
|
||||
JOIN fact_matches m ON m.match_id = p.match_id
|
||||
WHERE p.steam_id_64 = ?
|
||||
ORDER BY m.start_time DESC, p.match_id DESC
|
||||
LIMIT 20
|
||||
""", (steam_id,))
|
||||
|
||||
@@ -141,8 +142,35 @@ class MetaProcessor(BaseFeatureProcessor):
|
||||
map_ratings = [row[1] for row in cursor.fetchall() if row[1] is not None]
|
||||
map_stability = SafeAggregator.safe_stddev(map_ratings, 0.0)
|
||||
|
||||
# ELO tier stability (placeholder)
|
||||
elo_tier_stability = rating_volatility # Simplified
|
||||
cursor.execute("""
|
||||
SELECT
|
||||
CASE
|
||||
WHEN p.origin_elo - opponent.avg_elo > 200 THEN 'lower'
|
||||
WHEN p.origin_elo - opponent.avg_elo < -200 THEN 'higher'
|
||||
ELSE 'similar'
|
||||
END AS opponent_tier,
|
||||
AVG(p.rating) AS avg_rating
|
||||
FROM fact_match_players p
|
||||
JOIN (
|
||||
SELECT match_id, team_id, AVG(origin_elo) AS avg_elo
|
||||
FROM fact_match_players
|
||||
WHERE origin_elo IS NOT NULL
|
||||
GROUP BY match_id, team_id
|
||||
) opponent
|
||||
ON opponent.match_id = p.match_id
|
||||
AND opponent.team_id != p.team_id
|
||||
WHERE p.steam_id_64 = ?
|
||||
AND p.origin_elo IS NOT NULL
|
||||
AND p.rating IS NOT NULL
|
||||
GROUP BY opponent_tier
|
||||
""", (steam_id,))
|
||||
elo_tier_ratings = [
|
||||
row[1] for row in cursor.fetchall() if row[1] is not None
|
||||
]
|
||||
elo_tier_stability = SafeAggregator.safe_stddev(
|
||||
elo_tier_ratings,
|
||||
0.0,
|
||||
)
|
||||
|
||||
return {
|
||||
'meta_rating_volatility': round(rating_volatility, 3),
|
||||
|
||||
Reference in New Issue
Block a user