Skip to main content
Glama

Path of Exile 2 Build Optimizer MCP

weakness_detector.py30.1 kB
""" Weakness Detector for Path of Exile 2 Builds This module automatically identifies character weaknesses and provides actionable recommendations for improvement. Integrates with all calculator modules to provide comprehensive analysis: - Resistance gaps (using defense_calculator) - Life/ES pool deficiencies (using resource_calculator) - Spirit overflow issues (using spirit_calculator) - Overcapped stats (wasted investment) - Missing defensive layers (using ehp_calculator) - Stun vulnerability (using stun_calculator) Author: Claude Date: 2025-10-22 """ import logging from dataclasses import dataclass, field from typing import Dict, List, Optional, Any from enum import Enum # Import all our calculator modules from ..calculator.defense_calculator import DefenseCalculator, DefenseConstants from ..calculator.resource_calculator import ResourceCalculator, AttributeStats from ..calculator.ehp_calculator import EHPCalculator, DefensiveStats, ThreatProfile, DamageType from ..calculator.spirit_calculator import SpiritCalculator from ..calculator.damage_calculator import DamageCalculator logger = logging.getLogger(__name__) class WeaknessSeverity(Enum): """Severity levels for weaknesses.""" CRITICAL = "critical" # Immediate danger (negative res, no life) HIGH = "high" # Serious problem (very low EHP, overcapped) MEDIUM = "medium" # Room for improvement LOW = "low" # Minor optimization INFO = "info" # Informational only class WeaknessCategory(Enum): """Categories of weaknesses.""" RESISTANCE = "resistance" LIFE_POOL = "life_pool" ENERGY_SHIELD = "energy_shield" DEFENSE_LAYERS = "defense_layers" SPIRIT = "spirit" OVERCAPPED_STAT = "overcapped_stat" DAMAGE = "damage" RESOURCE_MANAGEMENT = "resource_management" STUN_VULNERABILITY = "stun_vulnerability" @dataclass class Weakness: """ Represents a detected character weakness. Attributes: category: Category of weakness severity: Severity level title: Short title description: Detailed description current_value: Current value of the stat recommended_value: Recommended target value impact: Impact description recommendations: List of specific recommendations to fix priority: Priority score (0-100, higher = more urgent) """ category: WeaknessCategory severity: WeaknessSeverity title: str description: str current_value: Any recommended_value: Any impact: str recommendations: List[str] = field(default_factory=list) priority: int = 50 @dataclass class CharacterData: """ Simplified character data for weakness detection. This is a convenience wrapper that extracts the necessary data from the full character JSON. """ level: int character_class: str # Core stats life: float energy_shield: float mana: float spirit_max: int = 0 spirit_reserved: int = 0 # Attributes strength: int = 0 dexterity: int = 0 intelligence: int = 0 # Defenses armor: float = 0.0 evasion: float = 0.0 block_chance: float = 0.0 # Resistances fire_res: float = 0.0 cold_res: float = 0.0 lightning_res: float = 0.0 chaos_res: float = 0.0 # Damage (if available) total_dps: Optional[float] = None # Gear slots (for detecting missing items) equipped_items: Dict[str, bool] = field(default_factory=dict) class WeaknessDetector: """ Main weakness detection engine. Uses all available calculator modules to provide comprehensive character analysis and improvement recommendations. Example: >>> detector = WeaknessDetector() >>> char_data = CharacterData(level=91, character_class="Stormweaver", ...) >>> weaknesses = detector.detect_all_weaknesses(char_data) >>> for weakness in weaknesses: ... print(f"[{weakness.severity.value.upper()}] {weakness.title}") """ def __init__(self): """Initialize weakness detector with calculator modules.""" self.defense_calc = DefenseCalculator() self.ehp_calc = EHPCalculator() # Note: ResourceCalculator and DamageCalculator require parameters, # so they're created on-demand when needed logger.info("WeaknessDetector initialized") # ============================================================================ # MAIN DETECTION METHODS # ============================================================================ def detect_all_weaknesses( self, char_data: CharacterData, threat_profile: Optional[ThreatProfile] = None ) -> List[Weakness]: """ Detect all weaknesses in a character build. Args: char_data: Character data to analyze threat_profile: Optional threat profile for EHP calculations Returns: List of detected weaknesses, sorted by priority (highest first) """ logger.info(f"Detecting weaknesses for level {char_data.level} {char_data.character_class}") threat_profile = threat_profile or ThreatProfile() weaknesses = [] # Run all detection methods weaknesses.extend(self._detect_resistance_gaps(char_data)) weaknesses.extend(self._detect_life_pool_issues(char_data)) weaknesses.extend(self._detect_es_issues(char_data)) weaknesses.extend(self._detect_spirit_issues(char_data)) weaknesses.extend(self._detect_overcapped_stats(char_data)) weaknesses.extend(self._detect_defense_layer_issues(char_data, threat_profile)) weaknesses.extend(self._detect_stun_vulnerability(char_data)) weaknesses.extend(self._detect_resource_issues(char_data)) # Sort by priority (highest first) weaknesses.sort(key=lambda w: w.priority, reverse=True) logger.info(f"Detected {len(weaknesses)} weaknesses") return weaknesses def get_critical_weaknesses( self, char_data: CharacterData ) -> List[Weakness]: """ Get only CRITICAL severity weaknesses. These are issues that need immediate attention. """ all_weaknesses = self.detect_all_weaknesses(char_data) critical = [w for w in all_weaknesses if w.severity == WeaknessSeverity.CRITICAL] logger.info(f"Found {len(critical)} critical weaknesses") return critical def get_weakness_summary( self, char_data: CharacterData ) -> Dict[str, Any]: """ Get a summary of all weaknesses by category and severity. Returns: Dictionary with summary statistics """ weaknesses = self.detect_all_weaknesses(char_data) # Count by severity by_severity = { severity: len([w for w in weaknesses if w.severity == severity]) for severity in WeaknessSeverity } # Count by category by_category = { category: len([w for w in weaknesses if w.category == category]) for category in WeaknessCategory } # Get top priorities top_5 = weaknesses[:5] summary = { 'total_weaknesses': len(weaknesses), 'by_severity': {k.value: v for k, v in by_severity.items()}, 'by_category': {k.value: v for k, v in by_category.items()}, 'top_priorities': [ { 'title': w.title, 'severity': w.severity.value, 'category': w.category.value, 'priority': w.priority } for w in top_5 ], 'needs_immediate_attention': by_severity[WeaknessSeverity.CRITICAL] > 0 } return summary # ============================================================================ # RESISTANCE DETECTION # ============================================================================ def _detect_resistance_gaps(self, char_data: CharacterData) -> List[Weakness]: """Detect resistance weaknesses.""" weaknesses = [] cap = DefenseConstants.RESISTANCE_DEFAULT_CAP resistances = { 'Fire': char_data.fire_res, 'Cold': char_data.cold_res, 'Lightning': char_data.lightning_res, 'Chaos': char_data.chaos_res } for res_name, res_value in resistances.items(): # Negative resistances (CRITICAL) if res_value < 0: deficit = abs(res_value) weaknesses.append(Weakness( category=WeaknessCategory.RESISTANCE, severity=WeaknessSeverity.CRITICAL, title=f"Negative {res_name} Resistance", description=f"{res_name} resistance is {res_value:.0f}%, amplifying damage taken", current_value=res_value, recommended_value=cap, impact=f"Taking {deficit:.0f}% MORE {res_name.lower()} damage", recommendations=[ f"Add +{deficit:.0f}% {res_name} resistance to reach 0%", f"Ideally reach {cap}% cap for maximum mitigation", "Check gear, passive tree, and charms for resistance mods" ], priority=95 + int(deficit / 10) # More negative = higher priority )) # Below cap (HIGH/MEDIUM) elif res_value < cap: deficit = cap - res_value # Chaos res is less critical if res_name == 'Chaos': severity = WeaknessSeverity.MEDIUM if res_value < cap * 0.5 else WeaknessSeverity.LOW priority = 40 + int(deficit / 5) else: severity = WeaknessSeverity.HIGH if deficit > 20 else WeaknessSeverity.MEDIUM priority = 70 + int(deficit / 5) weaknesses.append(Weakness( category=WeaknessCategory.RESISTANCE, severity=severity, title=f"Uncapped {res_name} Resistance", description=f"{res_name} resistance is {res_value:.0f}%, {deficit:.0f}% below cap", current_value=res_value, recommended_value=cap, impact=f"Taking extra {res_name.lower()} damage", recommendations=[ f"Add +{deficit:.0f}% {res_name} resistance to reach {cap}% cap", "Prioritize this before increasing damage" ], priority=priority )) return weaknesses # ============================================================================ # LIFE POOL DETECTION # ============================================================================ def _detect_life_pool_issues(self, char_data: CharacterData) -> List[Weakness]: """Detect life pool weaknesses.""" weaknesses = [] # Expected life: rough benchmark is 100 life per level expected_life = char_data.level * 100 # Very low life (CRITICAL) if char_data.life < expected_life * 0.5: deficit = expected_life - char_data.life weaknesses.append(Weakness( category=WeaknessCategory.LIFE_POOL, severity=WeaknessSeverity.CRITICAL, title="Critically Low Life Pool", description=f"Life pool ({char_data.life:.0f}) is dangerously low for level {char_data.level}", current_value=char_data.life, recommended_value=expected_life, impact="Extremely vulnerable to one-shots", recommendations=[ f"Increase life by {deficit:.0f} (current: {char_data.life:.0f}, recommended: {expected_life:.0f})", "Focus on % increased maximum life from tree", "Look for flat +life on all gear pieces", "Consider using life flasks more actively" ], priority=90 )) # Low life (HIGH/MEDIUM) elif char_data.life < expected_life * 0.75: deficit = expected_life - char_data.life severity = WeaknessSeverity.HIGH if char_data.life < expected_life * 0.6 else WeaknessSeverity.MEDIUM weaknesses.append(Weakness( category=WeaknessCategory.LIFE_POOL, severity=severity, title="Low Life Pool", description=f"Life pool ({char_data.life:.0f}) is below recommended for level {char_data.level}", current_value=char_data.life, recommended_value=expected_life, impact="Vulnerable to burst damage", recommendations=[ f"Increase life by {deficit:.0f}", "Allocate life nodes on passive tree", "Upgrade gear with higher life rolls" ], priority=65 )) return weaknesses # ============================================================================ # ENERGY SHIELD DETECTION # ============================================================================ def _detect_es_issues(self, char_data: CharacterData) -> List[Weakness]: """Detect energy shield weaknesses.""" weaknesses = [] # Check if ES-focused build (ES > Life) if char_data.energy_shield > char_data.life: # ES build with low ES expected_es = char_data.level * 150 # ES builds need more if char_data.energy_shield < expected_es * 0.6: weaknesses.append(Weakness( category=WeaknessCategory.ENERGY_SHIELD, severity=WeaknessSeverity.HIGH, title="Low Energy Shield for ES Build", description=f"ES ({char_data.energy_shield:.0f}) is low for an ES-focused build at level {char_data.level}", current_value=char_data.energy_shield, recommended_value=expected_es, impact="Insufficient effective HP pool", recommendations=[ f"Increase ES by {expected_es - char_data.energy_shield:.0f}", "Focus on % increased ES and flat +ES on gear", "Consider ES recharge rate improvements" ], priority=70 )) return weaknesses # ============================================================================ # SPIRIT DETECTION # ============================================================================ def _detect_spirit_issues(self, char_data: CharacterData) -> List[Weakness]: """Detect Spirit system weaknesses.""" weaknesses = [] # Only check if character uses Spirit if char_data.spirit_max == 0: return weaknesses # Spirit overflow (CRITICAL) if char_data.spirit_reserved > char_data.spirit_max: overflow = char_data.spirit_reserved - char_data.spirit_max weaknesses.append(Weakness( category=WeaknessCategory.SPIRIT, severity=WeaknessSeverity.CRITICAL, title="Spirit Overflow", description=f"Spirit reserved ({char_data.spirit_reserved}) exceeds maximum ({char_data.spirit_max})", current_value=char_data.spirit_reserved, recommended_value=char_data.spirit_max, impact="Cannot activate all Spirit gems", recommendations=[ f"Reduce Spirit reservation by {overflow}", "Disable lowest priority Spirit gems", "Remove support gems with high multipliers", "Get more maximum Spirit from gear or passives" ], priority=95 )) # Very high Spirit usage (MEDIUM) elif char_data.spirit_reserved > char_data.spirit_max * 0.95: available = char_data.spirit_max - char_data.spirit_reserved weaknesses.append(Weakness( category=WeaknessCategory.SPIRIT, severity=WeaknessSeverity.MEDIUM, title="Near Spirit Cap", description=f"Using {char_data.spirit_reserved}/{char_data.spirit_max} Spirit ({available} available)", current_value=char_data.spirit_reserved, recommended_value=char_data.spirit_max * 0.85, impact="No room for additional Spirit gems", recommendations=[ "Consider getting more maximum Spirit", "Optimize support gems to reduce costs", "Leave some Spirit headroom for flexibility" ], priority=45 )) return weaknesses # ============================================================================ # OVERCAPPED STATS DETECTION # ============================================================================ def _detect_overcapped_stats(self, char_data: CharacterData) -> List[Weakness]: """Detect overcapped stats (wasted investment).""" weaknesses = [] # Block overcap (PoE2: 50% max) if char_data.block_chance > DefenseConstants.BLOCK_MAX_CHANCE: waste = char_data.block_chance - DefenseConstants.BLOCK_MAX_CHANCE weaknesses.append(Weakness( category=WeaknessCategory.OVERCAPPED_STAT, severity=WeaknessSeverity.MEDIUM if waste > 10 else WeaknessSeverity.LOW, title="Overcapped Block Chance", description=f"Block chance ({char_data.block_chance:.0f}%) exceeds cap ({DefenseConstants.BLOCK_MAX_CHANCE}%)", current_value=char_data.block_chance, recommended_value=DefenseConstants.BLOCK_MAX_CHANCE, impact=f"Wasting {waste:.0f}% block chance investment", recommendations=[ f"Remove {waste:.0f}% block chance from tree/gear", "Reallocate to other defenses (armor, evasion, ES)", "Block cap in PoE2 is 50%, not 75% like PoE1" ], priority=50 if waste > 10 else 35 )) # Resistance overcap (90% hard cap in PoE2) for res_name in ['Fire', 'Cold', 'Lightning', 'Chaos']: res_value = getattr(char_data, f"{res_name.lower()}_res") if res_value > DefenseConstants.RESISTANCE_HARD_CAP: waste = res_value - DefenseConstants.RESISTANCE_HARD_CAP weaknesses.append(Weakness( category=WeaknessCategory.OVERCAPPED_STAT, severity=WeaknessSeverity.LOW, title=f"Overcapped {res_name} Resistance", description=f"{res_name} resistance ({res_value:.0f}%) exceeds hard cap ({DefenseConstants.RESISTANCE_HARD_CAP}%)", current_value=res_value, recommended_value=DefenseConstants.RESISTANCE_HARD_CAP, impact=f"Wasting {waste:.0f}% resistance", recommendations=[ f"Remove {waste:.0f}% {res_name} resistance", "Reallocate to other uncapped resistances or stats", "Note: PoE2 has 90% hard cap, cannot be exceeded" ], priority=30 )) return weaknesses # ============================================================================ # DEFENSE LAYERS DETECTION # ============================================================================ def _detect_defense_layer_issues( self, char_data: CharacterData, threat_profile: ThreatProfile ) -> List[Weakness]: """Detect defense layer weaknesses using EHP calculator.""" weaknesses = [] # Build DefensiveStats from CharacterData defensive_stats = DefensiveStats( life=char_data.life, energy_shield=char_data.energy_shield, armor=char_data.armor, evasion=char_data.evasion, block_chance=char_data.block_chance, fire_res=char_data.fire_res, cold_res=char_data.cold_res, lightning_res=char_data.lightning_res, chaos_res=char_data.chaos_res ) # Use EHP calculator's built-in gap detection ehp_gaps = self.ehp_calc.identify_defense_gaps(defensive_stats, threat_profile) # Convert EHP gaps to our Weakness format for gap in ehp_gaps: # Map EHP severity (0-10) to our severity enum if gap.severity >= 8: severity = WeaknessSeverity.CRITICAL elif gap.severity >= 6: severity = WeaknessSeverity.HIGH elif gap.severity >= 4: severity = WeaknessSeverity.MEDIUM else: severity = WeaknessSeverity.LOW weaknesses.append(Weakness( category=WeaknessCategory.DEFENSE_LAYERS, severity=severity, title=gap.gap_type.replace('_', ' ').title(), description=gap.description, current_value=gap.current_value, recommended_value=gap.recommended_value, impact="Reduced survivability", recommendations=[gap.recommendation], priority=int(gap.severity * 10) )) return weaknesses # ============================================================================ # STUN VULNERABILITY DETECTION # ============================================================================ def _detect_stun_vulnerability(self, char_data: CharacterData) -> List[Weakness]: """Detect stun vulnerability issues.""" weaknesses = [] # Low life makes you more vulnerable to stuns # In PoE2, stun chance = (damage / max_life) × 100 # If life is very low, even small hits can stun if char_data.life < 2000: weaknesses.append(Weakness( category=WeaknessCategory.STUN_VULNERABILITY, severity=WeaknessSeverity.HIGH, title="High Stun Vulnerability", description=f"Very low life ({char_data.life:.0f}) makes you vulnerable to stuns", current_value=char_data.life, recommended_value=3000, impact="Frequently interrupted by Light Stuns, rapid Heavy Stun buildup", recommendations=[ "Increase life pool to reduce stun chance", "Consider stun avoidance/threshold modifiers", "Look for 'reduced stun threshold' on gear" ], priority=60 )) return weaknesses # ============================================================================ # RESOURCE MANAGEMENT DETECTION # ============================================================================ def _detect_resource_issues(self, char_data: CharacterData) -> List[Weakness]: """Detect resource management weaknesses.""" weaknesses = [] # Low mana pool for mana-based build if char_data.mana > 0: expected_mana = char_data.level * 50 if char_data.mana < expected_mana * 0.5: weaknesses.append(Weakness( category=WeaknessCategory.RESOURCE_MANAGEMENT, severity=WeaknessSeverity.MEDIUM, title="Low Mana Pool", description=f"Mana pool ({char_data.mana:.0f}) is low for level {char_data.level}", current_value=char_data.mana, recommended_value=expected_mana, impact="May run out of mana during combat", recommendations=[ f"Increase mana by {expected_mana - char_data.mana:.0f}", "Add mana nodes from passive tree", "Consider mana regeneration improvements" ], priority=45 )) return weaknesses # ============================================================================ # FORMATTING AND DISPLAY # ============================================================================ def format_weakness_report( self, weaknesses: List[Weakness], include_low_severity: bool = True ) -> str: """ Format weaknesses into a human-readable report. Args: weaknesses: List of weaknesses to format include_low_severity: Include LOW and INFO severity issues Returns: Formatted string report """ if not weaknesses: return "✓ No significant weaknesses detected! Build looks solid." # Filter by severity if requested if not include_low_severity: weaknesses = [ w for w in weaknesses if w.severity not in [WeaknessSeverity.LOW, WeaknessSeverity.INFO] ] # Group by severity by_severity = {} for weakness in weaknesses: if weakness.severity not in by_severity: by_severity[weakness.severity] = [] by_severity[weakness.severity].append(weakness) # Build report lines = [] lines.append("=" * 80) lines.append("CHARACTER WEAKNESS REPORT") lines.append("=" * 80) lines.append("") severity_order = [ WeaknessSeverity.CRITICAL, WeaknessSeverity.HIGH, WeaknessSeverity.MEDIUM, WeaknessSeverity.LOW, WeaknessSeverity.INFO ] for severity in severity_order: if severity not in by_severity: continue severity_weaknesses = by_severity[severity] # Severity header severity_emoji = { WeaknessSeverity.CRITICAL: "🚨", WeaknessSeverity.HIGH: "⚠️", WeaknessSeverity.MEDIUM: "⚡", WeaknessSeverity.LOW: "ℹ️", WeaknessSeverity.INFO: "💡" } lines.append(f"{severity_emoji[severity]} {severity.value.upper()} PRIORITY ({len(severity_weaknesses)})") lines.append("-" * 80) lines.append("") for weakness in severity_weaknesses: lines.append(f"[{weakness.category.value.upper()}] {weakness.title}") lines.append(f" {weakness.description}") lines.append(f" Impact: {weakness.impact}") lines.append(f" Current: {weakness.current_value} → Recommended: {weakness.recommended_value}") if weakness.recommendations: lines.append(" Recommendations:") for rec in weakness.recommendations: lines.append(f" • {rec}") lines.append("") lines.append("=" * 80) return "\n".join(lines) # ============================================================================ # CONVENIENCE FUNCTIONS # ============================================================================ def quick_weakness_check(char_data: CharacterData) -> List[str]: """ Quick weakness check returning just the titles. Args: char_data: Character data to analyze Returns: List of weakness titles """ detector = WeaknessDetector() weaknesses = detector.detect_all_weaknesses(char_data) return [f"[{w.severity.value.upper()}] {w.title}" for w in weaknesses] def get_critical_issues(char_data: CharacterData) -> List[str]: """ Get critical issues only. Args: char_data: Character data to analyze Returns: List of critical issue descriptions """ detector = WeaknessDetector() critical = detector.get_critical_weaknesses(char_data) return [w.description for w in critical] if __name__ == "__main__": # Configure logging logging.basicConfig( level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s' ) print("=" * 80) print("Path of Exile 2 Weakness Detector - Test") print("=" * 80) print() # Example: Character with several issues char = CharacterData( level=91, character_class="Stormweaver", life=1413, # Very low energy_shield=4847, mana=850, spirit_max=100, spirit_reserved=95, strength=50, dexterity=120, intelligence=300, armor=2000, evasion=500, block_chance=25, fire_res=-2, # CRITICAL: negative cold_res=-8, # CRITICAL: negative lightning_res=75, chaos_res=-60 ) # Run detection detector = WeaknessDetector() weaknesses = detector.detect_all_weaknesses(char) # Print report report = detector.format_weakness_report(weaknesses) print(report) # Print summary print() summary = detector.get_weakness_summary(char) print("SUMMARY:") print(f" Total Weaknesses: {summary['total_weaknesses']}") print(f" Critical: {summary['by_severity']['critical']}") print(f" High: {summary['by_severity']['high']}") print(f" Medium: {summary['by_severity']['medium']}") print(f" Needs Immediate Attention: {summary['needs_immediate_attention']}") print() print("=" * 80) print("Test Complete!") print("=" * 80)

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/HivemindOverlord/poe2-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server