Spaces:
Sleeping
Sleeping
Wanderlust Dev
fix(chatbot): resolve 7 critical issues - KB expansion, pipeline fixes, dynamic responses
8191051 | from typing import Optional | |
| from .cost_calculator import CostCalculator | |
| class BudgetOptimizer: | |
| """Multi-constraint budget optimizer with 3-tier plan generation and priority allocation.""" | |
| TIER_ORDER = ['budget', 'mid_range', 'luxury'] | |
| BUDGET_PRIORITY_DEFAULTS = { | |
| 'accommodation': 0.35, | |
| 'food': 0.25, | |
| 'activities': 0.20, | |
| 'transport': 0.15, | |
| 'misc': 0.05, | |
| } | |
| def __init__(self, destinations: list): | |
| self.destinations = {d['id']: d for d in destinations} | |
| def get_destination(self, dest_id: str) -> Optional[dict]: | |
| return self.destinations.get(dest_id) | |
| def generate_three_tier_plans( | |
| self, | |
| destination: dict, | |
| days: int, | |
| num_adults: int = 2, | |
| num_children: int = 0, | |
| activities: Optional[list] = None, | |
| month: Optional[int] = None | |
| ) -> list[dict]: | |
| """Generate budget/mid_range/luxury plans with full cost breakdown.""" | |
| plans = [] | |
| for tier in self.TIER_ORDER: | |
| plan = CostCalculator.calculate_trip_total( | |
| destination=destination, | |
| days=days, | |
| tier=tier, | |
| num_adults=num_adults, | |
| num_children=num_children, | |
| activities=activities, | |
| month=month, | |
| ) | |
| tier_data = destination.get('cost_tiers', {}).get(tier, {}) | |
| plan['tier_description_vi'] = tier_data.get('description_vi', '') | |
| plan['tier_description_en'] = tier_data.get('description_en', '') | |
| plans.append(plan) | |
| return plans | |
| def check_budget_feasibility( | |
| self, | |
| destination: dict, | |
| total_budget: float, | |
| currency: str, | |
| days: int, | |
| num_adults: int = 2, | |
| num_children: int = 0, | |
| month: Optional[int] = None | |
| ) -> dict: | |
| """Check if a given budget is feasible and suggest the best matching tier.""" | |
| budget_vnd = total_budget | |
| if currency == 'USD': | |
| budget_vnd = CostCalculator.convert_currency(total_budget, 'USD', 'VND') | |
| plans = self.generate_three_tier_plans( | |
| destination, days, num_adults, num_children, month=month | |
| ) | |
| feasible_tiers = [] | |
| recommended_tier = None | |
| for plan in plans: | |
| if plan['total'] <= budget_vnd: | |
| feasible_tiers.append(plan['tier']) | |
| recommended_tier = plan['tier'] # last feasible = highest affordable | |
| if not feasible_tiers: | |
| # Budget too low even for budget tier | |
| budget_plan = plans[0] | |
| shortfall = budget_plan['total'] - budget_vnd | |
| return { | |
| 'feasible': False, | |
| 'budget_vnd': budget_vnd, | |
| 'minimum_required_vnd': budget_plan['total'], | |
| 'shortfall_vnd': int(shortfall), | |
| 'suggestion': 'reduce_days_or_people', | |
| 'recommended_days': max(1, int(days * budget_vnd / budget_plan['total'])), | |
| 'plans': plans, | |
| } | |
| return { | |
| 'feasible': True, | |
| 'budget_vnd': budget_vnd, | |
| 'feasible_tiers': feasible_tiers, | |
| 'recommended_tier': recommended_tier, | |
| 'remaining_budget_vnd': int(budget_vnd - next( | |
| p['total'] for p in plans if p['tier'] == recommended_tier | |
| )), | |
| 'plans': plans, | |
| } | |
| def optimize_with_priorities( | |
| self, | |
| destination: dict, | |
| total_budget: float, | |
| currency: str, | |
| days: int, | |
| num_adults: int = 2, | |
| num_children: int = 0, | |
| priorities: Optional[dict] = None, | |
| month: Optional[int] = None | |
| ) -> dict: | |
| """Allocate budget based on user spending priorities. | |
| priorities: {'accommodation': 'high', 'food': 'high', 'activities': 'low', ...} | |
| Values: 'high', 'medium', 'low' | |
| """ | |
| budget_vnd = total_budget | |
| if currency == 'USD': | |
| budget_vnd = CostCalculator.convert_currency(total_budget, 'USD', 'VND') | |
| # Convert priority labels to weights | |
| priority_weights = dict(self.BUDGET_PRIORITY_DEFAULTS) | |
| if priorities: | |
| multipliers = {'high': 1.5, 'medium': 1.0, 'low': 0.5} | |
| for category, level in priorities.items(): | |
| if category in priority_weights: | |
| priority_weights[category] *= multipliers.get(level, 1.0) | |
| # Normalize weights to sum to 1.0 | |
| total_weight = sum(priority_weights.values()) | |
| if total_weight > 0: | |
| priority_weights = {k: v / total_weight for k, v in priority_weights.items()} | |
| # Allocate budget per category | |
| allocation = {} | |
| for category, weight in priority_weights.items(): | |
| allocation[category] = int(budget_vnd * weight) | |
| # Determine tier per category based on allocation | |
| daily_budget_accommodation = allocation['accommodation'] // max(1, days - 1) | |
| cost_tiers = destination.get('cost_tiers', {}) | |
| # Find best matching tier for accommodation | |
| accom_tier = 'budget' | |
| for tier in self.TIER_ORDER: | |
| tier_cost = cost_tiers.get(tier, {}).get('accommodation_vnd', 0) | |
| if tier_cost <= daily_budget_accommodation: | |
| accom_tier = tier | |
| # Find best matching tier for food | |
| daily_food_budget = allocation['food'] // days | |
| total_people = num_adults + num_children | |
| per_person_food = daily_food_budget // max(1, total_people) | |
| per_meal_budget = per_person_food // 3 | |
| food_tier = 'budget' | |
| for tier in self.TIER_ORDER: | |
| tier_cost = cost_tiers.get(tier, {}).get('food_per_meal_vnd', 0) | |
| if tier_cost <= per_meal_budget: | |
| food_tier = tier | |
| return { | |
| 'total_budget_vnd': int(budget_vnd), | |
| 'allocation': allocation, | |
| 'priority_weights': {k: round(v, 3) for k, v in priority_weights.items()}, | |
| 'recommended_tiers': { | |
| 'accommodation': accom_tier, | |
| 'food': food_tier, | |
| }, | |
| 'daily_breakdown': { | |
| 'accommodation_per_night': daily_budget_accommodation, | |
| 'food_per_day': daily_food_budget, | |
| 'activities_per_day': allocation['activities'] // days, | |
| 'transport_per_day': allocation['transport'] // days, | |
| }, | |
| 'per_person_per_day': int(budget_vnd // max(1, total_people) // days), | |
| } | |
| def compare_destinations_cost( | |
| self, | |
| dest_ids: list[str], | |
| days: int, | |
| num_adults: int = 2, | |
| num_children: int = 0, | |
| tier: str = 'mid_range', | |
| month: Optional[int] = None | |
| ) -> list[dict]: | |
| """Compare costs across multiple destinations.""" | |
| results = [] | |
| for dest_id in dest_ids: | |
| dest = self.destinations.get(dest_id) | |
| if not dest: | |
| continue | |
| cost = CostCalculator.calculate_trip_total( | |
| destination=dest, | |
| days=days, | |
| tier=tier, | |
| num_adults=num_adults, | |
| num_children=num_children, | |
| month=month, | |
| ) | |
| cost['destination_id'] = dest_id | |
| cost['destination_name'] = dest.get('name', '') | |
| cost['destination_name_en'] = dest.get('name_en', '') | |
| cost['best_months'] = dest.get('best_months', []) | |
| cost['tags'] = dest.get('tags', []) | |
| results.append(cost) | |
| results.sort(key=lambda x: x['total']) | |
| return results | |