""" Venue Scoring Algorithm with Weighted Criteria Skill: multi-criteria decision analysis Type: analysis/processing Version: 1.0 Author: venue_engineer """ import json import math import datetime from typing import Dict, List, Optional, Any, Union, Tuple from dataclasses import dataclass from collections import defaultdict @dataclass class ScoringCriteria: """Individual scoring criterion definition.""" name: str weight: float category: str min_value: float max_value: float optimal_value: Optional[float] = None scoring_function: str = "linear" # linear, exponential, logarithmic, step @dataclass class VenueScore: """Comprehensive venue scoring results.""" venue_id: str overall_score: float category_scores: Dict[str, float] criterion_scores: Dict[str, float] ranking_position: Optional[int] strengths: List[str] weaknesses: List[str] recommendations: List[str] confidence_level: float class VenueScoringAlgorithm: """Advanced venue scoring algorithm with weighted criteria.""" def __init__(self): self.criteria = self._initialize_criteria() self.categories = { 'capacity': 0.20, 'acoustics': 0.15, 'accessibility': 0.15, 'logistics': 0.20, 'technical': 0.15, 'cost': 0.15 } self.scoring_functions = { 'linear': self._linear_score, 'exponential': self._exponential_score, 'logarithmic': self._logarithmic_score, 'step': self._step_score } def _initialize_criteria(self) -> Dict[str, ScoringCriteria]: """Initialize comprehensive scoring criteria.""" criteria = { # Capacity criteria 'max_attendees': ScoringCriteria( 'max_attendees', 0.08, 'capacity', 50, 5000, 2500, 'linear' ), 'seating_configurations': ScoringCriteria( 'seating_configurations', 0.06, 'capacity', 1, 10, 5, 'linear' ), 'floor_space_m2': ScoringCriteria( 'floor_space_m2', 0.06, 'capacity', 100, 10000, 5000, 'linear' ), # Acoustic criteria 'rt60_reverberation': ScoringCriteria( 'rt60_reverberation', 0.05, 'acoustics', 0.4, 2.5, 1.2, 'exponential' ), 'noise_isolation': ScoringCriteria( 'noise_isolation', 0.05, 'acoustics', 20, 60, 45, 'linear' ), 'sound_system_quality': ScoringCriteria( 'sound_system_quality', 0.05, 'acoustics', 1, 10, 8, 'linear' ), # Accessibility criteria 'wheelchair_access': ScoringCriteria( 'wheelchair_access', 0.05, 'accessibility', 0, 100, 100, 'step' ), 'public_transport': ScoringCriteria( 'public_transport', 0.05, 'accessibility', 0, 10, 7, 'linear' ), 'parking_spaces': ScoringCriteria( 'parking_spaces', 0.05, 'accessibility', 0, 500, 200, 'logarithmic' ), # Logistics criteria 'loading_docks': ScoringCriteria( 'loading_docks', 0.05, 'logistics', 0, 10, 3, 'linear' ), 'storage_space': ScoringCriteria( 'storage_space', 0.05, 'logistics', 0, 1000, 500, 'linear' ), 'change_rooms': ScoringCriteria( 'change_rooms', 0.05, 'logistics', 0, 10, 4, 'linear' ), 'catering_facilities': ScoringCriteria( 'catering_facilities', 0.05, 'logistics', 0, 10, 6, 'linear' ), # Technical criteria 'power_capacity': ScoringCriteria( 'power_capacity', 0.05, 'technical', 50, 1000, 500, 'linear' ), 'network_bandwidth': ScoringCriteria( 'network_bandwidth', 0.05, 'technical', 100, 2000, 1000, 'linear' ), 'lighting_system': ScoringCriteria( 'lighting_system', 0.05, 'technical', 1, 10, 7, 'linear' ), # Cost criteria 'hourly_rate': ScoringCriteria( 'hourly_rate', 0.08, 'cost', 50, 1000, 200, 'exponential' ), 'setup_cost': ScoringCriteria( 'setup_cost', 0.07, 'cost', 0, 10000, 2000, 'exponential' ) } return criteria def score_venue(self, venue_data: Dict[str, Any], event_requirements: Optional[Dict[str, Any]] = None) -> VenueScore: """Score venue using comprehensive weighted criteria.""" venue_id = venue_data.get('venue_id', 'unknown') criterion_scores = {} category_scores = defaultdict(float) # Score each criterion for criterion_name, criterion in self.criteria.items(): raw_value = venue_data.get(criterion_name, criterion.min_value) # Apply event requirement adjustments if event_requirements and criterion_name in event_requirements: raw_value = self._adjust_for_requirements( raw_value, event_requirements[criterion_name], criterion ) # Calculate criterion score score = self._score_criterion(raw_value, criterion) criterion_scores[criterion_name] = score category_scores[criterion.category] += score * criterion.weight # Calculate overall score overall_score = sum( category_scores[cat] * weight for cat, weight in self.categories.items() ) # Normalize category scores for category in category_scores: category_weight = sum( crit.weight for crit in self.criteria.values() if crit.category == category ) if category_weight > 0: category_scores[category] /= category_weight # Analyze strengths and weaknesses strengths, weaknesses = self._analyze_performance(criterion_scores) # Generate recommendations recommendations = self._generate_recommendations( venue_data, criterion_scores, event_requirements ) # Calculate confidence level confidence = self._calculate_confidence(venue_data, criterion_scores) return VenueScore( venue_id=venue_id, overall_score=overall_score, category_scores=dict(category_scores), criterion_scores=criterion_scores, ranking_position=None, strengths=strengths, weaknesses=weaknesses, recommendations=recommendations, confidence_level=confidence ) def rank_venues(self, venues_data: List[Dict[str, Any]], event_requirements: Optional[Dict[str, Any]] = None) -> List[VenueScore]: """Rank multiple venues and assign positions.""" scored_venues = [] for venue_data in venues_data: score = self.score_venue(venue_data, event_requirements) scored_venues.append(score) # Sort by overall score (descending) scored_venues.sort(key=lambda x: x.overall_score, reverse=True) # Assign ranking positions for i, venue_score in enumerate(scored_venues): venue_score.ranking_position = i + 1 return scored_venues def compare_venues(self, venue_scores: List[VenueScore]) -> Dict[str, Any]: """Compare multiple venues with detailed analysis.""" if len(venue_scores) < 2: return {"error": "Need at least 2 venues for comparison"} comparison = { 'total_venues': len(venue_scores), 'best_venue': venue_scores[0].venue_id, 'score_range': { 'highest': venue_scores[0].overall_score, 'lowest': venue_scores[-1].overall_score, 'average': sum(v.overall_score for v in venue_scores) / len(venue_scores) }, 'category_winners': {}, 'detailed_comparison': [] } # Find category winners categories = set() for score in venue_scores: categories.update(score.category_scores.keys()) for category in categories: best_venue = max( venue_scores, key=lambda v: v.category_scores.get(category, 0) ) comparison['category_winners'][category] = { 'venue': best_venue.venue_id, 'score': best_venue.category_scores.get(category, 0) } # Detailed comparison matrix criteria_names = list(self.criteria.keys()) for criterion in criteria_names: criterion_data = { 'criterion': criterion, 'category': self.criteria[criterion].category, 'weight': self.criteria[criterion].weight, 'scores': [] } for venue_score in venue_scores: score_value = venue_score.criterion_scores.get(criterion, 0) criterion_data['scores'].append({ 'venue': venue_score.venue_id, 'score': score_value, 'rank': 0 # Will be calculated below }) # Rank venues by this criterion criterion_data['scores'].sort(key=lambda x: x['score'], reverse=True) for i, score_data in enumerate(criterion_data['scores']): score_data['rank'] = i + 1 comparison['detailed_comparison'].append(criterion_data) return comparison def _score_criterion(self, value: float, criterion: ScoringCriteria) -> float: """Score individual criterion using appropriate function.""" # Clamp value to valid range value = max(criterion.min_value, min(criterion.max_value, value)) # Get scoring function scoring_func = self.scoring_functions.get( criterion.scoring_function, self._linear_score ) # Calculate score if criterion.optimal_value is not None: # For criteria with optimal values (like RT60) score = scoring_func(value, criterion, optimal=True) else: # For criteria where higher is better score = scoring_func(value, criterion, optimal=False) return max(0, min(100, score)) # Ensure score is between 0-100 def _linear_score(self, value: float, criterion: ScoringCriteria, optimal: bool = False) -> float: """Linear scoring function.""" if optimal: # Score based on distance from optimal value distance = abs(value - criterion.optimal_value) max_distance = max( criterion.optimal_value - criterion.min_value, criterion.max_value - criterion.optimal_value ) return 100 * (1 - distance / max_distance) else: # Higher is better range_size = criterion.max_value - criterion.min_value return 100 * (value - criterion.min_value) / range_size def _exponential_score(self, value: float, criterion: ScoringCriteria, optimal: bool = False) -> float: """Exponential scoring function.""" if optimal: distance = abs(value - criterion.optimal_value) max_distance = max( criterion.optimal_value - criterion.min_value, criterion.max_value - criterion.optimal_value ) normalized = distance / max_distance return 100 * math.exp(-3 * normalized) # Exponential decay else: # Exponential growth normalized = (value - criterion.min_value) / (criterion.max_value - criterion.min_value) return 100 * (math.exp(3 * normalized) - 1) / (math.exp(3) - 1) def _logarithmic_score(self, value: float, criterion: ScoringCriteria, optimal: bool = False) -> float: """Logarithmic scoring function.""" if optimal: distance = abs(value - criterion.optimal_value) max_distance = max( criterion.optimal_value - criterion.min_value, criterion.max_value - criterion.optimal_value ) normalized = distance / max_distance return 100 * (1 - math.log(1 + 9 * normalized) / math.log(10)) else: normalized = (value - criterion.min_value) / (criterion.max_value - criterion.min_value) return 100 * math.log(1 + 9 * normalized) / math.log(10) def _step_score(self, value: float, criterion: ScoringCriteria, optimal: bool = False) -> float: """Step function scoring.""" if optimal: threshold = criterion.optimal_value return 100 if abs(value - threshold) < 0.1 * threshold else 50 else: # Step thresholds at 25%, 50%, 75%, 100% normalized = (value - criterion.min_value) / (criterion.max_value - criterion.min_value) if normalized >= 0.9: return 100 elif normalized >= 0.7: return 80 elif normalized >= 0.5: return 60 elif normalized >= 0.3: return 40 else: return 20 def _adjust_for_requirements(self, venue_value: float, requirement: float, criterion: ScoringCriteria) -> float: """Adjust venue value based on event requirements.""" # If venue meets or exceeds requirement, boost the score if venue_value >= requirement: adjustment_factor = 1.2 # 20% boost else: # Penalize for not meeting requirements shortfall_ratio = venue_value / requirement adjustment_factor = max(0.5, shortfall_ratio) # Minimum 50% of original return venue_value * adjustment_factor def _analyze_performance(self, criterion_scores: Dict[str, float]) -> Tuple[List[str], List[str]]: """Analyze venue performance to identify strengths and weaknesses.""" strengths = [] weaknesses = [] for criterion_name, score in criterion_scores.items(): criterion = self.criteria[criterion_name] if score >= 80: strengths.append(f"Excellent {criterion.name} ({score:.1f}/100)") elif score <= 40: weaknesses.append(f"Poor {criterion.name} ({score:.1f}/100)") return strengths, weaknesses def _generate_recommendations(self, venue_data: Dict[str, Any], criterion_scores: Dict[str, float], event_requirements: Optional[Dict[str, Any]]) -> List[str]: """Generate venue improvement recommendations.""" recommendations = [] # Analyze low-scoring criteria low_scoring = [ (name, score) for name, score in criterion_scores.items() if score < 60 ] # Sort by priority (weight * score deficit) low_scoring.sort( key=lambda x: self.criteria[x[0]].weight * (60 - x[1]), reverse=True ) for criterion_name, score in low_scoring[:5]: # Top 5 recommendations criterion = self.criteria[criterion_name] current_value = venue_data.get(criterion_name, criterion.min_value) if criterion.scoring_function == 'exponential' and criterion.category == 'cost': # For cost criteria, lower is better recommendations.append( f"Reduce {criterion.name} from {current_value} to improve cost efficiency" ) else: # For other criteria, higher is better target_value = criterion.min_value + (criterion.max_value - criterion.min_value) * 0.8 recommendations.append( f"Improve {criterion.name} from {current_value} to {target_value:.0f} " f"(current score: {score:.1f}/100)" ) # Event-specific recommendations if event_requirements: for req_name, req_value in event_requirements.items(): if req_name in venue_data: venue_value = venue_data[req_name] if venue_value < req_value: recommendations.append( f"Upgrade {req_name} to meet event requirement ({venue_value} < {req_value})" ) return recommendations def _calculate_confidence(self, venue_data: Dict[str, Any], criterion_scores: Dict[str, float]) -> float: """Calculate confidence level in scoring results.""" # Factors affecting confidence data_completeness = len(venue_data) / len(self.criteria) score_variance = math.variance(list(criterion_scores.values())) if criterion_scores else 0 # High variance indicates inconsistent performance variance_factor = max(0.5, 1.0 - score_variance / 1000) # Overall confidence confidence = data_completeness * variance_factor * 100 return min(100, max(50, confidence)) # Minimum 50%, maximum 100%