Spaces:
Running
Running
| """ | |
| ์ฒญํฌ ์์ฑ ํธ๋ค๋ฌ | |
| ================ | |
| YAML์ ๊ฐ ์น์ ์ ๊ฒ์ ๊ฐ๋ฅํ ์ฒญํฌ๋ก ๋ณํํ๋ ํธ๋ค๋ฌ ํจ์๋ค. | |
| sync_to_supabase.py์์ ๋์ ์ผ๋ก ํธ์ถ๋ฉ๋๋ค. | |
| ํธ๋ค๋ฌ ๊ท์น: | |
| - ๊ฐ ํธ๋ค๋ฌ๋ (data, context) -> List[Dict] ํํ | |
| - context์๋ chain, hotel_name, hotel_name_ko ๋ฑ ํฌํจ | |
| - ๋ฐํ๊ฐ์ [{content: str, metadata: dict}, ...] ํํ | |
| """ | |
| from typing import Dict, Any, List, Optional | |
| # =========================================================================== | |
| # ์ ํธ๋ฆฌํฐ | |
| # =========================================================================== | |
| DISCLAIMER_PRICE = "\n\nโ ๏ธ ์ฐธ๊ณ : ์ ํฌ์ธํธ/๊ฐ๊ฒฉ ์ ๋ณด๋ ๊ณผ๊ฑฐ ๋ฐ์ดํฐ ๊ธฐ๋ฐ ์ฐธ๊ณ ์ฉ์ ๋๋ค. ์ค์ ํ์ ํฌ์ธํธ์ ๊ฐ๊ฒฉ์ ์์ฝ ์์ ์ ๋ฐ๋ผ ๋ณ๋๋๋ฏ๋ก, ๊ณต์ ์น์ฌ์ดํธ์์ ์ง์ ํ์ธํ์๊ธฐ ๋ฐ๋๋๋ค." | |
| DISCLAIMER_SHORT = "\n\nโ ๏ธ ์ฐธ๊ณ ์ฉ ์ ๋ณด์ ๋๋ค. ์ค์ ๋ด์ฉ์ ๊ณต์ ์น์ฌ์ดํธ์์ ํ์ธํ์ธ์." | |
| def safe_format_number(value: Any, suffix: str = "", prefix: str = "") -> str: | |
| """์ซ์๋ฅผ ์์ ํ๊ฒ ํฌ๋งทํ - None์ด๋ ๋น์ซ์ ๊ฐ ์ฒ๋ฆฌ | |
| Args: | |
| value: ํฌ๋งทํ ํ ๊ฐ (int, float, None ๋ฑ) | |
| suffix: ์ซ์ ๋ค์ ๋ถ์ผ ๋ฌธ์์ด (์: '์', '๋ง์ผ', 'P') | |
| prefix: ์ซ์ ์์ ๋ถ์ผ ๋ฌธ์์ด (์: '์ฝ ', '์ต๋ ') | |
| Returns: | |
| ํฌ๋งท๋ ๋ฌธ์์ด ๋๋ 'N/A' | |
| Examples: | |
| safe_format_number(1000, "์") -> "1,000์" | |
| safe_format_number(None, "์") -> "N/A" | |
| safe_format_number("unknown", "P") -> "N/A" | |
| """ | |
| if value is None: | |
| return "N/A" | |
| try: | |
| if isinstance(value, (int, float)): | |
| return f"{prefix}{value:,}{suffix}" | |
| # ๋ฌธ์์ด์ด ์ซ์๋ก ๋ณํ ๊ฐ๋ฅํ์ง ์๋ | |
| num_value = float(value) | |
| if num_value.is_integer(): | |
| return f"{prefix}{int(num_value):,}{suffix}" | |
| return f"{prefix}{num_value:,.2f}{suffix}" | |
| except (ValueError, TypeError): | |
| return str(value) if value else "N/A" | |
| def get_hotel_info(context: Dict[str, Any]) -> tuple: | |
| """context์์ ํธํ ์ ๋ณด ์ถ์ถ""" | |
| hotel_name = context.get("hotel_name", "Unknown Hotel") | |
| hotel_name_ko = context.get("hotel_name_ko") | |
| chain = context.get("chain", "UNKNOWN") | |
| return hotel_name, hotel_name_ko, chain | |
| def format_hotel_header(hotel_name: str, hotel_name_ko: Optional[str], chain: str) -> str: | |
| """ํธํ ์ ๋ณด ํค๋ ์์ฑ""" | |
| content = f"ํธํ ๋ช : {hotel_name}\n" | |
| if hotel_name_ko: | |
| content += f"ํธํ ๋ช (ํ๊ตญ์ด): {hotel_name_ko}\n" | |
| content += f"์ฒด์ธ: {chain}\n" | |
| return content | |
| def format_evidence(evidence_list: list, max_items: int = 2, max_quote_len: int = 80) -> str: | |
| """Evidence ๋ฐฐ์ด์ ์ฒญํฌ์ ํฌํจํ ํฌ๋งท์ผ๋ก ๋ณํ | |
| Args: | |
| evidence_list: evidence ๊ฐ์ฒด ๋ฆฌ์คํธ | |
| max_items: ํฌํจํ ์ต๋ evidence ์ | |
| max_quote_len: ์ธ์ฉ๋ฌธ ์ต๋ ๊ธธ์ด | |
| Returns: | |
| ํฌ๋งท๋ ์ถ์ฒ ์ ๋ณด ๋ฌธ์์ด (๋น์ด์์ผ๋ฉด ๋น ๋ฌธ์์ด) | |
| """ | |
| if not evidence_list or not isinstance(evidence_list, list): | |
| return "" | |
| content = "\n์ถ์ฒ:\n" | |
| added = 0 | |
| for ev in evidence_list: | |
| if added >= max_items: | |
| break | |
| if not isinstance(ev, dict): | |
| continue | |
| section = ev.get("section_path", "") | |
| quote = ev.get("quote", "") | |
| if section or quote: | |
| if section: | |
| content += f" - [{section}] " | |
| else: | |
| content += " - " | |
| if quote: | |
| # ์ธ์ฉ๋ฌธ ๊ธธ์ด ์ ํ | |
| truncated = quote[:max_quote_len] | |
| if len(quote) > max_quote_len: | |
| truncated += "..." | |
| content += f'"{truncated}"\n' | |
| else: | |
| content += "\n" | |
| added += 1 | |
| return content if added > 0 else "" | |
| # =========================================================================== | |
| # ํธ๋ค๋ฌ: ๋ก์ดํฐ ํ๋ก๊ทธ๋จ | |
| # =========================================================================== | |
| def handle_loyalty_programs(data: Any, context: Dict[str, Any]) -> List[Dict[str, Any]]: | |
| """loyalty_programs ๋๋ loyalty_program ์ฒ๋ฆฌ | |
| LoyaltyProgram ์คํค๋ง์ ๋ด๋ถ ๊ตฌ์ฑ ์์๋ ์๋ธ์ฒญํน: | |
| - points_system โ handle_points_systems | |
| - member_rates โ handle_member_rates | |
| - milestone_program โ handle_milestone_programs | |
| - partner_status_matches โ handle_partner_status_matches | |
| - co_branded_cards โ handle_credit_cards | |
| """ | |
| chunks = [] | |
| chain = context.get("chain", "UNKNOWN") | |
| # ๋จ์ผ ๊ฐ์ฒด์ธ ๊ฒฝ์ฐ ๋ฆฌ์คํธ๋ก ๋ณํ | |
| programs = data if isinstance(data, list) else [data] | |
| for program in programs[:5]: | |
| if not isinstance(program, dict): | |
| continue | |
| program_name = program.get("program_name", "N/A") | |
| program_chain = program.get("chain", chain) | |
| content = f""" | |
| ๋ก์ดํฐ ํ๋ก๊ทธ๋จ: {program_name} | |
| ์ฒด์ธ: {program_chain} | |
| ํฌ์ธํธ ์ด๋ฆ: {program.get("points_name", "N/A")} | |
| ํฌ์ธํธ ๋ง๋ฃ: {program.get("points_expiry", "N/A")} | |
| """ | |
| # ํ์ ๋ฑ๊ธ | |
| tiers = program.get("tiers", []) | |
| if tiers: | |
| content += "\n๋ฑ๊ธ:\n" | |
| for tier in tiers[:7]: | |
| tier_name = tier.get("tier_name", "N/A") | |
| upgrade_req = tier.get("upgrade_requirement", {}) | |
| req_nights = upgrade_req.get("nights") | |
| content += f" - {tier_name}: {req_nights}๋ฐ ์ด์\n" if req_nights else f" - {tier_name}\n" | |
| chunks.append({ | |
| "content": content.strip(), | |
| "metadata": { | |
| "type": "loyalty_program", | |
| "program_name": program_name, | |
| "chain": program_chain | |
| } | |
| }) | |
| # === ๋ด๋ถ ๊ตฌ์ฑ ์์ ์๋ธ์ฒญํน === | |
| # ํ๋ก๊ทธ๋จ๋ณ ์ปจํ ์คํธ ์์ฑ (chain ์ ๋ณด ๋ณด๊ฐ) | |
| program_context = {**context, "chain": program_chain} | |
| # 1. points_system ์ฒ๋ฆฌ | |
| points_system = program.get("points_system") | |
| if points_system: | |
| try: | |
| sub_chunks = handle_points_systems(points_system, program_context) | |
| chunks.extend(sub_chunks) | |
| except Exception: | |
| pass | |
| # 2. member_rates ์ฒ๋ฆฌ | |
| member_rates = program.get("member_rates") | |
| if member_rates: | |
| try: | |
| sub_chunks = handle_member_rates(member_rates, program_context) | |
| chunks.extend(sub_chunks) | |
| except Exception: | |
| pass | |
| # 3. milestone_program ์ฒ๋ฆฌ | |
| milestone_program = program.get("milestone_program") | |
| if milestone_program: | |
| try: | |
| sub_chunks = handle_milestone_programs(milestone_program, program_context) | |
| chunks.extend(sub_chunks) | |
| except Exception: | |
| pass | |
| # 4. partner_status_matches ์ฒ๋ฆฌ | |
| partner_status_matches = program.get("partner_status_matches") | |
| if partner_status_matches: | |
| try: | |
| sub_chunks = handle_partner_status_matches(partner_status_matches, program_context) | |
| chunks.extend(sub_chunks) | |
| except Exception: | |
| pass | |
| # 5. co_branded_cards ์ฒ๋ฆฌ (์ ํด ์นด๋) | |
| co_branded_cards = program.get("co_branded_cards") | |
| if co_branded_cards: | |
| try: | |
| sub_chunks = handle_credit_cards(co_branded_cards, program_context) | |
| chunks.extend(sub_chunks) | |
| except Exception: | |
| pass | |
| # 6. best_rate_guarantee ์ฒ๋ฆฌ (BRG) | |
| brg = program.get("best_rate_guarantee") | |
| if brg: | |
| try: | |
| sub_chunks = handle_best_rate_guarantee(brg, program_context) | |
| chunks.extend(sub_chunks) | |
| except Exception: | |
| pass | |
| return chunks | |
| def handle_membership_tiers(data: Any, context: Dict[str, Any]) -> List[Dict[str, Any]]: | |
| """membership_tiers ์ฒ๋ฆฌ""" | |
| chunks = [] | |
| chain = context.get("chain", "UNKNOWN") | |
| if data is None: | |
| return chunks | |
| tiers = data if isinstance(data, list) else [data] | |
| for tier in tiers[:10]: | |
| if not isinstance(tier, dict): | |
| continue | |
| tier_name = tier.get("tier_name", "N/A") | |
| tier_level = tier.get("tier_level", "N/A") | |
| tier_chain = tier.get("chain", chain) | |
| # ์คํค๋ง ํ๋๋ช : qualification (TierQualification) | |
| # ํ์ ํธํ์ฑ: upgrade_requirement๋ ํด๋ฐฑ ์ง์ | |
| qualification = tier.get("qualification", tier.get("upgrade_requirement", {})) | |
| req_nights = qualification.get("nights_required", qualification.get("nights", "N/A")) | |
| req_stays = qualification.get("stays_required") | |
| req_points = qualification.get("points_required", qualification.get("points")) | |
| req_spending = qualification.get("spending_required") | |
| qual_period = qualification.get("qualification_period", "calendar_year") | |
| # ์คํค๋ง ํ๋๋ช : bonus_points_percentage | |
| bonus_pct = tier.get("bonus_points_percentage", tier.get("elite_bonus_points")) | |
| content = f""" | |
| ๋ฑ๊ธ: {tier_name} ({tier_level}) | |
| ์ฒด์ธ: {tier_chain} | |
| ๋ฑ๊ธ ์์: {tier.get("rank", "N/A")} | |
| ์๊ฒฉ ์๊ฑด: | |
| - ์๋ฐ: {req_nights}๋ฐ""" | |
| if req_stays: | |
| content += f"\n - ์ฒด๋ฅ: {req_stays}ํ" | |
| if req_points: | |
| content += f"\n - ํฌ์ธํธ: {req_points:,}P" if isinstance(req_points, int) else f"\n - ํฌ์ธํธ: {req_points}" | |
| if req_spending and isinstance(req_spending, dict): | |
| content += f"\n - ์ง์ถ: {req_spending.get('amount', 'N/A')} {req_spending.get('currency', '')}" | |
| content += f"\n - ์ฐ์ ๊ธฐ๊ฐ: {qual_period}\n" | |
| # ๋์ฒด ์๊ฒฉ ๊ฒฝ๋ก | |
| alt_paths = qualification.get("alternative_paths", []) | |
| if alt_paths: | |
| content += f" - ๋์ฒด ๊ฒฝ๋ก: {', '.join(alt_paths[:3])}\n" | |
| if bonus_pct: | |
| content += f"๋ณด๋์ค ํฌ์ธํธ: {bonus_pct}%\n" | |
| # ๋ฑ๊ธ ์ ์ง ์กฐ๊ฑด: retention (TierRetention) | |
| retention = tier.get("retention") | |
| if retention and isinstance(retention, dict): | |
| content += "\n๋ฑ๊ธ ์ ์ง ์กฐ๊ฑด:\n" | |
| ret_nights = retention.get("nights_required") | |
| ret_points = retention.get("points_required") | |
| if ret_nights: | |
| content += f" - ์๋ฐ: {ret_nights}๋ฐ\n" | |
| if ret_points: | |
| content += f" - ํฌ์ธํธ: {ret_points:,}P\n" | |
| # ํ์ ๋ฑ๊ธ | |
| if tier.get("lifetime_tier_available"): | |
| content += f"\nํ์ ๋ฑ๊ธ: ๊ฐ๋ฅ" | |
| if tier.get("lifetime_requirements"): | |
| content += f" ({tier.get('lifetime_requirements')})" | |
| content += "\n" | |
| # ์ฃผ์ ํํ | |
| benefits = tier.get("benefits", []) | |
| if benefits: | |
| content += "\n์ฃผ์ ํํ:\n" | |
| for benefit in benefits[:8]: | |
| if isinstance(benefit, dict): | |
| ben_name = benefit.get("name", benefit.get("benefit_name", "N/A")) | |
| ben_desc = benefit.get("description", "")[:80] | |
| content += f" - {ben_name}" | |
| if ben_desc: | |
| content += f": {ben_desc}" | |
| content += "\n" | |
| # evidence ์ ๋ณด ํฌํจ (์ ํ์ ) | |
| evidence = tier.get("evidence", []) | |
| if evidence: | |
| content += format_evidence(evidence, max_items=2) | |
| chunks.append({ | |
| "content": content.strip(), | |
| "metadata": { | |
| "type": "membership_tier", | |
| "tier_name": tier_name, | |
| "tier_level": tier_level, | |
| "rank": tier.get("rank") | |
| } | |
| }) | |
| return chunks | |
| # =========================================================================== | |
| # ํธ๋ค๋ฌ: ๋ง์ผ์คํค ํ๋ก๊ทธ๋จ | |
| # =========================================================================== | |
| def handle_milestone_programs(data: Any, context: Dict[str, Any]) -> List[Dict[str, Any]]: | |
| """milestone_program ์ฒ๋ฆฌ (IHG Milestone Rewards ๋ฑ)""" | |
| chunks = [] | |
| chain = context.get("chain", "UNKNOWN") | |
| programs = data if isinstance(data, list) else [data] | |
| for program in programs[:3]: | |
| if not isinstance(program, dict): | |
| continue | |
| program_name = program.get("program_name", "Milestone Rewards") | |
| program_chain = program.get("chain", chain) | |
| minimum_tier = program.get("minimum_tier", "N/A") | |
| reset_period = program.get("reset_period", "calendar_year") | |
| content = f""" | |
| ๋ง์ผ์คํค ํ๋ก๊ทธ๋จ: {program_name} | |
| ์ฒด์ธ: {program_chain} | |
| ์ฐธ์ฌ ์ต์ ๋ฑ๊ธ: {minimum_tier} | |
| ๋ฆฌ์ ์ฃผ๊ธฐ: {reset_period} | |
| ๋กค์ค๋ฒ ๊ฐ๋ฅ: {"์" if program.get("rollover_enabled") else "์๋์ค"} | |
| """ | |
| # ๋กค์ค๋ฒ ๊ฐ๋ฅ ๋ฑ๊ธ | |
| rollover_tiers = program.get("rollover_eligible_tiers", []) | |
| if rollover_tiers: | |
| content += f"๋กค์ค๋ฒ ๊ฐ๋ฅ ๋ฑ๊ธ: {', '.join(str(t) for t in rollover_tiers)}\n" | |
| # ๋ง์ผ์คํค ๋ชฉ๋ก | |
| milestones = program.get("milestones", []) | |
| if milestones: | |
| content += "\n๋ง์ผ์คํค ๋ณด์:\n" | |
| for milestone in milestones[:10]: | |
| if not isinstance(milestone, dict): | |
| continue | |
| nights = milestone.get("nights_required", "?") | |
| is_bonus = milestone.get("is_bonus_choice", False) | |
| max_sel = milestone.get("max_selections", 1) | |
| content += f"\n [{nights}๋ฐ ๋ฌ์ฑ]" | |
| if is_bonus: | |
| content += f" (Bonus Choice - {max_sel}๊ฐ ์ ํ)" | |
| content += ":\n" | |
| # ๋ณด์ ์ต์ | |
| options = milestone.get("reward_options", []) | |
| for opt in options[:6]: | |
| if isinstance(opt, dict): | |
| opt_name = opt.get("name", "N/A") | |
| bonus_pts = opt.get("bonus_points") | |
| quantity = opt.get("quantity") | |
| opt_str = f" - {opt_name}" | |
| if bonus_pts: | |
| opt_str += f" ({bonus_pts:,}P)" | |
| elif quantity: | |
| opt_str += f" ({quantity}๊ฐ)" | |
| content += opt_str + "\n" | |
| content += DISCLAIMER_SHORT | |
| chunks.append({ | |
| "content": content.strip(), | |
| "metadata": { | |
| "type": "milestone_program", | |
| "program_name": program_name, | |
| "chain": program_chain, | |
| "minimum_tier": str(minimum_tier) | |
| } | |
| }) | |
| return chunks | |
| # =========================================================================== | |
| # ํธ๋ค๋ฌ: ํํธ๋ ๋ฑ๊ธ ๋งค์นญ | |
| # =========================================================================== | |
| def handle_partner_status_matches(data: Any, context: Dict[str, Any]) -> List[Dict[str, Any]]: | |
| """partner_status_matches ์ฒ๋ฆฌ (ํํธ๋ ๋ฑ๊ธ ๋งค์นญ)""" | |
| chunks = [] | |
| chain = context.get("chain", "UNKNOWN") | |
| matches = data if isinstance(data, list) else [data] | |
| # ์ฌ๋ฌ ํํธ๋๋ฅผ ํ๋์ ์ฒญํฌ๋ก ๋ฌถ๊ธฐ | |
| if not matches: | |
| return chunks | |
| content = f""" | |
| ํํธ๋ ๋ฑ๊ธ ๋งค์นญ ํ๋ก๊ทธ๋จ | |
| ์ฒด์ธ: {chain} | |
| """ | |
| for match in matches[:10]: | |
| if not isinstance(match, dict): | |
| continue | |
| partner_name = match.get("partner_name", "N/A") | |
| partner_program = match.get("partner_program", "") | |
| partner_tier = match.get("partner_tier", "N/A") | |
| required_tier = match.get("required_hotel_tier", "N/A") | |
| validity = match.get("validity", "") | |
| content += f"โข {partner_name}" | |
| if partner_program: | |
| content += f" ({partner_program})" | |
| content += f"\n" | |
| content += f" - ํ์ ํธํ ๋ฑ๊ธ: {required_tier}\n" | |
| content += f" - ๋ถ์ฌ ํํธ๋ ๋ฑ๊ธ: {partner_tier}\n" | |
| if validity: | |
| content += f" - ์ ํจ ๊ธฐ๊ฐ: {validity}\n" | |
| # ํํธ๋ ํํ | |
| partner_benefits = match.get("partner_benefits", []) | |
| if partner_benefits: | |
| content += f" - ํํ: {', '.join(partner_benefits[:3])}\n" | |
| content += "\n" | |
| content += DISCLAIMER_SHORT | |
| chunks.append({ | |
| "content": content.strip(), | |
| "metadata": { | |
| "type": "partner_status_match", | |
| "chain": chain | |
| } | |
| }) | |
| return chunks | |
| # =========================================================================== | |
| # ํธ๋ค๋ฌ: ํฌ์ธํธ ์์คํ | |
| # =========================================================================== | |
| def handle_points_systems(data: Any, context: Dict[str, Any]) -> List[Dict[str, Any]]: | |
| """points_systems ๋๋ points_system ์ฒ๋ฆฌ""" | |
| chunks = [] | |
| chain = context.get("chain", "UNKNOWN") | |
| if data is None: | |
| return chunks | |
| systems = data if isinstance(data, list) else [data] | |
| for system in systems[:3]: | |
| if not isinstance(system, dict): | |
| continue | |
| try: | |
| point_name = system.get("point_name", system.get("points_name", "N/A")) | |
| content = f""" | |
| ํฌ์ธํธ ์์คํ : {point_name} | |
| ์ฒด์ธ: {system.get("chain", chain)} | |
| """ | |
| # ์คํค๋ง ํ๋๋ช : expiration_policy (PointExpirationPolicy) | |
| # ํ์ ํธํ์ฑ: expiry๋ ํด๋ฐฑ ์ง์ | |
| expiration = system.get("expiration_policy", system.get("expiry")) | |
| if expiration: | |
| if isinstance(expiration, dict): | |
| expires = expiration.get("expires", expiration.get("has_expiry", True)) | |
| exp_months = expiration.get("expiration_months", expiration.get("expiry_period")) | |
| can_extend = expiration.get("can_extend_with_activity", False) | |
| activity_types = expiration.get("activity_types", []) | |
| content += f"\n๋ง๋ฃ: {'์์' if expires else '์์'}" | |
| if exp_months: | |
| content += f" ({exp_months}๊ฐ์)" | |
| content += "\n" | |
| if can_extend: | |
| content += f"ํ๋์ผ๋ก ์ฐ์ฅ: ๊ฐ๋ฅ" | |
| if activity_types: | |
| content += f" ({', '.join(activity_types)})" | |
| content += "\n" | |
| else: | |
| content += f"\n๋ง๋ฃ: {expiration}\n" | |
| # ์ ๋ฆฝ ๊ท์น | |
| content += "\n์ ๋ฆฝ ๊ท์น:\n" | |
| earning_rules = system.get("earning_rules", []) | |
| if earning_rules and isinstance(earning_rules, list): | |
| for rule in earning_rules[:6]: | |
| if isinstance(rule, dict): | |
| rule_name = rule.get("rule_name", rule.get("name", "N/A")) | |
| category = rule.get("category", "") | |
| points_per_unit = rule.get("points_per_unit") | |
| spending_unit = rule.get("spending_unit", {}) | |
| content += f" - {rule_name}" | |
| if category: | |
| content += f" ({category})" | |
| content += ": " | |
| # ์คํค๋ง: points_per_unit, spending_unit (Money ๊ฐ์ฒด) | |
| if points_per_unit and spending_unit: | |
| # spending_unit์ด dict์ธ ๊ฒฝ์ฐ Money ๊ตฌ์กฐ ํ์ธ | |
| if isinstance(spending_unit, dict): | |
| unit_amount = spending_unit.get("amount", "N/A") | |
| unit_currency = spending_unit.get("currency", "") | |
| if unit_amount != "N/A" and unit_currency: | |
| content += f"{unit_amount} {unit_currency}๋น {points_per_unit}P" | |
| else: | |
| content += f"{points_per_unit}P" | |
| else: | |
| # spending_unit์ด ๋จ์ ๊ฐ์ธ ๊ฒฝ์ฐ (ํ์ ํธํ์ฑ) | |
| content += f"{spending_unit}๋น {points_per_unit}P" | |
| elif rule.get("rate"): | |
| content += f"{rule.get('rate')}" | |
| else: | |
| content += "N/A" | |
| content += "\n" | |
| # ํ๋ | |
| monthly_cap = rule.get("monthly_cap") | |
| yearly_cap = rule.get("yearly_cap") | |
| if monthly_cap: | |
| content += f" ์ ํ๋: {monthly_cap:,}P\n" | |
| if yearly_cap: | |
| content += f" ์ฐ ํ๋: {yearly_cap:,}P\n" | |
| # ์ฌ์ฉ ๊ท์น | |
| redemption_rules = system.get("redemption_rules", []) | |
| if redemption_rules: | |
| content += "\n์ฌ์ฉ ๊ท์น:\n" | |
| for rule in redemption_rules[:3]: | |
| if isinstance(rule, dict): | |
| r_type = rule.get("redemption_type", "N/A") | |
| min_points = rule.get("minimum_points", "N/A") | |
| content += f" - {r_type}: ์ต์ {min_points:,}P\n" if isinstance(min_points, int) else f" - {r_type}: ์ต์ {min_points}P\n" | |
| # ์ ํ ํํธ๋ | |
| transfer_partners = system.get("transfer_partners", []) | |
| if transfer_partners: | |
| content += f"\n์ ํ ํํธ๋: {', '.join(transfer_partners[:5])}\n" | |
| chunks.append({ | |
| "content": content.strip(), | |
| "metadata": { | |
| "type": "points_system", | |
| "point_name": point_name | |
| } | |
| }) | |
| except Exception as e: | |
| # ์ค๋ฅ ๋ฐ์์ ์คํตํ๊ณ ๊ณ์ | |
| continue | |
| return chunks | |
| # =========================================================================== | |
| # ํธ๋ค๋ฌ: ํธํ ํ๋กํผํฐ | |
| # =========================================================================== | |
| def handle_hotel_properties(data: Any, context: Dict[str, Any]) -> List[Dict[str, Any]]: | |
| """hotel_properties ์ฒ๋ฆฌ""" | |
| chunks = [] | |
| chain = context.get("chain", "UNKNOWN") | |
| hotels = data if isinstance(data, list) else [data] | |
| for hotel in hotels[:5]: | |
| if not isinstance(hotel, dict): | |
| continue | |
| hotel_name = hotel.get("name", "Unknown Hotel") | |
| hotel_chain = hotel.get("chain", chain) | |
| location = hotel.get("location", {}) | |
| # ํ๊ตญ์ด ์ด๋ฆ ์ถ์ถ | |
| name_localized = hotel.get("name_localized", {}) | |
| name_ko = name_localized.get("ko") if isinstance(name_localized, dict) else None | |
| content = f"ํธํ ๋ช : {hotel_name}\n" | |
| if name_ko and name_ko != hotel_name: | |
| content += f"ํธํ ๋ช (ํ๊ตญ์ด): {name_ko}\n" | |
| content += f"""์ฒด์ธ: {hotel_chain} | |
| ๋ธ๋๋: {hotel.get('brand_code', 'N/A')} | |
| ์นดํ ๊ณ ๋ฆฌ: {hotel.get('category', 'N/A')} | |
| ๊ฐ์ค ์: {hotel.get('room_count', 'N/A')}์ค | |
| ์ฃผ์: {location.get('address', 'N/A')} | |
| ๋์: {location.get('city', 'N/A')} | |
| ๊ตญ๊ฐ: {location.get('country', 'N/A')} | |
| """ | |
| # ๋ ์คํ ๋ | |
| restaurants = hotel.get("restaurant_names", []) | |
| if restaurants: | |
| content += f"๋ ์คํ ๋: {', '.join(restaurants)}\n" | |
| # ๋ผ์ด์ง | |
| if hotel.get("lounge_name"): | |
| content += f"๋ผ์ด์ง: {hotel.get('lounge_name')}\n" | |
| # ์ฐธ์ฌ ์ฑ๋ (FHR, Virtuoso ๋ฑ) | |
| participating_channels = hotel.get("participating_channels", []) | |
| if participating_channels: | |
| content += f"์ฐธ์ฌ ์ฑ๋: {', '.join(participating_channels)}\n" | |
| # ํธํ ์๋ณ์ ๋ฐ ์์น ์ ๋ณด ์ถ์ถ | |
| hotel_id = hotel.get("hotel_id") | |
| country = location.get("country") if isinstance(location, dict) else None | |
| city = location.get("city") if isinstance(location, dict) else None | |
| region = hotel.get("region") | |
| # ๋ฉํ๋ฐ์ดํฐ ๊ตฌ์ฑ (hotel_id, chain, country ํฌํจ์ผ๋ก ํํฐ๋ง ๊ฐํ) | |
| metadata = { | |
| "type": "hotel_property", | |
| "hotel_name": hotel_name, | |
| "hotel_name_ko": name_ko, | |
| "brand": hotel.get("brand_code"), | |
| "chain": hotel_chain, | |
| } | |
| if hotel_id: | |
| metadata["hotel_id"] = hotel_id | |
| if country: | |
| metadata["country"] = country | |
| if city: | |
| metadata["city"] = city | |
| if region: | |
| metadata["region"] = region | |
| chunks.append({ | |
| "content": content.strip(), | |
| "metadata": metadata | |
| }) | |
| return chunks | |
| # =========================================================================== | |
| # ํธ๋ค๋ฌ: ํธํ ๋ธ๋๋ | |
| # =========================================================================== | |
| def handle_hotel_brands(data: Any, context: Dict[str, Any]) -> List[Dict[str, Any]]: | |
| """hotel_brands ์ฒ๋ฆฌ""" | |
| chunks = [] | |
| chain = context.get("chain", "UNKNOWN") | |
| brands = data if isinstance(data, list) else [data] | |
| for brand in brands[:10]: | |
| if not isinstance(brand, dict): | |
| continue | |
| brand_code = brand.get("brand_code", "N/A") | |
| brand_name = brand.get("brand_name", "N/A") | |
| brand_chain = brand.get("chain", chain) | |
| tier = brand.get("tier", "N/A") | |
| # ํ๊ตญ์ด ์ด๋ฆ | |
| name_localized = brand.get("brand_name_localized", {}) | |
| name_ko = name_localized.get("ko") if isinstance(name_localized, dict) else None | |
| content = f""" | |
| ํธํ ๋ธ๋๋: {brand_name} | |
| """ | |
| if name_ko and name_ko != brand_name: | |
| content += f"๋ธ๋๋๋ช (ํ๊ตญ์ด): {name_ko}\n" | |
| content += f"""๋ธ๋๋ ์ฝ๋: {brand_code} | |
| ์ฒด์ธ: {brand_chain} | |
| ํฐ์ด: {tier} | |
| """ | |
| # ๊ธฐ๋ณธ ํํ | |
| default_benefits = brand.get("default_benefits", []) | |
| if default_benefits: | |
| content += "\n๋ธ๋๋ ๊ธฐ๋ณธ ํํ:\n" | |
| for benefit in default_benefits[:5]: | |
| if isinstance(benefit, dict): | |
| ben_name = benefit.get("name", "N/A") | |
| content += f" - {ben_name}\n" | |
| chunks.append({ | |
| "content": content.strip(), | |
| "metadata": { | |
| "type": "hotel_brand", | |
| "brand_code": brand_code, | |
| "brand_name": brand_name, | |
| "tier": str(tier) | |
| } | |
| }) | |
| return chunks | |
| # =========================================================================== | |
| # ํธ๋ค๋ฌ: ๋ฑ๊ธ๋ณ ๊ตฌํ | |
| # =========================================================================== | |
| def handle_tier_implementations(data: Any, context: Dict[str, Any]) -> List[Dict[str, Any]]: | |
| """tier_implementations ์ฒ๋ฆฌ - ๊ตญ๊ฐ/ํธํ ๋ช ์ ๋ณด ํฌํจ์ผ๋ก ๊ฒ์ ์ ํ๋ ํฅ์""" | |
| chunks = [] | |
| chain = context.get("chain", "UNKNOWN") | |
| hotel_id_map = context.get("hotel_id_map", {}) # hotel_id โ ํธํ ์ ๋ณด ๋งคํ | |
| impls = data if isinstance(data, list) else [data] | |
| for impl in impls[:10]: | |
| if not isinstance(impl, dict): | |
| continue | |
| hotel_id = impl.get("hotel_id", "Unknown") | |
| tier_level = impl.get("tier_level", "N/A") | |
| impl_chain = impl.get("chain", chain) | |
| # hotel_id_map์์ ํธํ ์ ๋ณด ์กฐํ (์ฐ์ ) | |
| hotel_info = hotel_id_map.get(hotel_id, {}) | |
| hotel_name = hotel_info.get("name", "") | |
| hotel_name_ko = hotel_info.get("name_ko", "") | |
| country = hotel_info.get("country", "") | |
| city = hotel_info.get("city", "") | |
| # extra_attributes์์ ๋ณด์ (ํด๋ฐฑ) | |
| extra = impl.get("extra_attributes", {}) | |
| if not hotel_name: | |
| hotel_name = extra.get("hotel_name", "") | |
| if not country: | |
| country = extra.get("country", "") | |
| region = extra.get("region", "") | |
| content = f""" | |
| ํธํ ๋ณ ๋ฉค๋ฒ์ญ ํํ ๊ตฌํ | |
| """ | |
| # ์ค์ ํธํ ๋ช ์ด ์์ผ๋ฉด ์ฐ์ ํ์ | |
| if hotel_name: | |
| content += f"ํธํ : {hotel_name}\n" | |
| if hotel_name_ko and hotel_name_ko != hotel_name: | |
| content += f"ํธํ (ํ๊ธ๋ช ): {hotel_name_ko}\n" | |
| content += f"ํธํ ID: {hotel_id}\n" | |
| # ๊ตญ๊ฐ/์ง์ญ ์ ๋ณด (๊ฒ์ ๋งค์นญ์ ์ค์) | |
| if country: | |
| content += f"๊ตญ๊ฐ: {country}\n" | |
| if city: | |
| content += f"๋์: {city}\n" | |
| if region: | |
| content += f"์ง์ญ: {region}\n" | |
| content += f"""์ฒด์ธ: {impl_chain} | |
| ๋ฑ๊ธ: {tier_level} | |
| ๋ผ์ด์ง ์ด์ฉ: {'๊ฐ๋ฅ' if impl.get('lounge_access') else '๋ถ๊ฐ'} | |
| ์กฐ์ ํฌํจ: {'ํฌํจ' if impl.get('breakfast_included') else '๋ฏธํฌํจ'} | |
| ์กฐ์ ์ฅ์: {impl.get('breakfast_venue', 'N/A')} | |
| ํํ ์์ธ: | |
| """ | |
| # ์ ๊ทธ๋ ์ด๋ ์ ์ฑ (upgrade_policy, suite_upgrade_available) | |
| upgrade_policy = impl.get("upgrade_policy") | |
| if upgrade_policy: | |
| content += f"์ ๊ทธ๋ ์ด๋ ์ ์ฑ : {upgrade_policy}\n" | |
| if impl.get("suite_upgrade_available"): | |
| content += "์ค์ํธ ์ ๊ทธ๋ ์ด๋: ๊ฐ๋ฅ โจ\n" | |
| # ์กฐ์ ์์ธ (breakfast_value) | |
| breakfast_value = impl.get("breakfast_value") | |
| if isinstance(breakfast_value, dict): | |
| amount = breakfast_value.get("amount") | |
| currency = breakfast_value.get("currency", "") | |
| if amount: | |
| content += f"์กฐ์ ๊ฐ์น: {amount:,} {currency}\n" | |
| # ๋ผ์ด์ง ์๊ฐ (lounge_hours) | |
| lounge_hours = impl.get("lounge_hours") | |
| if lounge_hours: | |
| content += f"๋ผ์ด์ง ์ด์: {lounge_hours}\n" | |
| content += "\nํํ ์์ธ:\n" | |
| # ํํ ์ค๋ฒ๋ผ์ด๋ | |
| overrides = impl.get("benefit_overrides", []) | |
| for override in overrides[:5]: | |
| if isinstance(override, dict): | |
| category = override.get("benefit_category", "N/A") | |
| detail = override.get("implementation_detail", "")[:150] | |
| content += f"- {category}: {detail}\n" | |
| # ํ ์ถ๊ฐ | |
| tips = override.get("tips", []) | |
| for tip in tips[:2]: | |
| content += f" โ {tip}\n" | |
| # ๊ฒ์ฆ ์ ๋ณด (last_verified) - ์ ๋ณด ์ ์ ๋ ํ์ | |
| last_verified = impl.get("last_verified") | |
| if last_verified: | |
| content += f"\n๋ง์ง๋ง ํ์ธ: {last_verified}\n" | |
| content += DISCLAIMER_SHORT | |
| # ๋ฉํ๋ฐ์ดํฐ์๋ ๊ตญ๊ฐ ์ ๋ณด ํฌํจ | |
| metadata = { | |
| "type": "tier_implementation", | |
| "hotel_id": hotel_id, | |
| "tier_level": tier_level | |
| } | |
| if country: | |
| metadata["country"] = country | |
| if hotel_name: | |
| metadata["hotel_name"] = hotel_name | |
| chunks.append({ | |
| "content": content.strip(), | |
| "metadata": metadata | |
| }) | |
| return chunks | |
| # =========================================================================== | |
| # ํธ๋ค๋ฌ: ํฌ๋ ๋ง ์นด๋ | |
| # =========================================================================== | |
| def handle_credit_cards(data: Any, context: Dict[str, Any]) -> List[Dict[str, Any]]: | |
| """credit_cards ์ฒ๋ฆฌ (ํ์ฅ: Phase 3 - ์ฌํ ํนํ ํํ ํฌํจ)""" | |
| chunks = [] | |
| chain = context.get("chain", "UNKNOWN") | |
| cards = data if isinstance(data, list) else [data] | |
| for card in cards[:5]: | |
| if not isinstance(card, dict): | |
| continue | |
| card_name = card.get("card_name", "N/A") | |
| card_chain = card.get("chain", chain) | |
| card_type = card.get("card_type", "") | |
| issuer = card.get("issuer", "N/A") | |
| issuer_code = card.get("issuer_code", "") | |
| # ์คํค๋ง ๊ตฌ์กฐ: AnnualFee.total (Money ๊ฐ์ฒด) | |
| # ํ์ ํธํ์ฑ: ๋ ๊ฑฐ์ annual_fee.amount๋ ์ง์ | |
| annual_fee = card.get("annual_fee", {}) | |
| if isinstance(annual_fee, dict): | |
| # ์คํค๋ง ๊ตฌ์กฐ: annual_fee.total.amount | |
| total_fee = annual_fee.get("total", annual_fee) # ํด๋ฐฑ: ์ง์ ์ ๊ทผ | |
| if isinstance(total_fee, dict): | |
| fee_amount = total_fee.get("amount", "N/A") | |
| fee_currency = total_fee.get("currency", "KRW") | |
| else: | |
| fee_amount = annual_fee.get("amount", "N/A") | |
| fee_currency = annual_fee.get("currency", "KRW") | |
| # ์์ธ ์ฐํ๋น ์ ๋ณด | |
| base_fee = annual_fee.get("base_fee", {}) | |
| partner_fee = annual_fee.get("partner_fee", {}) | |
| family_fee = annual_fee.get("family_card_fee", {}) | |
| waiver_conds = annual_fee.get("waiver_conditions", []) | |
| else: | |
| fee_amount = annual_fee | |
| fee_currency = "KRW" | |
| base_fee = partner_fee = family_fee = {} | |
| waiver_conds = [] | |
| content = f""" | |
| ์ ์ฉ์นด๋: {card_name} | |
| ๋ฐ๊ธ์ฌ: {issuer}""" | |
| if issuer_code: | |
| content += f" ({issuer_code})" | |
| # ์ฐํ๋น ํ์ | |
| if isinstance(fee_amount, (int, float)): | |
| content += f"\n์ฐํ๋น: {fee_amount:,} {fee_currency}\n" | |
| else: | |
| content += f"\n์ฐํ๋น: {fee_amount} {fee_currency}\n" | |
| # ์์ธ ์ฐํ๋น (์คํค๋ง ๊ตฌ์กฐ ๋ฐ์) | |
| if base_fee and isinstance(base_fee, dict) and base_fee.get("amount"): | |
| content += f" - ๊ธฐ๋ณธ: {base_fee.get('amount'):,} {base_fee.get('currency', '')}\n" | |
| if partner_fee and isinstance(partner_fee, dict) and partner_fee.get("amount"): | |
| content += f" - ์ ํด: {partner_fee.get('amount'):,} {partner_fee.get('currency', '')}\n" | |
| if family_fee and isinstance(family_fee, dict) and family_fee.get("amount"): | |
| content += f" - ๊ฐ์กฑ์นด๋: {family_fee.get('amount'):,} {family_fee.get('currency', '')}\n" | |
| if waiver_conds: | |
| # Handle both list of strings and list of dicts | |
| waiver_strs = [] | |
| for item in waiver_conds[:2]: | |
| if isinstance(item, dict): | |
| waiver_strs.append(item.get("summary", item.get("condition", str(item)))) | |
| else: | |
| waiver_strs.append(str(item)) | |
| content += f" - ๋ฉด์ ์กฐ๊ฑด: {', '.join(waiver_strs)}\n" | |
| # ์นด๋ ์ ํ (Phase 3) | |
| if card_type: | |
| type_labels = { | |
| "HOTEL_PLCC": "ํธํ ์ ํด์นด๋", | |
| "AIRLINE_PLCC": "ํญ๊ณต ์ ํด์นด๋", | |
| "TRAVEL_PREMIUM": "ํ๋ฆฌ๋ฏธ์ ์ฌํ์นด๋", | |
| "GENERAL_REWARDS": "๋ฆฌ์๋ ์นด๋" | |
| } | |
| content += f"์นด๋ ์ ํ: {type_labels.get(card_type, card_type)}\n" | |
| # ์ฐ๊ณ ํ๋ก๊ทธ๋จ | |
| if card_chain and card_chain != "OTHER": | |
| content += f"์ฐ๊ณ ํธํ : {card_chain}\n" | |
| linked_airline = card.get("linked_airline") | |
| linked_program = card.get("linked_airline_program") | |
| if linked_airline: | |
| content += f"์ฐ๊ณ ํญ๊ณต์ฌ: {linked_airline}\n" | |
| if linked_program: | |
| content += f"๋ง์ผ๋ฆฌ์ง ํ๋ก๊ทธ๋จ: {linked_program}\n" | |
| # ๋ฑ๊ธ ํํ | |
| granted_tier = card.get("granted_membership_tier") | |
| if granted_tier: | |
| content += f"์ ๊ณต ํธํ ๋ฑ๊ธ: {granted_tier}\n" | |
| granted_airline_tier = card.get("granted_airline_tier") | |
| if granted_airline_tier: | |
| content += f"์ ๊ณต ํญ๊ณต ๋ฑ๊ธ: {granted_airline_tier}\n" | |
| # ๋ง์ผ๋ฆฌ์ง ์ ๋ฆฝ (Phase 3) | |
| miles_rate = card.get("miles_earning_rate") | |
| if miles_rate: | |
| content += f"๋ง์ผ๋ฆฌ์ง ์ ๋ฆฝ: {miles_rate}\n" | |
| bonus_categories = card.get("bonus_miles_categories", []) | |
| if bonus_categories: | |
| content += f"๋ณด๋์ค ์ ๋ฆฝ ์นดํ ๊ณ ๋ฆฌ: {', '.join(bonus_categories)}\n" | |
| # ๊ฐ์ /๊ธฐ๋ ์ผ ๋ณด๋์ค | |
| welcome_bonus = card.get("welcome_bonus") | |
| welcome_points = card.get("welcome_bonus_points") | |
| welcome_miles = card.get("welcome_bonus_miles") | |
| if welcome_bonus or welcome_points or welcome_miles: | |
| content += "\n๊ฐ์ ํํ:\n" | |
| if welcome_bonus: | |
| content += f" - {welcome_bonus}\n" | |
| if welcome_points: | |
| content += f" - ๊ฐ์ ๋ณด๋์ค: {welcome_points:,} ํฌ์ธํธ\n" | |
| if welcome_miles: | |
| content += f" - ๊ฐ์ ๋ณด๋์ค: {welcome_miles:,} ๋ง์ผ\n" | |
| # ๋ผ์ด์ง ํํ (Phase 3) | |
| lounge_access = card.get("lounge_access", []) | |
| lounge_visits = card.get("lounge_visits_per_year") | |
| lounge_guest = card.get("lounge_guest_allowed", False) | |
| if lounge_access or lounge_visits: | |
| content += "\n๋ผ์ด์ง ํํ:\n" | |
| if lounge_access: | |
| # Handle both list of strings and list of dicts | |
| lounge_names = [] | |
| for item in lounge_access[:5]: | |
| if isinstance(item, dict): | |
| lounge_names.append(item.get("lounge_name", item.get("name", str(item)))) | |
| else: | |
| lounge_names.append(str(item)) | |
| content += f" - ์ด์ฉ ๊ฐ๋ฅ: {', '.join(lounge_names)}\n" | |
| if lounge_visits: | |
| content += f" - ์ฐ๊ฐ ์ด์ฉ: {lounge_visits}ํ\n" | |
| if lounge_guest: | |
| content += f" - ๋๋ฐ์ ์ด์ฉ: ๊ฐ๋ฅ\n" | |
| # ๊ณตํญ ์๋น์ค (Phase 3) | |
| airport_services = [] | |
| if card.get("airport_valet"): | |
| airport_services.append("๋ฐ๋ ") | |
| if card.get("airport_fast_track"): | |
| airport_services.append("ํจ์คํธํธ๋") | |
| if card.get("airport_transfer"): | |
| airport_services.append("๊ณตํญ ์๋ฉ") | |
| if airport_services: | |
| content += f"\n๊ณตํญ ์๋น์ค: {', '.join(airport_services)}\n" | |
| # ์ฌํ ๋ณดํ (Phase 3) | |
| if card.get("travel_insurance"): | |
| insurance_detail = card.get("travel_insurance_details", "์ฌํ์ ๋ณดํ ํฌํจ") | |
| content += f"์ฌํ ๋ณดํ: {insurance_detail}\n" | |
| # ์ปจ์์ด์ง/ํธํ ์๋น์ค (Phase 3) | |
| if card.get("concierge_service"): | |
| content += "์ปจ์์ด์ง: 24์๊ฐ ์ด์ฉ ๊ฐ๋ฅ\n" | |
| hotel_status = card.get("hotel_status_benefit") | |
| if hotel_status: | |
| content += f"ํธํ ํน๋ณ ํํ: {hotel_status}\n" | |
| # ๊ธฐ์กด ํํ ๋ชฉ๋ก | |
| benefits = card.get("benefits", card.get("card_benefits", [])) | |
| if benefits: | |
| content += "\n์ฃผ์ ํํ:\n" | |
| for benefit in benefits[:7]: | |
| if isinstance(benefit, dict): | |
| ben_name = benefit.get('name', benefit.get('benefit_name', 'N/A')) | |
| content += f" - {ben_name}\n" | |
| elif isinstance(benefit, str): | |
| content += f" - {benefit}\n" | |
| # extra_attributes ์ฃผ์ ์ ๋ณด ์ถ๋ ฅ (๊ฒ์ ๋๋ฝ ๋ฐฉ์ง) | |
| extra_attrs = card.get("extra_attributes", {}) | |
| if isinstance(extra_attrs, dict): | |
| target_members = extra_attrs.get("target_members", []) | |
| benefit_period = extra_attrs.get("benefit_period", "") | |
| hotel_count = extra_attrs.get("participating_hotel_count") | |
| if target_members or benefit_period or hotel_count: | |
| content += "\nํ๋ก๊ทธ๋จ ์ ๋ณด:\n" | |
| if target_members: | |
| if isinstance(target_members, list): | |
| content += f" - ๋์ ํ์: {', '.join(target_members[:3])}\n" | |
| else: | |
| content += f" - ๋์ ํ์: {target_members}\n" | |
| if benefit_period: | |
| content += f" - ํํ ๊ธฐ๊ฐ: {benefit_period}\n" | |
| if hotel_count: | |
| content += f" - ์ ํด ํธํ : {hotel_count}๊ฐ\n" | |
| # evidence ์ ๋ณด ํฌํจ (์ ํ์ ) | |
| evidence = card.get("evidence", []) | |
| if evidence: | |
| content += format_evidence(evidence, max_items=2) | |
| content += DISCLAIMER_SHORT | |
| # ๋ฉํ๋ฐ์ดํฐ์ ์ถ๊ฐ ์ ๋ณด ํฌํจ | |
| metadata = { | |
| "type": "credit_card", | |
| "card_name": card_name, | |
| "issuer": issuer | |
| } | |
| if card_type: | |
| metadata["card_type"] = card_type | |
| if linked_airline: | |
| metadata["linked_airline"] = linked_airline | |
| chunks.append({ | |
| "content": content.strip(), | |
| "metadata": metadata | |
| }) | |
| return chunks | |
| # =========================================================================== | |
| # ํธ๋ค๋ฌ: ๊ตฌ๋ ํ๋ก๊ทธ๋จ | |
| # =========================================================================== | |
| def handle_subscription_programs(data: Any, context: Dict[str, Any]) -> List[Dict[str, Any]]: | |
| """subscription_programs ์ฒ๋ฆฌ""" | |
| chunks = [] | |
| chain = context.get("chain", "UNKNOWN") | |
| programs = data if isinstance(data, list) else [data] | |
| for program in programs[:3]: | |
| if not isinstance(program, dict): | |
| continue | |
| program_name = program.get("program_name", "N/A") | |
| content = f""" | |
| ๊ตฌ๋ ํ๋ก๊ทธ๋จ: {program_name} | |
| ์ฒด์ธ: {program.get('chain', chain)} | |
| ๊ธฐ๋ณธ ๋ฉค๋ฒ์ญ ํ์: {program.get('requires_base_membership', 'N/A')} | |
| ๊ธฐ๋ณธ ํ๋ก๊ทธ๋จ: {program.get('base_program_name', 'N/A')} | |
| """ | |
| # ํ๋ (์คํค๋ง: SubscriptionPlan.prices -> SubscriptionPlanPrice) | |
| plans = program.get("plans", []) | |
| for plan in plans[:5]: | |
| if isinstance(plan, dict): | |
| plan_name = plan.get("plan_name", "N/A") | |
| prices = plan.get("prices", []) | |
| price_str = "" | |
| # ๊ฐ๊ฒฉ ์ฒ๋ฆฌ ๊ฐ์ (์คํค๋ง์ ๋ง์ถค) | |
| if prices: | |
| for p in prices[:1]: # ์ฒซ ๋ฒ์งธ ๊ฐ๊ฒฉ๋ง | |
| if isinstance(p, dict): | |
| billing_cycle = p.get("billing_cycle", "YEARLY") | |
| price_info = p.get("price", {}) | |
| if isinstance(price_info, dict): | |
| amount = price_info.get("amount", "N/A") | |
| currency = price_info.get("currency", "") | |
| # ๊ฒฐ์ ์ฃผ๊ธฐ๋ฅผ ํ๊ตญ์ด๋ก ๋ณํ | |
| cycle_kr = { | |
| "YEARLY": "๋ ", | |
| "MONTHLY": "์", | |
| "QUARTERLY": "๋ถ๊ธฐ", | |
| "WEEKLY": "์ฃผ" | |
| }.get(billing_cycle, billing_cycle.lower()) | |
| if amount != "N/A" and currency: | |
| price_str = f"{amount} {currency}/{cycle_kr}" | |
| else: | |
| price_str = "๊ฐ๊ฒฉ ์ ๋ณด ์์" | |
| else: | |
| price_str = str(price_info) if price_info else "๊ฐ๊ฒฉ ์ ๋ณด ์์" | |
| content += f"\nํ๋: {plan_name}\n๊ฐ๊ฒฉ: {price_str}\n" | |
| # ํ๋ ํํ | |
| plan_benefits = plan.get("benefits", []) | |
| if plan_benefits: | |
| content += "ํํ:\n" | |
| for pb in plan_benefits[:3]: | |
| if isinstance(pb, dict): | |
| content += f"- {pb.get('name', 'N/A')}\n" | |
| chunks.append({ | |
| "content": content.strip(), | |
| "metadata": { | |
| "type": "subscription_program", | |
| "program_name": program_name | |
| } | |
| }) | |
| return chunks | |
| # =========================================================================== | |
| # ํธ๋ค๋ฌ: ํ๋ก๋ชจ์ | |
| # =========================================================================== | |
| def handle_promotions(data: Any, context: Dict[str, Any]) -> List[Dict[str, Any]]: | |
| """promotions ์ฒ๋ฆฌ""" | |
| chunks = [] | |
| chain = context.get("chain", "UNKNOWN") | |
| promos = data if isinstance(data, list) else [data] | |
| for promo in promos[:5]: | |
| if not isinstance(promo, dict): | |
| continue | |
| title = promo.get("title", "N/A") | |
| content = f""" | |
| ํ๋ก๋ชจ์ : {title} | |
| ์ฒด์ธ: {promo.get('chain', chain)} | |
| ๋์: {promo.get('target_audience', 'N/A')} | |
| ๊ธฐ๊ฐ: {promo.get('valid_from', 'N/A')} ~ {promo.get('valid_until', 'N/A')} | |
| ์ง์ญ: {', '.join(promo.get('regions', []) or ['N/A'])} | |
| ์ค๋ช : {promo.get('description', 'N/A')} | |
| """ | |
| chunks.append({ | |
| "content": content.strip(), | |
| "metadata": { | |
| "type": "promotion", | |
| "title": title | |
| } | |
| }) | |
| return chunks | |
| # =========================================================================== | |
| # ํธ๋ค๋ฌ: ๊ฐ๊ฒฉ/ํฌ์ธํธ (pricing, pricing_analysis) | |
| # =========================================================================== | |
| def handle_pricing(data: Any, context: Dict[str, Any]) -> List[Dict[str, Any]]: | |
| """pricing, pricing_2026 ๋ฑ ๋ค์ํ ํ์ ์ฒ๋ฆฌ""" | |
| chunks = [] | |
| hotel_name, hotel_name_ko, chain = get_hotel_info(context) | |
| if data is None: | |
| return chunks | |
| # --- ๋ฆฌ์คํธ ๊ตฌ์กฐ ์ฒ๋ฆฌ (raw_pricing_data ๋ฑ) --- | |
| if isinstance(data, list): | |
| for item in data[:10]: | |
| if not isinstance(item, dict): | |
| continue | |
| room_type = item.get("room_type_code", item.get("room_type", "")) | |
| room_name = item.get("room_type_full_name", "") | |
| rate_type = item.get("rate_type", "") | |
| rate_krw = item.get("rate_krw", 0) | |
| rate_usd = item.get("rate_usd_approx", 0) | |
| content = format_hotel_header(hotel_name, hotel_name_ko, chain) | |
| content += f"๊ฐ์ค ์ ํ: {room_type}\n" | |
| if room_name: | |
| content += f"๊ฐ์ค๋ช : {room_name}\n" | |
| if rate_type: | |
| content += f"์๊ธ ์ ํ: {rate_type}\n" | |
| content += f"๊ฐ๊ฒฉ:\n" | |
| if rate_krw: | |
| content += f" - KRW: โฉ{rate_krw:,}\n" | |
| if rate_usd: | |
| content += f" - USD: ${rate_usd}\n" | |
| content += DISCLAIMER_PRICE | |
| chunks.append({ | |
| "content": content.strip(), | |
| "metadata": { | |
| "type": "room_pricing_raw", | |
| "hotel_name": hotel_name, | |
| "room_type": room_type, | |
| "rate_krw": rate_krw | |
| } | |
| }) | |
| return chunks | |
| # ๋์ ๋๋ฆฌ ๊ตฌ์กฐ๊ฐ ์๋๋ฉด ๋น ๋ฐฐ์ด ๋ฐํ | |
| if not isinstance(data, dict): | |
| return chunks | |
| # --- ํ์ 1: annual_statistics ๊ตฌ์กฐ (Westin ๋ฑ) --- | |
| annual_stats = data.get("annual_statistics", {}) | |
| points_stats = annual_stats.get("points", {}) | |
| if points_stats: | |
| min_points = points_stats.get("minimum", {}) | |
| max_points = points_stats.get("maximum", {}) | |
| avg_points = points_stats.get("average_estimate") | |
| cash_stats = annual_stats.get("cash", {}) | |
| content = format_hotel_header(hotel_name, hotel_name_ko, chain) | |
| content += f"""์์ฝ ์ ํ: ํฌ์ธํธ ์๋ฐ | |
| ํฌ์ธํธ ํ์๋: | |
| - ์ต์: {min_points.get('points', 'N/A'):,}P ({min_points.get('period', '๋น์๊ธฐ ํ์ผ')}) | |
| - ์ต๋: {max_points.get('points', 'N/A'):,}P ({max_points.get('period', '์ฑ์๊ธฐ/์ฃผ๋ง')}) | |
| """ | |
| if avg_points: | |
| content += f" - ํ๊ท : {avg_points:,}P (์ถ์ )\n" | |
| if cash_stats: | |
| min_cash = cash_stats.get("minimum", {}) | |
| content += f""" | |
| ํ๊ธ ๊ฐ๊ฒฉ ๋น๊ต: | |
| - ์ต์ ๊ฐ: โฉ{min_cash.get('amount', 'N/A'):,} ({min_cash.get('period', '')}) | |
| """ | |
| blackout = data.get("points_blackout_dates", []) | |
| if blackout: | |
| content += f"\nํฌ์ธํธ ์์ฝ ๋ถ๊ฐ ๊ธฐ๊ฐ: {', '.join(blackout[:5])}" | |
| content += DISCLAIMER_PRICE | |
| chunks.append({ | |
| "content": content.strip(), | |
| "metadata": { | |
| "type": "points_pricing", | |
| "hotel_name": hotel_name, | |
| "hotel_name_ko": hotel_name_ko, | |
| "min_points": min_points.get("points"), | |
| "max_points": max_points.get("points") | |
| } | |
| }) | |
| # --- ํ์ 2: points_rates ๋ฐฐ์ด ๊ตฌ์กฐ (JW Marriott ๋ฑ) --- | |
| points_rates = data.get("points_rates", []) | |
| if points_rates: | |
| # ํฌ์ธํธ ๋ฒ์ ๊ณ์ฐ | |
| all_min = [] | |
| all_max = [] | |
| for rate in points_rates: | |
| if rate.get("min_points"): | |
| all_min.append(rate["min_points"]) | |
| if rate.get("max_points"): | |
| all_max.append(rate["max_points"]) | |
| min_points = min(all_min) if all_min else None | |
| max_points = max(all_max) if all_max else None | |
| content = format_hotel_header(hotel_name, hotel_name_ko, chain) | |
| content += f"""์์ฝ ์ ํ: ํฌ์ธํธ ์๋ฐ | |
| ํฌ์ธํธ ํ์๋: | |
| - ์ต์: {min_points:,}P (์ฐ๊ฐ) | |
| - ์ต๋: {max_points:,}P (์ฐ๊ฐ) | |
| """ | |
| # ํ๊ธ ๊ฐ๊ฒฉ๋ ์์ผ๋ฉด ์ถ๊ฐ | |
| cash_rates = data.get("cash_rates", []) | |
| if cash_rates: | |
| all_cash_min = [r.get("min_krw") for r in cash_rates if r.get("min_krw")] | |
| all_cash_max = [r.get("max_krw") for r in cash_rates if r.get("max_krw")] | |
| if all_cash_min: | |
| content += f""" | |
| ํ๊ธ ๊ฐ๊ฒฉ ๋น๊ต: | |
| - ์ต์ ๊ฐ: โฉ{min(all_cash_min):,} (์ฐ๊ฐ) | |
| - ์ต๊ณ ๊ฐ: โฉ{max(all_cash_max):,} (์ฐ๊ฐ) | |
| """ | |
| # ์๋ณ ์์ฝ ์ถ๊ฐ | |
| content += "\n์๋ณ ํฌ์ธํธ:\n" | |
| for rate in points_rates[:6]: | |
| month = rate.get("month", "?") | |
| min_p = rate.get("min_points", "?") | |
| max_p = rate.get("max_points", "?") | |
| content += f" - {month}์: {min_p:,}P ~ {max_p:,}P\n" | |
| if len(points_rates) > 6: | |
| content += f" ... (์ธ {len(points_rates) - 6}๊ฐ์)\n" | |
| content += DISCLAIMER_PRICE | |
| chunks.append({ | |
| "content": content.strip(), | |
| "metadata": { | |
| "type": "points_pricing", | |
| "hotel_name": hotel_name, | |
| "hotel_name_ko": hotel_name_ko, | |
| "min_points": min_points, | |
| "max_points": max_points | |
| } | |
| }) | |
| # --- ์๋ณ ๋ฐ์ดํฐ (๊ธฐ์กด) --- | |
| monthly_data = data.get("monthly_data", {}) | |
| if monthly_data: | |
| monthly_content = format_hotel_header(hotel_name, hotel_name_ko, chain) | |
| monthly_content += "์๋ณ ํฌ์ธํธ ์ ๋ณด:\n" | |
| for month, mdata in list(monthly_data.items())[:6]: | |
| points_range = mdata.get("points_range", "N/A") | |
| monthly_content += f" - {month}: {points_range}\n" | |
| monthly_content += DISCLAIMER_SHORT | |
| chunks.append({ | |
| "content": monthly_content.strip(), | |
| "metadata": { | |
| "type": "points_monthly", | |
| "hotel_name": hotel_name | |
| } | |
| }) | |
| return chunks | |
| def handle_pricing_analysis(data: Any, context: Dict[str, Any]) -> List[Dict[str, Any]]: | |
| """pricing_analysis ํ์ ์ฒ๋ฆฌ (Josun Palace ๋ฑ)""" | |
| chunks = [] | |
| hotel_name, hotel_name_ko, chain = get_hotel_info(context) | |
| # ๋ฆฌ์คํธ ๊ตฌ์กฐ์ธ ๊ฒฝ์ฐ ์ฒ๋ฆฌ (ClassicTravel ๋ฑ) | |
| if isinstance(data, list): | |
| for item in data[:10]: | |
| if not isinstance(item, dict): | |
| continue | |
| hotel_id = item.get("hotel_id", "") | |
| item_hotel_name = item.get("hotel_name", hotel_name) | |
| search_date = item.get("search_date", "") | |
| stay_nights = item.get("stay_nights", 1) | |
| currency = item.get("currency", "KRW") | |
| content = format_hotel_header(item_hotel_name, hotel_name_ko, chain) | |
| content += f"ํธํ ID: {hotel_id}\n" | |
| content += f"๊ฒ์ ๋ ์ง: {search_date}\n" | |
| content += f"์๋ฐ ๊ธฐ๊ฐ: {stay_nights}๋ฐ\n\n" | |
| room_rates = item.get("room_rates", []) | |
| if room_rates: | |
| content += "๊ฐ์ค ์๊ธ:\n" | |
| for rate in room_rates[:5]: | |
| room_type = rate.get("room_type", "") | |
| rate_plan = rate.get("rate_plan", "") | |
| rate_desc = rate.get("rate_plan_description", "") | |
| price_krw = rate.get("price_krw", 0) | |
| content += f" - {room_type}\n" | |
| content += f" ์๊ธ์ : {rate_plan}\n" | |
| if rate_desc: | |
| content += f" ์ค๋ช : {rate_desc}\n" | |
| content += f" ๊ฐ๊ฒฉ: โฉ{price_krw:,}\n" | |
| content += DISCLAIMER_PRICE | |
| chunks.append({ | |
| "content": content.strip(), | |
| "metadata": { | |
| "type": "room_pricing", | |
| "hotel_name": item_hotel_name, | |
| "hotel_id": hotel_id, | |
| "currency": currency | |
| } | |
| }) | |
| return chunks | |
| # ๊ธฐ์กด ๋์ ๋๋ฆฌ ๊ตฌ์กฐ ์ฒ๋ฆฌ | |
| if not isinstance(data, dict): | |
| return chunks | |
| points_stats = data.get("points_price_stats", {}) | |
| cash_stats = data.get("cash_price_stats", {}) | |
| if not points_stats: | |
| return chunks | |
| min_points = points_stats.get("lowest_points") | |
| max_points = points_stats.get("highest_points") | |
| avg_points = points_stats.get("annual_average_estimated") | |
| min_period = points_stats.get("lowest_points_period", "๋น์๊ธฐ ํ์ผ") | |
| max_period = points_stats.get("highest_points_period", "์ฑ์๊ธฐ/์ฃผ๋ง") | |
| content = format_hotel_header(hotel_name, hotel_name_ko, chain) | |
| content += f"""์์ฝ ์ ํ: ํฌ์ธํธ ์๋ฐ | |
| ํฌ์ธํธ ํ์๋: | |
| - ์ต์: {min_points:,}P ({min_period}) | |
| - ์ต๋: {max_points:,}P ({max_period}) | |
| """ | |
| if avg_points: | |
| content += f" - ํ๊ท : {avg_points:,}P (์ถ์ )\n" | |
| # ํ๊ธ ๊ฐ๊ฒฉ | |
| if cash_stats: | |
| min_cash = cash_stats.get("lowest_price") | |
| min_cash_period = cash_stats.get("lowest_price_period", "") | |
| if min_cash: | |
| content += f""" | |
| ํ๊ธ ๊ฐ๊ฒฉ ๋น๊ต: | |
| - ์ต์ ๊ฐ: โฉ{min_cash:,} ({min_cash_period}) | |
| """ | |
| # ์์ฆ๋ณ ํจํด | |
| seasonal = data.get("seasonal_patterns", []) | |
| if seasonal: | |
| content += "\n์์ฆ๋ณ ๊ฐ๊ฒฉ ํจํด:\n" | |
| for pattern in seasonal[:4]: | |
| content += f" - {pattern.get('season', '')}: {pattern.get('price_range', '')} ({pattern.get('period', '')})\n" | |
| content += DISCLAIMER_PRICE | |
| chunks.append({ | |
| "content": content.strip(), | |
| "metadata": { | |
| "type": "points_pricing", | |
| "hotel_name": hotel_name, | |
| "hotel_name_ko": hotel_name_ko, | |
| "min_points": min_points, | |
| "max_points": max_points | |
| } | |
| }) | |
| # ์ธ์ฌ์ดํธ | |
| insights = data.get("pricing_insights", []) | |
| if insights: | |
| insight_content = format_hotel_header(hotel_name, hotel_name_ko, chain) | |
| insight_content += "์์ฝ ํ:\n" | |
| for insight in insights[:5]: | |
| insight_content += f" - {insight}\n" | |
| insight_content += DISCLAIMER_SHORT | |
| chunks.append({ | |
| "content": insight_content.strip(), | |
| "metadata": { | |
| "type": "pricing_insight", | |
| "hotel_name": hotel_name | |
| } | |
| }) | |
| return chunks | |
| # =========================================================================== | |
| # ํธ๋ค๋ฌ: ํํ | |
| # =========================================================================== | |
| def handle_benefits(data: Any, context: Dict[str, Any]) -> List[Dict[str, Any]]: | |
| """benefits ์ฒ๋ฆฌ""" | |
| chunks = [] | |
| benefits = data if isinstance(data, list) else [data] | |
| for benefit in benefits[:10]: | |
| if not isinstance(benefit, dict): | |
| continue | |
| ben_name = benefit.get("benefit_name", benefit.get("name", "N/A")) | |
| content = f""" | |
| ํํ: {ben_name} | |
| ์นดํ ๊ณ ๋ฆฌ: {benefit.get('category', 'N/A')} | |
| ์ค๋ช : {benefit.get('description', benefit.get('details', 'N/A'))} | |
| """ | |
| chunks.append({ | |
| "content": content.strip(), | |
| "metadata": { | |
| "type": "benefit", | |
| "benefit_name": ben_name | |
| } | |
| }) | |
| return chunks | |
| # =========================================================================== | |
| # ํธ๋ค๋ฌ: Best Rate Guarantee | |
| # =========================================================================== | |
| def handle_best_rate_guarantee(data: Any, context: Dict[str, Any]) -> List[Dict[str, Any]]: | |
| """best_rate_guarantee ์ฒ๋ฆฌ (์คํค๋ง: BestRateGuarantee, BRGRule, BRGRemedy) | |
| ์ฒญํฌ ๋ถ๋ฆฌ ์ ๋ต: | |
| 1. ํ๋ก๊ทธ๋จ ๊ฐ์ ์ฒญํฌ (๊ธฐ๋ณธ ์ ๋ณด, ์ ์ธ ์ฌํญ) | |
| 2. ๊ท์น๋ณ ์ฒญํฌ (๊ฐ BRGRule์ ๊ฐ๋ณ ์ฒญํฌ๋ก) | |
| """ | |
| chunks = [] | |
| chain = context.get("chain", "UNKNOWN") | |
| if not isinstance(data, dict): | |
| return chunks | |
| program_name = data.get("program_name", "Best Rate Guarantee") | |
| brg_chain = data.get("chain", chain) | |
| # === ์ฒญํฌ 1: ํ๋ก๊ทธ๋จ ๊ฐ์ === | |
| overview_content = f""" | |
| ์ต์ ๊ฐ ๋ณด์ฅ ํ๋ก๊ทธ๋จ: {program_name} | |
| ์ฒด์ธ: {brg_chain} | |
| ๋ฉค๋ฒ์ญ ํ์: {"์" if data.get("requires_membership") else "์๋์ค"} | |
| """ | |
| # ์ ์ธ ์ฌํญ (์คํค๋ง ํ๋) | |
| excluded_regions = data.get("excluded_regions", []) | |
| excluded_brands = data.get("excluded_brands", []) | |
| excluded_hotels = data.get("excluded_hotels", []) | |
| if excluded_regions: | |
| overview_content += f"์ ์ธ ์ง์ญ: {', '.join(excluded_regions[:5])}\n" | |
| if excluded_brands: | |
| overview_content += f"์ ์ธ ๋ธ๋๋: {', '.join(excluded_brands[:5])}\n" | |
| if excluded_hotels: | |
| overview_content += f"์ ์ธ ํธํ : {', '.join(excluded_hotels[:3])}\n" | |
| # ์ฝ๊ด | |
| if data.get("governing_law"): | |
| overview_content += f"\n์ค๊ฑฐ๋ฒ: {data.get('governing_law')}\n" | |
| if data.get("arbitration_clause"): | |
| overview_content += "์ค์ฌ ์กฐํญ: ํฌํจ\n" | |
| # ๋ง์ง๋ง ์ ๋ฐ์ดํธ | |
| if data.get("last_updated"): | |
| overview_content += f"๋ง์ง๋ง ์ ๋ฐ์ดํธ: {data.get('last_updated')}\n" | |
| overview_content += DISCLAIMER_SHORT | |
| chunks.append({ | |
| "content": overview_content.strip(), | |
| "metadata": { | |
| "type": "best_rate_guarantee_overview", | |
| "program_name": program_name, | |
| "chain": brg_chain | |
| } | |
| }) | |
| # === ์ฒญํฌ 2+: ๊ท์น๋ณ ์์ธ (์คํค๋ง: BRGRule) === | |
| rules = data.get("rules", []) | |
| for rule in rules[:5]: # ์ต๋ 5๊ฐ ๊ท์น | |
| if not isinstance(rule, dict): | |
| continue | |
| rule_name = rule.get("rule_name", "N/A") | |
| summary = rule.get("summary", "") | |
| rule_content = f""" | |
| {program_name} - {rule_name} | |
| ์ฒด์ธ: {brg_chain} | |
| """ | |
| if summary: | |
| rule_content += f"์์ฝ: {summary}\n\n" | |
| # ํด๋ ์ ์กฐ๊ฑด (์คํค๋ง ํ๋) | |
| claim_window = rule.get("claim_window") | |
| min_hours = rule.get("minimum_hours_before_checkin") | |
| rate_diff = rule.get("rate_difference_threshold_percent") | |
| max_rooms = rule.get("max_rooms_per_claim") | |
| if claim_window or min_hours or rate_diff or max_rooms: | |
| rule_content += "ํด๋ ์ ์กฐ๊ฑด:\n" | |
| if claim_window: | |
| rule_content += f" - ํด๋ ์ ๊ฐ๋ฅ ๊ธฐ๊ฐ: {claim_window}\n" | |
| if min_hours: | |
| rule_content += f" - ์ฒดํฌ์ธ ์ ์ต์: {min_hours}์๊ฐ\n" | |
| if rate_diff: | |
| rule_content += f" - ์ต์ ๊ฐ๊ฒฉ ์ฐจ์ด: {rate_diff}%\n" | |
| if max_rooms: | |
| rule_content += f" - ํด๋ ์๋น ์ต๋ ๊ฐ์ค: {max_rooms}์ค\n" | |
| # ๋น๊ต ๊ธฐ์ค | |
| comparison = rule.get("comparison_criteria", []) | |
| if comparison: | |
| rule_content += f"\n๋น๊ต ๊ธฐ์ค:\n" | |
| for c in comparison[:6]: | |
| rule_content += f" - {c}\n" | |
| # ์ฑ๋ ์๊ตฌ์ฌํญ | |
| channel_reqs = rule.get("channel_requirements", []) | |
| if channel_reqs: | |
| rule_content += f"\n์์ฝ ์ฑ๋: {', '.join(channel_reqs)}\n" | |
| # ์ ์ฉ/์ ์ธ ์๊ธ | |
| eligible_rates = rule.get("eligible_rates", []) | |
| ineligible_rates = rule.get("ineligible_rates", []) | |
| if eligible_rates: | |
| rule_content += f"\n์ ์ฉ ์๊ธ:\n" | |
| for r in eligible_rates[:5]: | |
| rule_content += f" - {r}\n" | |
| if ineligible_rates: | |
| rule_content += f"\n์ ์ธ ์๊ธ:\n" | |
| for r in ineligible_rates[:8]: | |
| rule_content += f" - {r}\n" | |
| # ๋ณด์ (์คํค๋ง: BRGRemedy) | |
| remedies = rule.get("remedies", []) | |
| if remedies: | |
| rule_content += "\n๋ณด์:\n" | |
| for remedy in remedies[:3]: | |
| if isinstance(remedy, dict): | |
| r_type = remedy.get("type", "N/A") | |
| r_desc = remedy.get("description", "") | |
| rule_content += f" [{r_type}] {r_desc}\n" | |
| # ๊ฐ์น (BenefitValue ๊ตฌ์กฐ) | |
| value = remedy.get("value", {}) | |
| if isinstance(value, dict): | |
| v_type = value.get("type") | |
| v_text = value.get("text", "") | |
| if v_text: | |
| rule_content += f" - ๊ฐ์น: {v_text}\n" | |
| elif v_type == "PERCENT" and value.get("percent"): | |
| rule_content += f" - ๊ฐ์น: {value.get('percent')}%\n" | |
| elif v_type == "POINTS" and value.get("points"): | |
| rule_content += f" - ๊ฐ์น: {value.get('points'):,}P\n" | |
| # ์ต๋ ๋ณด์ (Money ๊ตฌ์กฐ) | |
| max_val = remedy.get("max_value", {}) | |
| if isinstance(max_val, dict) and max_val.get("amount"): | |
| rule_content += f" - ์ต๋: {max_val.get('amount'):,} {max_val.get('currency', '')}\n" | |
| # ์ถ๊ฐ ์กฐ๊ฑด | |
| conditions = rule.get("conditions", []) | |
| if conditions: | |
| rule_content += "\n์ถ๊ฐ ์กฐ๊ฑด:\n" | |
| for cond in conditions[:5]: | |
| if isinstance(cond, dict): | |
| cond_summary = cond.get("summary", "") | |
| if cond_summary: | |
| rule_content += f" - {cond_summary}\n" | |
| rule_content += DISCLAIMER_SHORT | |
| if len(rule_content) > 100: | |
| chunks.append({ | |
| "content": rule_content.strip(), | |
| "metadata": { | |
| "type": "best_rate_guarantee_rule", | |
| "program_name": program_name, | |
| "rule_name": rule_name, | |
| "chain": brg_chain | |
| } | |
| }) | |
| # === ํ์ ํธํ: ๊ธฐ์กด conditions ํ์ ์ง์ (rules๊ฐ ์๋ ๊ฒฝ์ฐ) === | |
| conditions = data.get("conditions", []) | |
| if conditions and not rules: | |
| cond_content = f""" | |
| {program_name} - ์ด์ฉ ์กฐ๊ฑด | |
| ์ฒด์ธ: {brg_chain} | |
| ์ด์ฉ ์กฐ๊ฑด: | |
| """ | |
| for cond in conditions[:5]: | |
| if isinstance(cond, dict): | |
| cond_content += f" - {cond.get('description', cond.get('summary', cond))}\n" | |
| else: | |
| cond_content += f" - {cond}\n" | |
| exclusions = data.get("exclusions", []) | |
| if exclusions: | |
| cond_content += "\n์ ์ธ ์ฌํญ:\n" | |
| for exc in exclusions[:5]: | |
| if isinstance(exc, str): | |
| cond_content += f" - {exc}\n" | |
| cond_content += DISCLAIMER_SHORT | |
| chunks.append({ | |
| "content": cond_content.strip(), | |
| "metadata": { | |
| "type": "best_rate_guarantee_conditions", | |
| "program_name": program_name, | |
| "chain": brg_chain | |
| } | |
| }) | |
| # === ์ฒญํฌ: ํด๋ ์ ๊ฑฐ๋ถ ์ฌ์ (extra_attributes) === | |
| extra_attrs = data.get("extra_attributes", {}) | |
| if extra_attrs and isinstance(extra_attrs, dict): | |
| rejection_reasons = extra_attrs.get("claim_rejection_reasons", []) | |
| if rejection_reasons: | |
| rejection_content = f""" | |
| {program_name} - ํด๋ ์ ๊ฑฐ๋ถ ์ฌ์ | |
| ์ฒด์ธ: {brg_chain} | |
| ํด๋ ์์ด ๊ฑฐ๋ถ๋ ์ ์๋ ์ฌ์ : | |
| """ | |
| for reason in rejection_reasons: | |
| rejection_content += f" - {reason}\n" | |
| # ์ถ๊ฐ ์์ฑ | |
| if extra_attrs.get("nj_resident_exception"): | |
| rejection_content += f"\n๋ด์ ์ง ๊ฑฐ์ฃผ์ ์์ธ: {extra_attrs.get('nj_resident_exception')}\n" | |
| if extra_attrs.get("modification_rights"): | |
| rejection_content += f"ํ๋ก๊ทธ๋จ ์์ ๊ถํ: {extra_attrs.get('modification_rights')}\n" | |
| if extra_attrs.get("employee_exclusion"): | |
| rejection_content += f"์ง์ ์ ์ธ: {extra_attrs.get('employee_exclusion')}\n" | |
| rejection_content += DISCLAIMER_SHORT | |
| chunks.append({ | |
| "content": rejection_content.strip(), | |
| "metadata": { | |
| "type": "best_rate_guarantee_rejection", | |
| "program_name": program_name, | |
| "chain": brg_chain | |
| } | |
| }) | |
| return chunks | |
| # =========================================================================== | |
| # ํธ๋ค๋ฌ: Channel Implementations | |
| # =========================================================================== | |
| def handle_channel_implementations(data: Any, context: Dict[str, Any]) -> List[Dict[str, Any]]: | |
| """channel_implementations ์ฒ๋ฆฌ - ๊ตญ๊ฐ/ํธํ ๋ช ์ ๋ณด ํฌํจ์ผ๋ก ๊ฒ์ ์ ํ๋ ํฅ์""" | |
| chunks = [] | |
| chain = context.get("chain", "UNKNOWN") | |
| impls = data if isinstance(data, list) else [data] | |
| for impl in impls[:10]: | |
| if not isinstance(impl, dict): | |
| continue | |
| program_name = impl.get("program_name", "N/A") | |
| hotel_id = impl.get("hotel_id", "N/A") | |
| # extra_attributes์์ ๊ตญ๊ฐ/ํธํ ๋ช ์ถ์ถ (๊ฒ์ ์ ํ๋ ํฅ์) | |
| extra = impl.get("extra_attributes", {}) | |
| country = extra.get("country", "") | |
| hotel_name = extra.get("hotel_name", "") | |
| region = extra.get("region", "") | |
| content = f""" | |
| ์ฑ๋ ํ๋ก๊ทธ๋จ: {program_name} | |
| """ | |
| # ์ค์ ํธํ ๋ช ์ด ์์ผ๋ฉด ์ฐ์ ํ์ | |
| if hotel_name: | |
| content += f"ํธํ : {hotel_name}\n" | |
| else: | |
| content += f"ํธํ ID: {hotel_id}\n" | |
| # ๊ตญ๊ฐ/์ง์ญ ์ ๋ณด (๊ฒ์ ๋งค์นญ์ ์ค์) | |
| if country: | |
| content += f"๊ตญ๊ฐ: {country}\n" | |
| if region: | |
| content += f"์ง์ญ: {region}\n" | |
| content += f"""์ฑ๋: {impl.get('channel', 'DIRECT')} | |
| """ | |
| # ํํ ์ค๋ฒ๋ผ์ด๋ | |
| overrides = impl.get("benefit_overrides", []) | |
| if overrides: | |
| content += "\nํํ:\n" | |
| for override in overrides[:5]: | |
| if isinstance(override, dict): | |
| category = override.get("benefit_category", "N/A") | |
| detail = override.get("implementation_detail", "")[:100] | |
| content += f"- {category}: {detail}\n" | |
| # ํ ์ถ๊ฐ | |
| tips = override.get("tips", []) | |
| for tip in tips[:2]: | |
| content += f" โ {tip}\n" | |
| # ์ถ๊ฐ ํํ (additional_benefits) ์ถ๋ ฅ - ๊ฒ์ ๋๋ฝ ๋ฐฉ์ง | |
| additional = impl.get("additional_benefits", []) | |
| if additional: | |
| content += "\n์ถ๊ฐ ํํ:\n" | |
| for benefit in additional[:5]: | |
| if isinstance(benefit, dict): | |
| ben_name = benefit.get("name", "N/A") | |
| ben_desc = (benefit.get("description", "") or "")[:120] | |
| ben_category = benefit.get("category", "") | |
| content += f"- {ben_name}" | |
| if ben_category: | |
| content += f" [{ben_category}]" | |
| if ben_desc and ben_desc != ben_name: | |
| content += f": {ben_desc}" | |
| content += "\n" | |
| # ๊ฐ์น ํ์ | |
| value = benefit.get("value", {}) | |
| if isinstance(value, dict): | |
| v_type = value.get("type") | |
| if v_type == "PERCENT" and value.get("percent"): | |
| content += f" โ ํ ์ธ์จ: {value.get('percent')}%\n" | |
| elif v_type == "POINTS" and value.get("points"): | |
| content += f" โ ํฌ์ธํธ: {value.get('points'):,}P\n" | |
| # ์ผ๋ฐ ํ (general_tips) - ๊ฒ์ ํ์ง ํฅ์ | |
| general_tips = impl.get("general_tips", []) | |
| if general_tips: | |
| content += "\n์ด์ฉ ํ:\n" | |
| for tip in general_tips[:5]: | |
| content += f" ๐ก {tip}\n" | |
| # ์์ฝ ๊ด๋ จ ์ ๋ณด (booking_notes, contact_for_benefits) | |
| booking_notes = impl.get("booking_notes") | |
| if booking_notes: | |
| content += f"\n์์ฝ ์ ์ฐธ๊ณ : {booking_notes}\n" | |
| contact = impl.get("contact_for_benefits") | |
| if contact: | |
| content += f"ํํ ๋ฌธ์: {contact}\n" | |
| # ๊ฒ์ฆ ์ ๋ณด (last_verified, verified_by) - ์ ๋ณด ์ ์ ๋ ํ์ | |
| last_verified = impl.get("last_verified") | |
| verified_by = impl.get("verified_by") | |
| if last_verified: | |
| content += f"\n๋ง์ง๋ง ํ์ธ: {last_verified}" | |
| if verified_by: | |
| content += f" ({verified_by})" | |
| content += "\n" | |
| content += DISCLAIMER_SHORT | |
| # ๋ฉํ๋ฐ์ดํฐ์๋ ๊ตญ๊ฐ ์ ๋ณด ํฌํจ | |
| metadata = { | |
| "type": "channel_implementation", | |
| "program_name": program_name, | |
| "hotel_id": hotel_id, | |
| "chain": context.get("chain", "UNKNOWN") # chain ์ ๋ณด ์ถ๊ฐ | |
| } | |
| if country: | |
| metadata["country"] = country | |
| if hotel_name: | |
| metadata["hotel_name"] = hotel_name | |
| if last_verified: | |
| metadata["last_verified"] = str(last_verified) | |
| chunks.append({ | |
| "content": content.strip(), | |
| "metadata": metadata | |
| }) | |
| return chunks | |
| # =========================================================================== | |
| # ํธ๋ค๋ฌ: Channel Benefit Packages (FHR ๋ฑ) | |
| # =========================================================================== | |
| def handle_channel_benefit_packages(data: Any, context: Dict[str, Any]) -> List[Dict[str, Any]]: | |
| """channel_benefit_packages ์ฒ๋ฆฌ (FHR, THC ๋ฑ) - ์คํค๋ง: ChannelBenefitPackage""" | |
| chunks = [] | |
| packages = data if isinstance(data, list) else [data] | |
| for pkg in packages[:5]: | |
| if not isinstance(pkg, dict): | |
| continue | |
| pkg_name = pkg.get("package_name", pkg.get("name", "N/A")) | |
| channel = pkg.get("channel", pkg.get("agent", "N/A")) | |
| content = f""" | |
| ํํ ํจํค์ง: {pkg_name} | |
| ์ฑ๋: {channel} | |
| """ | |
| # ํน์ ์๊ธ์ ํ์ ์ฌ๋ถ | |
| if pkg.get("requires_specific_rate"): | |
| content += "ํน์ ์๊ธ์ ํ์: ์\n" | |
| eligible_rates = pkg.get("eligible_rates", []) | |
| if eligible_rates: | |
| content += f"์ ์ฉ ๊ฐ๋ฅ ์๊ธ: {', '.join(eligible_rates)}\n" | |
| # ์คํค๋ง ํ๋๋ช : standard_benefits | |
| # ํ์ ํธํ: benefits, included_benefits๋ ์ง์ | |
| benefits = pkg.get("standard_benefits", pkg.get("benefits", pkg.get("included_benefits", []))) | |
| if benefits: | |
| content += "\nํฌํจ ํํ:\n" | |
| for benefit in benefits[:8]: | |
| if isinstance(benefit, dict): | |
| ben_name = benefit.get("name", benefit.get("benefit_name", "N/A")) | |
| ben_desc = benefit.get("description", "")[:60] | |
| ben_category = benefit.get("category", "") | |
| content += f" - {ben_name}" | |
| if ben_category: | |
| content += f" [{ben_category}]" | |
| if ben_desc: | |
| content += f": {ben_desc}" | |
| content += "\n" | |
| # ๊ฐ์น ํ์ (BenefitValue ๊ตฌ์กฐ) | |
| value = benefit.get("value", {}) | |
| if isinstance(value, dict): | |
| v_type = value.get("type") | |
| if v_type == "MONEY" and value.get("money"): | |
| money = value.get("money", {}) | |
| content += f" ๊ฐ์น: {money.get('amount', 'N/A')} {money.get('currency', '')}\n" | |
| elif v_type == "TEXT" and value.get("text"): | |
| content += f" ๊ฐ์น: {value.get('text')}\n" | |
| elif v_type == "PERCENT" and value.get("percent"): | |
| content += f" ๊ฐ์น: {value.get('percent')}%\n" | |
| elif isinstance(benefit, str): | |
| content += f" - {benefit}\n" | |
| content += DISCLAIMER_SHORT | |
| chunks.append({ | |
| "content": content.strip(), | |
| "metadata": { | |
| "type": "channel_benefit_package", | |
| "package_name": pkg_name, | |
| "channel": channel | |
| } | |
| }) | |
| return chunks | |
| # =========================================================================== | |
| # ํธ๋ค๋ฌ: Member Rates (ํ์ ์๊ธ) | |
| # =========================================================================== | |
| def handle_member_rates(data: Any, context: Dict[str, Any]) -> List[Dict[str, Any]]: | |
| """member_rates ์ฒ๋ฆฌ (ํ์ ์ ์ฉ ์๊ธ) - ์คํค๋ง MemberRate ํ๋ ๊ตฌ์กฐ์ ๋ง์ถค""" | |
| chunks = [] | |
| chain = context.get("chain", "UNKNOWN") | |
| if data is None: | |
| return chunks | |
| rates = data if isinstance(data, list) else [data] | |
| for rate in rates[:5]: | |
| if not isinstance(rate, dict): | |
| continue | |
| rate_name = rate.get("rate_name", rate.get("name", "N/A")) | |
| content = f""" | |
| ํ์ ์๊ธ: {rate_name} | |
| ์ฒด์ธ: {rate.get('chain', chain)} | |
| """ | |
| # ํ ์ธ์จ (์คํค๋ง ๊ตฌ์กฐ ๋ฐ์) | |
| weekday_discount = rate.get("weekday_discount_percent") | |
| weekend_discount = rate.get("weekend_discount_percent") | |
| min_discount = rate.get("minimum_discount_percent") | |
| max_discount = rate.get("maximum_discount_percent") | |
| # ํ์ ํธํ์ฑ: ๊ธฐ์กด ํ๋๋ ์ง์ | |
| legacy_discount = rate.get("discount_percentage", rate.get("discount")) | |
| if weekday_discount or weekend_discount: | |
| content += "ํ ์ธ์จ:\n" | |
| if weekday_discount: | |
| content += f" - ํ์ผ: {weekday_discount}%\n" | |
| if weekend_discount: | |
| content += f" - ์ฃผ๋ง: {weekend_discount}%\n" | |
| elif min_discount or max_discount: | |
| content += f"ํ ์ธ์จ: {min_discount or '?'}% ~ {max_discount or '?'}%\n" | |
| elif legacy_discount and legacy_discount != "N/A": | |
| content += f"ํ ์ธ์จ: {legacy_discount}%\n" | |
| # ํ์ ๋ฉค๋ฒ์ญ | |
| if rate.get("requires_membership", True): | |
| content += "๋ฉค๋ฒ์ญ ํ์: ์\n" | |
| if rate.get("minimum_tier"): | |
| content += f"์ต์ ๋ฑ๊ธ: {rate.get('minimum_tier')}\n" | |
| # ์ ์ฉ ๊ฐ์ค | |
| applies_to = rate.get("applies_to_room_types", []) | |
| excluded = rate.get("excluded_room_types", []) | |
| if applies_to: | |
| content += f"์ ์ฉ ๊ฐ์ค: {', '.join(applies_to)}\n" | |
| if excluded: | |
| content += f"์ ์ธ ๊ฐ์ค: {', '.join(excluded)}\n" | |
| # ์์ฝ ์ฑ๋ | |
| channels = rate.get("eligible_channels", []) | |
| if channels: | |
| content += f"์์ฝ ์ฑ๋: {', '.join(channels)}\n" | |
| # ์ ํ์ฌํญ | |
| max_rooms = rate.get("max_rooms") | |
| if max_rooms: | |
| content += f"์ต๋ ๊ฐ์ค: {max_rooms}์ค\n" | |
| excluded_rate_types = rate.get("excluded_rate_types", []) | |
| if excluded_rate_types: | |
| content += f"์ ์ธ ์๊ธ ์ ํ: {', '.join(excluded_rate_types)}\n" | |
| excluded_hotels = rate.get("excluded_hotels", []) | |
| if excluded_hotels: | |
| content += f"์ ์ธ ํธํ : {', '.join(excluded_hotels[:3])}\n" | |
| # ์ค๋ณต ์ ์ฉ | |
| if rate.get("combinable_with_promotions"): | |
| content += "๋ค๋ฅธ ํ๋ก๋ชจ์ ๊ณผ ์ค๋ณต ๊ฐ๋ฅ\n" | |
| # ํ์ ํธํ์ฑ: ๊ธฐ์กด ํ๋๋ค | |
| legacy_conditions = rate.get("conditions", rate.get("eligibility")) | |
| if legacy_conditions and legacy_conditions != "N/A": | |
| content += f"์ ์ฉ ์กฐ๊ฑด: {legacy_conditions}\n" | |
| if rate.get("description"): | |
| content += f"์ค๋ช : {rate.get('description')}\n" | |
| chunks.append({ | |
| "content": content.strip(), | |
| "metadata": { | |
| "type": "member_rate", | |
| "rate_name": rate_name, | |
| "chain": rate.get("chain", chain) | |
| } | |
| }) | |
| return chunks | |
| # =========================================================================== | |
| # ํธ๋ค๋ฌ: ํธํ ์์ค | |
| # =========================================================================== | |
| def handle_hotel_facilities(data: Any, context: Dict[str, Any]) -> List[Dict[str, Any]]: | |
| """hotel_facilities, other_facilities, hotel_amenities_summary, | |
| executive_lounge, pulse8_wellness ๋ฑ ๋ค์ํ ์์ค ๋ฐ์ดํฐ ์ฒ๋ฆฌ | |
| ์ง์ ํํ: | |
| 1. ๋ฆฌ์คํธ ํํ (๊ธฐ์กด hotel_facilities) | |
| 2. ๋์ ๋๋ฆฌ ํํ (executive_lounge, pulse8_wellness ๋ฑ) | |
| 3. ๋ฌธ์์ด ๋ฆฌ์คํธ (hotel_amenities_summary) | |
| """ | |
| chunks = [] | |
| hotel_name, hotel_name_ko, chain = get_hotel_info(context) | |
| if data is None: | |
| return chunks | |
| content = format_hotel_header(hotel_name, hotel_name_ko, chain) | |
| # ๋ฐ์ดํฐ ํํ์ ๋ฐ๋ฅธ ์ฒ๋ฆฌ | |
| if isinstance(data, dict): | |
| # ๋์ ๋๋ฆฌ ํํ (executive_lounge, pulse8_wellness ๋ฑ) | |
| facility_name = data.get("name", data.get("lounge_name", "์์ค")) | |
| location = data.get("location", "") | |
| hours = data.get("operating_hours", "") | |
| content += f"์์ค: {facility_name}\n" | |
| if location: | |
| content += f"์์น: {location}\n" | |
| if hours: | |
| content += f"์ด์์๊ฐ: {hours}\n" | |
| # ์ ๋ง | |
| if data.get("view"): | |
| content += f"์ ๋ง: {data.get('view')}\n" | |
| # ์๋น์ค/์์ค ๋ชฉ๋ก | |
| services = data.get("services", data.get("facilities", [])) | |
| if services: | |
| content += "\n์ ๊ณต ์๋น์ค/์์ค:\n" | |
| for service in services[:10]: | |
| if isinstance(service, dict): | |
| s_name = service.get("name", "N/A") | |
| s_desc = service.get("description", "") | |
| content += f" - {s_name}" | |
| if s_desc: | |
| content += f": {s_desc[:60]}" | |
| content += "\n" | |
| else: | |
| content += f" - {service}\n" | |
| # ๋๋ ์ค์ฝ๋ | |
| if data.get("dress_code"): | |
| content += f"\n๋๋ ์ค์ฝ๋: {data.get('dress_code')}\n" | |
| # ์ฐ๋ น ์ ํ | |
| if data.get("age_restriction"): | |
| content += f"์ฐ๋ น ์ ํ: {data.get('age_restriction')}\n" | |
| # ๋ฉํ๋ฐ์ดํฐ ํ์ ๊ฒฐ์ | |
| meta_type = "hotel_facility" | |
| if "lounge" in str(data).lower(): | |
| meta_type = "executive_lounge" | |
| elif "wellness" in str(data).lower() or "pulse" in str(data).lower(): | |
| meta_type = "wellness_facility" | |
| elif isinstance(data, list): | |
| # ๋ฆฌ์คํธ ํํ | |
| facilities = data | |
| # ์ฒซ ๋ฒ์งธ ํญ๋ชฉ ํ์ธํ์ฌ ํ์ ๊ฒฐ์ | |
| first_item = facilities[0] if facilities else None | |
| if first_item and isinstance(first_item, str): | |
| # ๋ฌธ์์ด ๋ฆฌ์คํธ (hotel_amenities_summary) | |
| content += "ํธํ ์์ค ๋ฐ ์๋น์ค ์์ฝ:\n" | |
| for amenity in facilities[:20]: | |
| content += f" โ {amenity}\n" | |
| meta_type = "hotel_amenities_summary" | |
| else: | |
| # ๋์ ๋๋ฆฌ ๋ฆฌ์คํธ (๊ธฐ์กด hotel_facilities) | |
| content += "ํธํ ์์ค:\n" | |
| for facility in facilities[:15]: | |
| if isinstance(facility, dict): | |
| name = facility.get("name", facility.get("facility_name", "N/A")) | |
| desc = facility.get("description", "") | |
| content += f" - {name}" | |
| if desc: | |
| content += f": {desc[:60]}" | |
| content += "\n" | |
| elif isinstance(facility, str): | |
| content += f" - {facility}\n" | |
| meta_type = "hotel_facilities" | |
| else: | |
| return chunks | |
| if len(content) > 100: | |
| chunks.append({ | |
| "content": content.strip(), | |
| "metadata": { | |
| "type": meta_type, | |
| "hotel_name": hotel_name | |
| } | |
| }) | |
| return chunks | |
| # =========================================================================== | |
| # ํธ๋ค๋ฌ: ๊ฐ์ค ์ ํ | |
| # =========================================================================== | |
| def handle_room_types(data: Any, context: Dict[str, Any]) -> List[Dict[str, Any]]: | |
| """room_types ์ฒ๋ฆฌ""" | |
| chunks = [] | |
| hotel_name, hotel_name_ko, chain = get_hotel_info(context) | |
| if data is None: | |
| return chunks | |
| rooms = data if isinstance(data, list) else [data] | |
| # ๊ฐ ๊ฐ์ค ์ ํ์ ์ฒญํฌ๋ก | |
| for room in rooms[:10]: | |
| if not isinstance(room, dict): | |
| continue | |
| room_name = room.get("name", room.get("room_name", "N/A")) | |
| room_name_ko = room.get("name_ko", room.get("name_localized", {}).get("ko")) | |
| content = format_hotel_header(hotel_name, hotel_name_ko, chain) | |
| content += f"""๊ฐ์ค ์ ํ: {room_name} | |
| """ | |
| if room_name_ko: | |
| content += f"๊ฐ์ค๋ช (ํ๊ตญ์ด): {room_name_ko}\n" | |
| content += f"""๋ฉด์ : {room.get("area_sqm", room.get("size", "N/A"))}ใก | |
| ์นจ๋: {room.get("bed_type", "N/A")} | |
| ์ต๋ ํฌ์: {room.get("max_occupancy", "N/A")}๋ช | |
| ๋ทฐ: {room.get("view", "N/A")} | |
| ์นดํ ๊ณ ๋ฆฌ: {room.get("category", "N/A")} | |
| ์กฐ์ ํฌํจ: {'์' if room.get("breakfast_included") else '์๋์ค'} | |
| ๋ผ์ด์ง: {'๊ฐ๋ฅ' if room.get("lounge_access") else '๋ถ๊ฐ'} | |
| """ | |
| # ํน์ง | |
| features = room.get("features", []) | |
| if features: | |
| content += "ํน์ง:\n" | |
| for feat in features[:5]: | |
| content += f" - {feat}\n" | |
| chunks.append({ | |
| "content": content.strip(), | |
| "metadata": { | |
| "type": "room_type", | |
| "hotel_name": hotel_name, | |
| "room_name": room_name, | |
| "category": room.get("category") | |
| } | |
| }) | |
| return chunks | |
| # =========================================================================== | |
| # ํธ๋ค๋ฌ: ๊ฐ์ค ์ด๋ฉ๋ํฐ | |
| # =========================================================================== | |
| def handle_room_amenities(data: Any, context: Dict[str, Any]) -> List[Dict[str, Any]]: | |
| """room_common_amenities, room_common_features ์ฒ๋ฆฌ""" | |
| chunks = [] | |
| hotel_name, hotel_name_ko, chain = get_hotel_info(context) | |
| if data is None: | |
| return chunks | |
| content = format_hotel_header(hotel_name, hotel_name_ko, chain) | |
| content += "๊ฐ์ค ๊ณตํต ์ด๋ฉ๋ํฐ:\n" | |
| if isinstance(data, dict): | |
| for category, items in data.items(): | |
| if isinstance(items, list): | |
| content += f"\n{category}:\n" | |
| for item in items[:8]: | |
| content += f" - {item}\n" | |
| elif isinstance(data, list): | |
| for item in data[:15]: | |
| content += f" - {item}\n" | |
| if len(content) > 100: | |
| chunks.append({ | |
| "content": content.strip(), | |
| "metadata": { | |
| "type": "room_amenities", | |
| "hotel_name": hotel_name | |
| } | |
| }) | |
| return chunks | |
| # =========================================================================== | |
| # ํธ๋ค๋ฌ: ๋ ์คํ ๋ | |
| # =========================================================================== | |
| def handle_restaurants(data: Any, context: Dict[str, Any]) -> List[Dict[str, Any]]: | |
| """restaurants ์ฒ๋ฆฌ""" | |
| chunks = [] | |
| hotel_name, hotel_name_ko, chain = get_hotel_info(context) | |
| if data is None: | |
| return chunks | |
| restaurants = data if isinstance(data, list) else [data] | |
| for rest in restaurants[:5]: | |
| if not isinstance(rest, dict): | |
| continue | |
| rest_name = rest.get("name", rest.get("restaurant_name", "N/A")) | |
| content = format_hotel_header(hotel_name, hotel_name_ko, chain) | |
| content += f"""๋ ์คํ ๋: {rest_name} | |
| ์ ํ: {rest.get("type", rest.get("cuisine_type", "N/A"))} | |
| ์์น: {rest.get("location", "N/A")} | |
| ์ข์์: {rest.get("seating", rest.get("capacity", "N/A"))} | |
| """ | |
| # ์ด์ ์๊ฐ | |
| hours = rest.get("operating_hours", {}) | |
| if hours: | |
| content += "์ด์ ์๊ฐ:\n" | |
| if isinstance(hours, dict): | |
| for period, time in hours.items(): | |
| content += f" - {period}: {time}\n" | |
| # ๋ฉ๋ด/๊ฐ๊ฒฉ | |
| prices = rest.get("prices", rest.get("menu_prices", {})) | |
| if isinstance(prices, dict): | |
| content += "๊ฐ๊ฒฉ:\n" | |
| for item, price in list(prices.items())[:3]: | |
| content += f" - {item}: โฉ{price:,}\n" if isinstance(price, (int, float)) else f" - {item}: {price}\n" | |
| chunks.append({ | |
| "content": content.strip(), | |
| "metadata": { | |
| "type": "restaurant", | |
| "hotel_name": hotel_name, | |
| "restaurant_name": rest_name | |
| } | |
| }) | |
| return chunks | |
| # =========================================================================== | |
| # ํธ๋ค๋ฌ: ์กฐ์ ์ต์ | |
| # =========================================================================== | |
| def handle_breakfast(data: Any, context: Dict[str, Any]) -> List[Dict[str, Any]]: | |
| """breakfast_options ์ฒ๋ฆฌ""" | |
| chunks = [] | |
| hotel_name, hotel_name_ko, chain = get_hotel_info(context) | |
| if data is None: | |
| return chunks | |
| content = format_hotel_header(hotel_name, hotel_name_ko, chain) | |
| content += "์กฐ์ ์ต์ :\n" | |
| if isinstance(data, dict): | |
| for key, value in data.items(): | |
| if isinstance(value, dict): | |
| content += f"\n{key}:\n" | |
| for k, v in value.items(): | |
| content += f" - {k}: {v}\n" | |
| else: | |
| content += f" - {key}: {value}\n" | |
| elif isinstance(data, list): | |
| for item in data[:10]: | |
| if isinstance(item, dict): | |
| name = item.get("name", "N/A") | |
| price = item.get("price", "") | |
| content += f" - {name}" | |
| if price: | |
| content += f": โฉ{price:,}" if isinstance(price, (int, float)) else f": {price}" | |
| content += "\n" | |
| else: | |
| content += f" - {item}\n" | |
| if len(content) > 100: | |
| chunks.append({ | |
| "content": content.strip(), | |
| "metadata": { | |
| "type": "breakfast_options", | |
| "hotel_name": hotel_name | |
| } | |
| }) | |
| return chunks | |
| # =========================================================================== | |
| # ํธ๋ค๋ฌ: ํ์ | |
| # =========================================================================== | |
| def handle_ratings(data: Any, context: Dict[str, Any]) -> List[Dict[str, Any]]: | |
| """ratings ์ฒ๋ฆฌ""" | |
| chunks = [] | |
| hotel_name, hotel_name_ko, chain = get_hotel_info(context) | |
| if data is None or not isinstance(data, dict): | |
| return chunks | |
| content = format_hotel_header(hotel_name, hotel_name_ko, chain) | |
| content += "ํธํ ํ์ :\n" | |
| for source, rating in data.items(): | |
| if isinstance(rating, dict): | |
| score = rating.get("score", rating.get("rating", "N/A")) | |
| content += f" - {source}: {score}\n" | |
| else: | |
| content += f" - {source}: {rating}\n" | |
| if len(content) > 80: | |
| chunks.append({ | |
| "content": content.strip(), | |
| "metadata": { | |
| "type": "hotel_ratings", | |
| "hotel_name": hotel_name | |
| } | |
| }) | |
| return chunks | |
| # =========================================================================== | |
| # ํธ๋ค๋ฌ: ๋ค์ด๋ ํ๋ก๊ทธ๋จ | |
| # =========================================================================== | |
| def handle_dining_programs(data: Any, context: Dict[str, Any]) -> List[Dict[str, Any]]: | |
| """dining_programs ์ฒ๋ฆฌ""" | |
| chunks = [] | |
| chain = context.get("chain", "UNKNOWN") | |
| if not isinstance(data, list): | |
| return chunks | |
| for program in data: | |
| if not isinstance(program, dict): | |
| continue | |
| program_name = program.get("program_name", "Unknown Program") | |
| program_id = program.get("program_id", "unknown") | |
| region = program.get("region", "Unknown") | |
| # ๊ธฐ๋ณธ ํ๋ก๊ทธ๋จ ์ ๋ณด ์ฒญํฌ | |
| content = f"๋ค์ด๋ ํ๋ก๊ทธ๋จ: {program_name}\n" | |
| content += f"์ฒด์ธ: {chain}\n" | |
| content += f"์ง์ญ: {region}\n" | |
| # ์ ํจ ๊ธฐ๊ฐ | |
| validity = program.get("validity", {}) | |
| if validity: | |
| start_date = validity.get("start_date") | |
| end_date = validity.get("end_date") | |
| if start_date and end_date: | |
| content += f"์ ํจ ๊ธฐ๊ฐ: {start_date} ~ {end_date}\n" | |
| # ๋ฑ๊ธ๋ณ ํํ | |
| tier_benefits = program.get("tier_benefits", []) | |
| if tier_benefits: | |
| content += "\n๋ฑ๊ธ๋ณ ํํ:\n" | |
| for benefit in tier_benefits: | |
| tier = benefit.get("tier", "Unknown") | |
| discount = benefit.get("discount_percentage", 0) | |
| bonus_points = benefit.get("bonus_points", 0) | |
| min_spend = benefit.get("minimum_spend_usd", 0) | |
| content += f" {tier}:\n" | |
| content += f" - ํ ์ธ: {discount}%\n" | |
| content += f" - ๋ณด๋์ค ํฌ์ธํธ: {bonus_points}์ \n" | |
| if min_spend: | |
| content += f" - ์ต์ ์๋น ๊ธ์ก: ${min_spend}\n" | |
| # ํ๋ก๊ทธ๋จ ์ถ๊ฐ ์ ๋ณด (์คํค๋ง ํ๋ ๊ธฐ๋ฐ) | |
| # ์ฐธ์ฌ ๋งค์ฅ ์ ๋ณด | |
| participating_outlets = program.get("participating_outlets") | |
| if participating_outlets: | |
| content += f"\n์ฐธ์ฌ ๋งค์ฅ ์: {participating_outlets}๊ฐ\n" | |
| outlet_types = program.get("outlet_types", []) | |
| if outlet_types: | |
| content += f"๋งค์ฅ ์ ํ: {', '.join(outlet_types)}\n" | |
| # ์ ํ์ฌํญ | |
| restrictions_added = False | |
| max_group = program.get("max_group_size") | |
| if max_group: | |
| if not restrictions_added: | |
| content += "\n์ ํ์ฌํญ:\n" | |
| restrictions_added = True | |
| content += f" - ์ต๋ ๊ทธ๋ฃน ํฌ๊ธฐ: {max_group}๋ช \n" | |
| split_bills = program.get("split_bills_allowed") | |
| if split_bills is not None: | |
| if not restrictions_added: | |
| content += "\n์ ํ์ฌํญ:\n" | |
| restrictions_added = True | |
| content += f" - ๋ถํ ๊ฒฐ์ : {'ํ์ฉ' if split_bills else '๋ถํ์ฉ'}\n" | |
| member_present = program.get("member_must_be_present") | |
| if member_present is not None: | |
| if not restrictions_added: | |
| content += "\n์ ํ์ฌํญ:\n" | |
| restrictions_added = True | |
| content += f" - ํ์ ๋ณธ์ธ ์ฐธ์: {'ํ์' if member_present else '๋ถํ์'}\n" | |
| # ์ ์ธ์ฌํญ | |
| exclusions = program.get("exclusions", []) | |
| if exclusions: | |
| content += f"\n์ ์ธ์ฌํญ: {', '.join(exclusions[:3])}\n" | |
| content += DISCLAIMER_SHORT | |
| if len(content) > 100: | |
| chunks.append({ | |
| "content": content.strip(), | |
| "metadata": { | |
| "type": "dining_program", | |
| "program_id": program_id, | |
| "program_name": program_name, | |
| "chain": chain, | |
| "region": region | |
| } | |
| }) | |
| return chunks | |
| # =========================================================================== | |
| # ํธ๋ค๋ฌ: ํญ๊ณต ํํธ๋์ญ (Flying Blue โ Korean Air ๋ฑ) | |
| # =========================================================================== | |
| def handle_airline_partnership(data: Any, context: Dict[str, Any]) -> List[Dict[str, Any]]: | |
| """airline_partnership ์ฒ๋ฆฌ - ํํธ๋ ํ๋ก๊ทธ๋จ์ผ๋ก ํํญ๊ณต์ฌ ์์ฝ ์ ๋ณด""" | |
| chunks = [] | |
| if not data or not isinstance(data, dict): | |
| return chunks | |
| partner_program = data.get("partner_program", "N/A") | |
| operating_airline = data.get("operating_airline", "UNKNOWN") | |
| alliance = data.get("alliance", "") | |
| currency_name = data.get("currency_name", "ํฌ์ธํธ") | |
| pricing_model = data.get("pricing_model", "") | |
| pricing_desc = data.get("pricing_model_description", "") | |
| content = f"""# {partner_program}๋ก {operating_airline} ์์ฝํ๊ธฐ | |
| **ํํธ๋ ํ๋ก๊ทธ๋จ**: {partner_program} | |
| **์ดํญ ํญ๊ณต์ฌ**: {operating_airline} | |
| **์ ํด**: {alliance if alliance else "N/A"} | |
| **ํฌ์ธํธ ๋จ์**: {currency_name} | |
| """ | |
| if pricing_model: | |
| content += f"**๊ฐ๊ฒฉ ์ ์ฑ **: {pricing_model}" | |
| if pricing_desc: | |
| content += f" - {pricing_desc}" | |
| content += "\n" | |
| # ์ด์ ์ฃผ์ฒด | |
| operator = data.get("partner_program_operator") | |
| if operator: | |
| content += f"**ํ๋ก๊ทธ๋จ ์ด์**: {operator}\n" | |
| # ์ ํ์ฌํญ | |
| restrictions = data.get("restrictions", []) | |
| if restrictions: | |
| content += "\n## โ ๏ธ ์์ฝ ์ ํ์ฌํญ\n" | |
| for restriction in restrictions[:10]: | |
| if isinstance(restriction, dict): | |
| r_type = restriction.get("restriction_type", "") | |
| desc = restriction.get("description", "") | |
| content += f"- **{r_type}**: {desc}\n" | |
| else: | |
| content += f"- {restriction}\n" | |
| # ์์ฝ ๊ฐ๋ฅ ํญ๋ชฉ | |
| available = data.get("available_booking_classes", []) | |
| if available: | |
| content += "\n## โ ์์ฝ ๊ฐ๋ฅ ํด๋์ค\n" | |
| for cls in available[:5]: | |
| if isinstance(cls, dict): | |
| cabin = cls.get("cabin_class", "") | |
| is_available = cls.get("available", True) | |
| status = "โ ๊ฐ๋ฅ" if is_available else "โ ๋ถ๊ฐ" | |
| content += f"- {cabin}: {status}\n" | |
| content += DISCLAIMER_SHORT | |
| if len(content) > 100: | |
| chunks.append({ | |
| "content": content.strip(), | |
| "metadata": { | |
| "type": "airline_partnership", | |
| "partner_program": partner_program, | |
| "operating_airline": operating_airline, | |
| "alliance": alliance | |
| } | |
| }) | |
| return chunks | |
| # =========================================================================== | |
| # ํธ๋ค๋ฌ: ํญ๊ณต ํ๋ก๊ทธ๋จ (Phase 2) | |
| # =========================================================================== | |
| def handle_airline_programs(data: Any, context: Dict[str, Any]) -> List[Dict[str, Any]]: | |
| """airline_programs ๋๋ airline_program ์ฒ๋ฆฌ""" | |
| chunks = [] | |
| programs = data if isinstance(data, list) else [data] | |
| for program in programs[:5]: | |
| if not isinstance(program, dict): | |
| continue | |
| program_name = program.get("program_name", "N/A") | |
| airline = program.get("airline", "UNKNOWN") | |
| alliance = program.get("alliance", "") | |
| miles_name = program.get("miles_name", "๋ง์ผ๋ฆฌ์ง") | |
| content = f""" | |
| ํญ๊ณต ๋ง์ผ๋ฆฌ์ง ํ๋ก๊ทธ๋จ: {program_name} | |
| ํญ๊ณต์ฌ: {airline} | |
| ๋๋งน: {alliance if alliance else "์์"} | |
| ๋ง์ผ๋ฆฌ์ง ๋ช ์นญ: {miles_name} | |
| ๋ง์ผ๋ฆฌ์ง ๋ง๋ฃ: {program.get("miles_expiration_months", "N/A")}๊ฐ์ | |
| ํ๋์ผ๋ก ์ฐ์ฅ: {"๊ฐ๋ฅ" if program.get("can_extend_with_activity") else "๋ถ๊ฐ"} | |
| """ | |
| # ๋ฑ๊ธ ์ ๋ณด | |
| tiers = program.get("tiers", []) | |
| if tiers: | |
| content += "\n๋ฑ๊ธ:\n" | |
| for tier in tiers[:6]: | |
| if isinstance(tier, dict): | |
| tier_name = tier.get("tier_name", "N/A") | |
| miles_req = tier.get("miles_required") | |
| alliance_tier = tier.get("alliance_tier", "") | |
| tier_info = f" - {tier_name}" | |
| if miles_req: | |
| tier_info += f" ({miles_req:,} ๋ง์ผ)" | |
| if alliance_tier: | |
| tier_info += f" [๋๋งน: {alliance_tier}]" | |
| content += tier_info + "\n" | |
| # ํํธ๋ ํญ๊ณต์ฌ | |
| partners = program.get("partner_airlines", []) | |
| if partners: | |
| content += f"\n์ ํด ํญ๊ณต์ฌ: {', '.join(partners[:8])}\n" | |
| if len(partners) > 8: | |
| content += f" ... ์ธ {len(partners) - 8}๊ฐ\n" | |
| # ํธํ ํํธ๋ | |
| hotel_partners = program.get("hotel_partners", []) | |
| if hotel_partners: | |
| content += f"์ ํด ํธํ : {', '.join(hotel_partners[:5])}\n" | |
| content += DISCLAIMER_SHORT | |
| chunks.append({ | |
| "content": content.strip(), | |
| "metadata": { | |
| "type": "airline_program", | |
| "program_name": program_name, | |
| "airline": airline, | |
| "alliance": alliance | |
| } | |
| }) | |
| # === ๋ด๋ถ ๊ตฌ์ฑ ์์ ์๋ธ์ฒญํน === | |
| # ํ๋ก๊ทธ๋จ๋ณ ์ปจํ ์คํธ ์์ฑ | |
| program_context = {**context, "chain": airline, "airline": airline} | |
| # 1. earning_rules ์ฒ๋ฆฌ (๋ง์ผ๋ฆฌ์ง ์ ๋ฆฝ ๊ท์น) | |
| earning_rules = program.get("earning_rules") | |
| if earning_rules: | |
| try: | |
| sub_chunks = handle_airline_earning_rules(earning_rules, program_context) | |
| chunks.extend(sub_chunks) | |
| except Exception: | |
| pass | |
| # 2. award_chart ์ฒ๋ฆฌ (๋ง์ผ๋ฆฌ์ง ์ฌ์ฉ ์ฐจํธ) | |
| award_chart = program.get("award_chart") | |
| if award_chart: | |
| try: | |
| sub_chunks = handle_award_charts(award_chart, program_context) | |
| chunks.extend(sub_chunks) | |
| except Exception: | |
| pass | |
| # 3. co_branded_cards ์ฒ๋ฆฌ (์ ํด ์ ์ฉ์นด๋) | |
| co_branded_cards = program.get("co_branded_cards") | |
| if co_branded_cards: | |
| try: | |
| sub_chunks = handle_credit_cards(co_branded_cards, program_context) | |
| chunks.extend(sub_chunks) | |
| except Exception: | |
| pass | |
| # 4. tiers๋ฅผ ๋ณ๋ ์ฒญํฌ๋ก ์ฒ๋ฆฌ (์์ธ ๋ฑ๊ธ ์ ๋ณด) | |
| tiers_data = program.get("tiers") | |
| if tiers_data and isinstance(tiers_data, list) and len(tiers_data) > 0: | |
| try: | |
| sub_chunks = handle_airline_tiers(tiers_data, program_context) | |
| chunks.extend(sub_chunks) | |
| except Exception: | |
| pass | |
| return chunks | |
| def handle_airline_tiers(data: Any, context: Dict[str, Any]) -> List[Dict[str, Any]]: | |
| """airline_tiers ์ฒ๋ฆฌ""" | |
| chunks = [] | |
| if data is None: | |
| return chunks | |
| tiers = data if isinstance(data, list) else [data] | |
| for tier in tiers[:10]: | |
| if not isinstance(tier, dict): | |
| continue | |
| tier_name = tier.get("tier_name", "N/A") | |
| airline = tier.get("airline", context.get("chain", "UNKNOWN")) | |
| # f-string ์ค๋ฅ ์์ : miles_required ์ฒ๋ฆฌ | |
| miles_req = tier.get("miles_required") | |
| miles_str = f"{miles_req:,}" if isinstance(miles_req, int) else "N/A" | |
| content = f""" | |
| ํญ๊ณต ๋ฉค๋ฒ์ญ ๋ฑ๊ธ: {tier_name} | |
| ํญ๊ณต์ฌ: {airline} | |
| ๋ฑ๊ธ ์์: {tier.get("rank", "N/A")} | |
| ์๊ฒฉ ์๊ฑด: | |
| - ํ์ ๋ง์ผ๋ฆฌ์ง: {miles_str} | |
| - ํ์ ๊ตฌ๊ฐ: {tier.get("segments_required", "N/A")} | |
| - ์ฐ์ ๊ธฐ๊ฐ: {tier.get("qualification_period", "calendar_year")} | |
| """ | |
| # ๋๋งน ๋ฑ๊ธ | |
| if tier.get("alliance") or tier.get("alliance_tier"): | |
| content += f"\n๋๋งน: {tier.get('alliance', 'N/A')}\n" | |
| content += f"๋๋งน ๋ฑ๊ธ: {tier.get('alliance_tier', 'N/A')}\n" | |
| # ์ฃผ์ ํํ | |
| content += "\n์ฃผ์ ํํ:\n" | |
| if tier.get("lounge_access"): | |
| lounge_details = tier.get("lounge_access_details", "๋ผ์ด์ง ์ด์ฉ ๊ฐ๋ฅ") | |
| content += f" - ๋ผ์ด์ง: {lounge_details}\n" | |
| if tier.get("earning_bonus_percent"): | |
| content += f" - ๋ง์ผ๋ฆฌ์ง ๋ณด๋์ค: {tier.get('earning_bonus_percent')}% ์ถ๊ฐ\n" | |
| if tier.get("complimentary_upgrades"): | |
| content += f" - ๋ฌด๋ฃ ์ ๊ทธ๋ ์ด๋: ์ฐ {tier.get('complimentary_upgrades')}ํ\n" | |
| if tier.get("extra_baggage_kg") or tier.get("extra_baggage_pieces"): | |
| baggage_info = [] | |
| if tier.get("extra_baggage_kg"): | |
| baggage_info.append(f"+{tier.get('extra_baggage_kg')}kg") | |
| if tier.get("extra_baggage_pieces"): | |
| baggage_info.append(f"+{tier.get('extra_baggage_pieces')}๊ฐ") | |
| content += f" - ์ถ๊ฐ ์ํ๋ฌผ: {', '.join(baggage_info)}\n" | |
| # ๊ธฐํ ํํ | |
| benefits = tier.get("benefits", []) | |
| for benefit in benefits[:5]: | |
| if isinstance(benefit, dict): | |
| content += f" - {benefit.get('name', 'N/A')}\n" | |
| content += DISCLAIMER_SHORT | |
| chunks.append({ | |
| "content": content.strip(), | |
| "metadata": { | |
| "type": "airline_tier", | |
| "tier_name": tier_name, | |
| "airline": airline, | |
| "rank": tier.get("rank") | |
| } | |
| }) | |
| return chunks | |
| def handle_award_charts(data: Any, context: Dict[str, Any]) -> List[Dict[str, Any]]: | |
| """award_charts ๋๋ award_chart ์ฒ๋ฆฌ""" | |
| chunks = [] | |
| charts = data if isinstance(data, list) else [data] | |
| for chart in charts[:3]: | |
| if not isinstance(chart, dict): | |
| continue | |
| chart_name = chart.get("chart_name", "๋ง์ผ๋ฆฌ์ง ์ข์ ์ฐจํธ") | |
| airline = chart.get("airline", context.get("chain", "UNKNOWN")) | |
| content = f""" | |
| ๋ง์ผ๋ฆฌ์ง ์ข์ ์ฐจํธ: {chart_name} | |
| ํญ๊ณต์ฌ: {airline} | |
| ์ํ์ผ: {chart.get("effective_date", "N/A")} | |
| ์ ๋ฅํ ์ฆ๋ฃ: {"์ ์ฉ" if chart.get("fuel_surcharge_applies", True) else "๋ฏธ์ ์ฉ"} | |
| ์ธ๊ธ: {"๋ณ๋" if chart.get("taxes_additional", True) else "ํฌํจ"} | |
| """ | |
| # ๊ตฌ๊ฐ๋ณ ํ์ ๋ง์ผ๋ฆฌ์ง | |
| entries = chart.get("entries", []) | |
| regions = chart.get("regions", []) # regions ํค ์ง์ ์ถ๊ฐ | |
| if entries: | |
| content += "\n๊ตฌ๊ฐ๋ณ ํ์ ๋ง์ผ๋ฆฌ์ง (์๋ณต ๊ธฐ์ค):\n" | |
| for entry in entries[:10]: | |
| if isinstance(entry, dict): | |
| route = entry.get("route_type", "N/A") | |
| content += f"\n{route}:\n" | |
| if entry.get("economy"): | |
| content += f" - ์ด์ฝ๋ ธ๋ฏธ: {entry['economy']:,} ๋ง์ผ\n" | |
| if entry.get("premium_economy"): | |
| content += f" - ํ๋ฆฌ๋ฏธ์ ์ด์ฝ๋ ธ๋ฏธ: {entry['premium_economy']:,} ๋ง์ผ\n" | |
| if entry.get("business"): | |
| content += f" - ๋น์ฆ๋์ค: {entry['business']:,} ๋ง์ผ\n" | |
| if entry.get("first"): | |
| content += f" - ํผ์คํธ: {entry['first']:,} ๋ง์ผ\n" | |
| # regions ๊ตฌ์กฐ ์ฒ๋ฆฌ (Asiana ๋ฑ) | |
| elif regions: | |
| content += "\n๊ตฌ๊ฐ๋ณ ํ์ ๋ง์ผ๋ฆฌ์ง:\n" | |
| for region in regions[:10]: | |
| if isinstance(region, dict): | |
| r_name = region.get("region_name", region.get("region_code", "N/A")) | |
| trip_type = region.get("trip_type", "") | |
| trip_label = "์๋ณต" if trip_type == "ROUND_TRIP" else "ํธ๋" if trip_type == "ONE_WAY" else "" | |
| content += f"\n{r_name} ({trip_label}):\n" | |
| cabin_classes = region.get("cabin_classes", []) | |
| for cabin in cabin_classes: | |
| if isinstance(cabin, dict): | |
| c_name = cabin.get("cabin_class", "N/A") | |
| cabin_labels = { | |
| "ECONOMY": "์ผ๋ฐ์", | |
| "PREMIUM_ECONOMY": "ํ๋ฆฌ๋ฏธ์ ์ด์ฝ๋ ธ๋ฏธ", | |
| "BUSINESS": "๋น์ฆ๋์ค", | |
| "BUSINESS_SMARTIUM": "๋น์ฆ๋์ค ์ค๋งํฐ์", | |
| "FIRST": "ํผ์คํธ" | |
| } | |
| c_label = cabin_labels.get(c_name, c_name) | |
| low = cabin.get("low_season_miles", 0) | |
| high = cabin.get("high_season_miles", 0) | |
| if low and high: | |
| content += f" - {c_label}: {low:,}~{high:,} ๋ง์ผ\n" | |
| elif low: | |
| content += f" - {c_label}: {low:,} ๋ง์ผ\n" | |
| # ์ฐธ๊ณ ์ฌํญ | |
| notes = chart.get("notes", []) | |
| if notes: | |
| content += "\n์ฐธ๊ณ :\n" | |
| for note in notes[:5]: | |
| content += f" - {note}\n" | |
| content += DISCLAIMER_PRICE | |
| chunks.append({ | |
| "content": content.strip(), | |
| "metadata": { | |
| "type": "award_chart", | |
| "chart_name": chart_name, | |
| "airline": airline | |
| } | |
| }) | |
| return chunks | |
| def handle_airline_earning_rules(data: Any, context: Dict[str, Any]) -> List[Dict[str, Any]]: | |
| """airline_earning_rules ์ฒ๋ฆฌ (์คํค๋ง AirlineEarningRule ํ๋ ๊ตฌ์กฐ์ ๋ง์ถค)""" | |
| chunks = [] | |
| rules = data if isinstance(data, list) else [data] | |
| airline = context.get("chain", "UNKNOWN") | |
| # ๊ท์น๋ค์ ํ๋์ ์ฒญํฌ๋ก ๋ฌถ๊ธฐ | |
| if not rules: | |
| return chunks | |
| content = f""" | |
| ๋ง์ผ๋ฆฌ์ง ์ ๋ฆฝ ๊ท์น | |
| ํญ๊ณต์ฌ: {airline} | |
| """ | |
| for rule in rules[:15]: | |
| if not isinstance(rule, dict): | |
| continue | |
| rule_name = rule.get("rule_name", "N/A") | |
| cabin = rule.get("cabin_class", "") | |
| content += f"โข {rule_name}\n" | |
| if cabin: | |
| content += f" - ๊ฐ์ค: {cabin}\n" | |
| # ๋ ธ์ ์ ํ | |
| route = rule.get("route_type", "") | |
| if route: | |
| content += f" - ๋ ธ์ : {route}\n" | |
| # ์ ๋ฆฝ๋ฅ (์คํค๋ง ํ๋ ๋ฐ์) | |
| earning_rate = rule.get("earning_rate_percent") | |
| base_miles_type = rule.get("base_miles_type", "FLOWN") | |
| fixed_miles = rule.get("fixed_miles") | |
| if earning_rate: | |
| content += f" - ์ ๋ฆฝ๋ฅ : {earning_rate}%\n" | |
| elif base_miles_type == "FIXED" and fixed_miles: | |
| content += f" - ๊ณ ์ ๋ง์ผ: {fixed_miles:,}๋ง์ผ\n" | |
| # ์์ฝ ํด๋์ค | |
| booking_classes = rule.get("booking_classes", []) | |
| if booking_classes: | |
| content += f" - ์์ฝ ํด๋์ค: {', '.join(booking_classes)}\n" | |
| # ๋ณด๋์ค ๋ง์ผ | |
| bonus_miles = rule.get("bonus_miles") | |
| if bonus_miles: | |
| content += f" - ๋ณด๋์ค: +{bonus_miles:,}๋ง์ผ\n" | |
| # ํ์ ๋ฑ๊ธ | |
| req_tier = rule.get("requires_membership_tier") | |
| if req_tier: | |
| content += f" - ํ์ ๋ฑ๊ธ: {req_tier}\n" | |
| # ์ ํจ ๊ธฐ๊ฐ | |
| validity = rule.get("validity") | |
| if validity and isinstance(validity, dict): | |
| start = validity.get("start_date") | |
| end = validity.get("end_date") | |
| if start or end: | |
| content += f" - ์ ํจ ๊ธฐ๊ฐ: {start or '?'} ~ {end or '?'}\n" | |
| # ์ง์ญ | |
| region = rule.get("region") | |
| if region and region != "GLOBAL": | |
| content += f" - ์ ์ฉ ์ง์ญ: {region}\n" | |
| content += "\n" | |
| content += DISCLAIMER_SHORT | |
| if len(content) > 100: | |
| chunks.append({ | |
| "content": content.strip(), | |
| "metadata": { | |
| "type": "airline_earning_rules", | |
| "airline": airline | |
| } | |
| }) | |
| return chunks | |
| # =========================================================================== | |
| # ํธ๋ค๋ฌ: ํญ๊ณต ์ด์ (Airline Fares) | |
| # =========================================================================== | |
| def handle_airline_fare_tables(data: Any, context: Dict[str, Any]) -> List[Dict[str, Any]]: | |
| """airline_fare_tables ์ฒ๋ฆฌ - ํญ๊ณต ์ด์ํ ๊ธฐ๋ณธ ์ ๋ณด""" | |
| chunks = [] | |
| if not data: | |
| return chunks | |
| items = data if isinstance(data, list) else [data] | |
| for item in items: | |
| airline = item.get("airline", "UNKNOWN") | |
| program = item.get("program_name", "") | |
| currency = item.get("currency", "KRW") | |
| effective_date = item.get("effective_date", "") | |
| content = f"# {airline} ํญ๊ณต ์ด์ํ ์ ๋ณด\n\n" | |
| content += f"**ํญ๊ณต์ฌ**: {airline}\n" | |
| if program: | |
| content += f"**ํ๋ก๊ทธ๋จ**: {program}\n" | |
| content += f"**ํตํ**: {currency}\n" | |
| if effective_date: | |
| content += f"**์ ์ฉ์ผ**: {effective_date}\n" | |
| # ์ผ๋ฐ ์ ๋ณด | |
| general_info = item.get("general_info", {}) | |
| if general_info: | |
| content += f"\n## ์ด์ ํฌํจ ์ฌํญ\n" | |
| fare_includes = general_info.get("fare_includes", []) | |
| if fare_includes: | |
| content += "- " + "\n- ".join(fare_includes) + "\n" | |
| fare_type = general_info.get("fare_type", "") | |
| if fare_type: | |
| content += f"**์ด์ ์ ํ**: {fare_type}\n" | |
| variability = general_info.get("variability_note", "") | |
| if variability: | |
| content += f"**์ฐธ๊ณ **: {variability}\n" | |
| # ์ข์ ๋ฑ๊ธ | |
| cabin_classes = item.get("cabin_classes", []) | |
| if cabin_classes: | |
| content += f"\n## ์ข์ ๋ฑ๊ธ\n" | |
| for cabin in cabin_classes: | |
| code = cabin.get("cabin_code", "") | |
| name = cabin.get("cabin_name", "") | |
| name_en = cabin.get("cabin_name_en", "") | |
| booking_classes = cabin.get("booking_classes", []) | |
| content += f"- **{name}** ({name_en}, {code}): {', '.join(booking_classes)}\n" | |
| # ์์ฆ ์ ๋ณด | |
| seasons = item.get("seasons", []) | |
| if seasons: | |
| content += f"\n## ์์ฆ ๊ตฌ๋ถ\n" | |
| for season in seasons: | |
| code = season.get("season_code", "") | |
| name = season.get("season_name", "") | |
| content += f"- {name} ({code})\n" | |
| # ์ ๋ฅํ ์ฆ๋ฃ | |
| fuel_table = item.get("fuel_surcharge_table", []) | |
| if fuel_table: | |
| content += f"\n## ์ ๋ฅํ ์ฆ๋ฃ\n" | |
| for fuel in fuel_table: | |
| tier = fuel.get("distance_tier", "") | |
| desc = fuel.get("description", "") | |
| trip = fuel.get("trip_type", "") | |
| amount = fuel.get("amount", fuel.get("amount_range", {}).get("min", "")) | |
| content += f"- {tier}" | |
| if desc: | |
| content += f" ({desc})" | |
| content += f": {amount:,}์" if isinstance(amount, int) else f": {amount}" | |
| if trip: | |
| content += f" ({trip})" | |
| content += "\n" | |
| content += DISCLAIMER_SHORT | |
| if len(content) > 100: | |
| chunks.append({ | |
| "content": content.strip(), | |
| "metadata": { | |
| "type": "airline_fare_table", | |
| "airline": airline, | |
| "program": program | |
| } | |
| }) | |
| return chunks | |
| def handle_domestic_fare_rules(data: Any, context: Dict[str, Any]) -> List[Dict[str, Any]]: | |
| """domestic_fare_rules ์ฒ๋ฆฌ - ๊ตญ๋ด์ ์ด์ ๊ท์ """ | |
| chunks = [] | |
| if not data: | |
| return chunks | |
| items = data if isinstance(data, list) else [data] | |
| for item in items: | |
| airline = item.get("airline", "UNKNOWN") | |
| region = item.get("region", "DOMESTIC") | |
| content = f"# {airline} ๊ตญ๋ด์ ์ด์ ๊ท์ \n\n" | |
| fare_basis = item.get("fare_basis", "") | |
| if fare_basis: | |
| content += f"**์ด์ ๊ธฐ์ค**: {fare_basis}\n" | |
| fuel = item.get("fuel_surcharge_oneway", 0) | |
| airport = item.get("airport_fee_oneway", 0) | |
| if fuel or airport: | |
| content += f"**์ ๋ฅํ ์ฆ๋ฃ (ํธ๋)**: {fuel:,}์\n" | |
| content += f"**๊ณตํญ์ด์ฉ๋ฃ (ํธ๋)**: {airport:,}์\n" | |
| # ์์ผ ๊ตฌ๋ถ | |
| weekday = item.get("weekday_days", []) | |
| weekend = item.get("weekend_days", []) | |
| if weekday or weekend: | |
| content += f"\n## ์์ผ ๊ตฌ๋ถ\n" | |
| if weekday: | |
| content += f"- **์ฃผ์ค**: {', '.join(weekday)}\n" | |
| if weekend: | |
| content += f"- **์ฃผ๋ง**: {', '.join(weekend)}\n" | |
| # ์๊ฐ๋ ๊ตฌ๋ถ | |
| time_zones = item.get("time_zones", []) | |
| if time_zones: | |
| content += f"\n## ์๊ฐ๋ ๊ตฌ๋ถ\n" | |
| for tz in time_zones: | |
| direction = tz.get("direction", "") | |
| preferred = tz.get("preferred_time", "") | |
| standard = tz.get("standard_time", "") | |
| content += f"- **{direction}**: ์ ํธ {preferred}, ์ผ๋ฐ {standard}\n" | |
| # ํ๋ ฅํ ์ฆ์ด์ | |
| flex_routes = item.get("flexible_surcharge_routes", []) | |
| if flex_routes: | |
| content += f"\n## ํ๋ ฅํ ์ฆ์ด์ ์ ์ฉ\n" | |
| for route in flex_routes: | |
| direction = route.get("direction", "") | |
| applies = route.get("applies_to", "") | |
| content += f"- **{direction}**: {applies}\n" | |
| content += DISCLAIMER_SHORT | |
| if len(content) > 100: | |
| chunks.append({ | |
| "content": content.strip(), | |
| "metadata": { | |
| "type": "domestic_fare_rules", | |
| "airline": airline, | |
| "region": region | |
| } | |
| }) | |
| return chunks | |
| def handle_domestic_peak_seasons(data: Any, context: Dict[str, Any]) -> List[Dict[str, Any]]: | |
| """domestic_peak_seasons ์ฒ๋ฆฌ - ๊ตญ๋ด์ ์ฑ์๊ธฐ ๊ธฐ๊ฐ""" | |
| chunks = [] | |
| if not data: | |
| return chunks | |
| items = data if isinstance(data, list) else [data] | |
| content = "# ๊ตญ๋ด์ ์ฑ์๊ธฐ ์ ์ฉ ๊ธฐ๊ฐ\n\n" | |
| for item in items: | |
| year = item.get("year", "") | |
| dates = item.get("dates", []) | |
| if year and dates: | |
| content += f"## {year}๋ ์ฑ์๊ธฐ\n" | |
| content += "- " + "\n- ".join(dates) + "\n\n" | |
| content += DISCLAIMER_SHORT | |
| if len(content) > 100: | |
| chunks.append({ | |
| "content": content.strip(), | |
| "metadata": { | |
| "type": "domestic_peak_seasons", | |
| "airline": context.get("chain", "UNKNOWN") | |
| } | |
| }) | |
| return chunks | |
| def handle_domestic_route_fares(data: Any, context: Dict[str, Any]) -> List[Dict[str, Any]]: | |
| """domestic_route_fares ์ฒ๋ฆฌ - ๊ตญ๋ด์ ๋ ธ์ ๋ณ ์ด์""" | |
| chunks = [] | |
| if not data: | |
| return chunks | |
| items = data if isinstance(data, list) else [data] | |
| for item in items: | |
| # ์ ๋ฐ์ดํฐ ๊ตฌ์กฐ ์ฒ๋ฆฌ | |
| origin_name = item.get("origin_name", "") | |
| dest_name = item.get("destination_name", "") | |
| route = item.get("route", f"{origin_name}-{dest_name}") | |
| airline = item.get("airline", context.get("chain", "UNKNOWN")) | |
| cabin_name = item.get("cabin_name", "") | |
| cabin_class = item.get("cabin_class", "") | |
| trip_type = item.get("trip_type", "ONE_WAY") | |
| content = f"# {airline} {route} ๊ตญ๋ด์ ์ด์\n\n" | |
| content += f"**๋ ธ์ **: {origin_name} โ {dest_name}\n" | |
| if cabin_name: | |
| content += f"**์ข์๋ฑ๊ธ**: {cabin_name} ({cabin_class})\n" | |
| content += f"**์ด์์ ํ**: {'ํธ๋' if trip_type == 'ONE_WAY' else '์๋ณต'}\n" | |
| # fares๊ฐ ๋์ ๋๋ฆฌ์ธ ๊ฒฝ์ฐ (์ ๊ตฌ์กฐ) | |
| fares = item.get("fares", {}) | |
| if isinstance(fares, dict): | |
| content += f"\n## ์ด์ํ\n" | |
| wd_pref = fares.get("weekday_preferred", "-") | |
| wd_std = fares.get("weekday_standard", "-") | |
| we_pref = fares.get("weekend_preferred", "-") | |
| we_std = fares.get("weekend_standard", "-") | |
| peak_flex = fares.get("peak_flexible", "-") | |
| if isinstance(wd_pref, int): | |
| wd_pref = f"{wd_pref:,}" | |
| if isinstance(wd_std, int): | |
| wd_std = f"{wd_std:,}" | |
| if isinstance(we_pref, int): | |
| we_pref = f"{we_pref:,}" | |
| if isinstance(we_std, int): | |
| we_std = f"{we_std:,}" | |
| if isinstance(peak_flex, int): | |
| peak_flex = f"{peak_flex:,}" | |
| content += f"- **์ฃผ์ค์ ํธ**: {wd_pref}์\n" | |
| content += f"- **์ฃผ์ค์ผ๋ฐ**: {wd_std}์\n" | |
| content += f"- **์ฃผ๋ง์ ํธ**: {we_pref}์\n" | |
| content += f"- **์ฃผ๋ง์ผ๋ฐ**: {we_std}์\n" | |
| if peak_flex != "-": | |
| content += f"- **์ฑ์๊ธฐํ๋ ฅ**: {peak_flex}์\n" | |
| # fares๊ฐ ๋ฆฌ์คํธ์ธ ๊ฒฝ์ฐ (์ด์ ๊ตฌ์กฐ) | |
| elif isinstance(fares, list) and fares: | |
| content += f"\n## ์ด์ํ (ํธ๋)\n" | |
| content += "| ๋ฑ๊ธ | ์์ฝํด๋์ค | ์ฃผ์ค์ผ๋ฐ | ์ฃผ์ค์ ํธ | ์ฃผ๋ง์ผ๋ฐ | ์ฃผ๋ง์ ํธ |\n" | |
| content += "|------|-----------|---------|---------|---------|--------|\n" | |
| for fare in fares: | |
| if isinstance(fare, dict): | |
| cabin = fare.get("cabin_name", "") | |
| booking = fare.get("booking_class", "") | |
| wd_std = fare.get("weekday_standard", "-") | |
| wd_pref = fare.get("weekday_preferred", "-") | |
| we_std = fare.get("weekend_standard", "-") | |
| we_pref = fare.get("weekend_preferred", "-") | |
| if isinstance(wd_std, int): | |
| wd_std = f"{wd_std:,}" | |
| if isinstance(wd_pref, int): | |
| wd_pref = f"{wd_pref:,}" | |
| if isinstance(we_std, int): | |
| we_std = f"{we_std:,}" | |
| if isinstance(we_pref, int): | |
| we_pref = f"{we_pref:,}" | |
| content += f"| {cabin} | {booking} | {wd_std} | {wd_pref} | {we_std} | {we_pref} |\n" | |
| content += DISCLAIMER_SHORT | |
| if len(content) > 100: | |
| chunks.append({ | |
| "content": content.strip(), | |
| "metadata": { | |
| "type": "domestic_route_fares", | |
| "airline": airline, | |
| "route": route, | |
| "cabin_class": cabin_class | |
| } | |
| }) | |
| return chunks | |
| def handle_codeshare_domestic_fares(data: Any, context: Dict[str, Any]) -> List[Dict[str, Any]]: | |
| """codeshare_domestic_fares ์ฒ๋ฆฌ - ๊ณต๋์ดํญ ๊ตญ๋ด์ ์ด์""" | |
| chunks = [] | |
| if not data: | |
| return chunks | |
| items = data if isinstance(data, list) else [data] | |
| for item in items: | |
| route = item.get("route", "") | |
| operating_carrier = item.get("operating_carrier", "") | |
| marketing_carrier = item.get("marketing_carrier", context.get("chain", "")) | |
| content = f"# {marketing_carrier} ๊ณต๋์ดํญ ({operating_carrier}) {route} ์ด์\n\n" | |
| content += f"**์ดํญ์ฌ**: {operating_carrier}\n" | |
| content += f"**ํ๋งค์ฌ**: {marketing_carrier}\n" | |
| # ์ด์ ์ ๋ณด | |
| fares = item.get("fares", []) | |
| if fares: | |
| content += f"\n## ์ด์ํ (ํธ๋)\n" | |
| content += "| ๋ฑ๊ธ | ์์ฝํด๋์ค | ์ฃผ์ค์ผ๋ฐ | ์ฃผ์ค์ ํธ | ์ฃผ๋ง์ผ๋ฐ | ์ฃผ๋ง์ ํธ |\n" | |
| content += "|------|-----------|---------|---------|---------|--------|\n" | |
| for fare in fares: | |
| cabin = fare.get("cabin_name", "") | |
| booking = fare.get("booking_class", "") | |
| wd_std = fare.get("weekday_standard", "-") | |
| wd_pref = fare.get("weekday_preferred", "-") | |
| we_std = fare.get("weekend_standard", "-") | |
| we_pref = fare.get("weekend_preferred", "-") | |
| if isinstance(wd_std, int): | |
| wd_std = f"{wd_std:,}" | |
| if isinstance(wd_pref, int): | |
| wd_pref = f"{wd_pref:,}" | |
| if isinstance(we_std, int): | |
| we_std = f"{we_std:,}" | |
| if isinstance(we_pref, int): | |
| we_pref = f"{we_pref:,}" | |
| content += f"| {cabin} | {booking} | {wd_std} | {wd_pref} | {we_std} | {we_pref} |\n" | |
| content += DISCLAIMER_SHORT | |
| if len(content) > 100: | |
| chunks.append({ | |
| "content": content.strip(), | |
| "metadata": { | |
| "type": "codeshare_domestic_fares", | |
| "airline": marketing_carrier, | |
| "operating_carrier": operating_carrier, | |
| "route": route | |
| } | |
| }) | |
| return chunks | |
| def handle_international_route_fares(data: Any, context: Dict[str, Any]) -> List[Dict[str, Any]]: | |
| """international_route_fares ์ฒ๋ฆฌ - ๊ตญ์ ์ ๋ ธ์ ๋ณ ์ด์""" | |
| chunks = [] | |
| if not data: | |
| return chunks | |
| items = data if isinstance(data, list) else [data] | |
| for item in items: | |
| route = item.get("route", "") | |
| region = item.get("region", "") | |
| airline = item.get("airline", context.get("chain", "UNKNOWN")) | |
| trip_type = item.get("trip_type", "ROUND_TRIP") | |
| content = f"# {airline} {route} ๊ตญ์ ์ ์ด์ ({region})\n\n" | |
| content += f"**๋ ธ์ **: {route}\n" | |
| content += f"**์ง์ญ**: {region}\n" | |
| content += f"**์ด์ ์ ํ**: {'์๋ณต' if trip_type == 'ROUND_TRIP' else 'ํธ๋'}\n" | |
| # ์ธ๊ธ/๋ถ๊ฐ์ธ | |
| fuel = item.get("fuel_surcharge", 0) | |
| tax = item.get("tax", 0) | |
| if fuel or tax: | |
| content += f"**์ ๋ฅํ ์ฆ๋ฃ**: {fuel:,}์\n" | |
| content += f"**์ธ๊ธ**: {tax:,}์\n" | |
| # ์ด์ ์ ๋ณด | |
| fares = item.get("fares", []) | |
| if fares: | |
| content += f"\n## ์ด์ํ\n" | |
| content += "| ๋ฑ๊ธ | ์์ฝํด๋์ค | ๋น์๊ธฐ | ์ค์ฑ์๊ธฐ | ์ฑ์๊ธฐ |\n" | |
| content += "|------|-----------|---------|---------|--------|\n" | |
| for fare in fares: | |
| cabin = fare.get("cabin_name", "") | |
| booking = fare.get("booking_class", "") | |
| low = fare.get("low_season", "-") | |
| shoulder = fare.get("shoulder_season", "-") | |
| high = fare.get("high_season", "-") | |
| if isinstance(low, int): | |
| low = f"{low:,}" | |
| if isinstance(shoulder, int): | |
| shoulder = f"{shoulder:,}" | |
| if isinstance(high, int): | |
| high = f"{high:,}" | |
| content += f"| {cabin} | {booking} | {low} | {shoulder} | {high} |\n" | |
| content += DISCLAIMER_SHORT | |
| if len(content) > 100: | |
| chunks.append({ | |
| "content": content.strip(), | |
| "metadata": { | |
| "type": "international_route_fares", | |
| "airline": airline, | |
| "route": route, | |
| "region": region | |
| } | |
| }) | |
| return chunks | |
| def handle_fare_comparison_summary(data: Any, context: Dict[str, Any]) -> List[Dict[str, Any]]: | |
| """fare_comparison_summary ์ฒ๋ฆฌ - ์ด์ ๋น๊ต ์์ฝ""" | |
| chunks = [] | |
| if not data: | |
| return chunks | |
| items = data if isinstance(data, list) else [data] | |
| content = "# ๋ ธ์ ๋ณ ์ต์ ๊ฐ ์ด์ ๋น๊ต\n\n" | |
| content += "| ์ง์ญ | ๋ ธ์ | ์๋ณต์ ํ | ์ต์ ์ด์ | ์ ๋ฅํ ์ฆ๋ฃ | ์ธ๊ธ | ์ด์ก |\n" | |
| content += "|------|------|----------|---------|-----------|-----|------|\n" | |
| for item in items: | |
| region = item.get("region", "") | |
| route = item.get("route", "") | |
| trip = item.get("trip_type", "") | |
| lowest = item.get("lowest_economy", 0) | |
| fuel = item.get("fuel_surcharge", 0) | |
| tax = item.get("tax", 0) | |
| total = item.get("total", 0) | |
| note = item.get("note", "") | |
| trip_str = "์๋ณต" if trip == "ROUND_TRIP" else "ํธ๋" | |
| content += f"| {region} | {route} | {trip_str} | {lowest:,} | {fuel:,} | {tax:,} | {total:,} |\n" | |
| content += DISCLAIMER_SHORT | |
| if len(content) > 100: | |
| chunks.append({ | |
| "content": content.strip(), | |
| "metadata": { | |
| "type": "fare_comparison_summary", | |
| "airline": context.get("chain", "UNKNOWN") | |
| } | |
| }) | |
| return chunks | |
| def handle_important_notes(data: Any, context: Dict[str, Any]) -> List[Dict[str, Any]]: | |
| """important_notes ์ฒ๋ฆฌ - ์ค์ ์ฐธ๊ณ ์ฌํญ""" | |
| chunks = [] | |
| if not data: | |
| return chunks | |
| items = data if isinstance(data, list) else [data] | |
| content = "# ์ด์ ๊ด๋ จ ์ค์ ์ฐธ๊ณ ์ฌํญ\n\n" | |
| for item in items: | |
| note_id = item.get("note_id", "") | |
| description = item.get("description", "") | |
| if description: | |
| content += f"- **{note_id}**: {description}\n" | |
| content += DISCLAIMER_SHORT | |
| if len(content) > 100: | |
| chunks.append({ | |
| "content": content.strip(), | |
| "metadata": { | |
| "type": "important_notes", | |
| "category": context.get("chain", "UNKNOWN") | |
| } | |
| }) | |
| return chunks | |
| # =========================================================================== | |
| # ํธ๋ค๋ฌ: ํญ๊ณต ๋ถ๊ฐ ์๊ธ ๊ท์ (Extra Fee Rules) | |
| # =========================================================================== | |
| def handle_baggage_rules(data: Any, context: Dict[str, Any]) -> List[Dict[str, Any]]: | |
| """baggage_rules ์ฒ๋ฆฌ - ์ํ๋ฌผ ๊ท์ """ | |
| chunks = [] | |
| if not data: | |
| return chunks | |
| items = data if isinstance(data, list) else [data] | |
| for item in items: | |
| airline = item.get("airline", context.get("chain", "UNKNOWN")) | |
| rule_name = item.get("rule_name", "์ํ๋ฌผ ๊ท์ ") | |
| route_type = item.get("route_type", "") | |
| content = f"# {airline} {rule_name}\n\n" | |
| if route_type: | |
| route_label = "๊ตญ๋ด์ " if route_type == "DOMESTIC" else "๊ตญ์ ์ " if route_type == "INTERNATIONAL" else route_type | |
| content += f"**๋ ธ์ ์ ํ**: {route_label}\n" | |
| # Allowances | |
| allowances = item.get("allowances", []) | |
| if allowances: | |
| content += f"\n## ๋ฌด๋ฃ ์ํ๋ฌผ ํ์ฉ๋\n" | |
| for allow in allowances: | |
| if isinstance(allow, dict): | |
| cabin = allow.get("cabin_class_name") or allow.get("cabin_class", "") | |
| fare = allow.get("fare_class", "") | |
| pieces = allow.get("pieces", "") | |
| weight = allow.get("weight_kg") or allow.get("max_weight_per_piece_kg", "") | |
| info = f"- **{cabin}**" | |
| if fare: | |
| info += f" ({fare})" | |
| info += ": " | |
| if pieces: | |
| info += f"{pieces}๊ฐ" | |
| if weight: | |
| info += f" (๊ฐ {weight}kg)" if pieces else f"{weight}kg" | |
| content += info + "\n" | |
| # Excess fee | |
| excess = item.get("excess_fee", {}) | |
| if excess: | |
| desc = excess.get("description", "") | |
| amount = excess.get("amount", 0) | |
| currency = excess.get("currency", "KRW") | |
| unit = excess.get("unit", "") | |
| if desc: | |
| content += f"\n**์ด๊ณผ ์๊ธ**: {desc}\n" | |
| elif amount: | |
| content += f"\n**์ด๊ณผ ์๊ธ**: {amount:,} {currency} ({unit})\n" | |
| content += DISCLAIMER_SHORT | |
| if len(content) > 100: | |
| chunks.append({ | |
| "content": content.strip(), | |
| "metadata": { | |
| "type": "baggage_rules", | |
| "airline": airline, | |
| "route_type": route_type | |
| } | |
| }) | |
| return chunks | |
| def handle_baggage_general_rules(data: Any, context: Dict[str, Any]) -> List[Dict[str, Any]]: | |
| """baggage_general_rules ์ฒ๋ฆฌ - ์ผ๋ฐ ์ํ๋ฌผ ๊ท์ (์ ์ฑ , ๊ธ์ง ํ๋ชฉ, ์ ํ ์ฌํญ ๋ฑ) | |
| baggage_rules์ ๋ฌ๋ฆฌ ๊ตฌ์ฒด์ ์ธ ํ์ฉ๋๋ณด๋ค ์ ์ฑ ์ ์ธ ๋ด์ฉ์ ๋ค๋ฃธ: | |
| - ์ํ๋ฌผ ์ ์ฑ ๊ฐ์ | |
| - ์ํ/๊ธฐ๋ด/ํด๋ ์ํ๋ฌผ ์ ํ | |
| - ๊ธ์ง ํ๋ชฉ | |
| - ํน์ ์ํ๋ฌผ ์ ์ฑ | |
| """ | |
| chunks = [] | |
| if not data: | |
| return chunks | |
| items = data if isinstance(data, list) else [data] | |
| airline = context.get("chain", "UNKNOWN") | |
| for item in items: | |
| if not isinstance(item, dict): | |
| continue | |
| rule_name = item.get("rule_name", "์ผ๋ฐ ์ํ๋ฌผ ๊ท์ ") | |
| category = item.get("category", "") | |
| route_type = item.get("route_type", "") | |
| content = f"# {airline} {rule_name}\n\n" | |
| if route_type: | |
| route_label = "๊ตญ๋ด์ " if route_type == "DOMESTIC" else "๊ตญ์ ์ " if route_type == "INTERNATIONAL" else route_type | |
| content += f"**๋ ธ์ ์ ํ**: {route_label}\n" | |
| if category: | |
| content += f"**๋ถ๋ฅ**: {category}\n" | |
| # ์ ์ฑ ์ค๋ช | |
| description = item.get("description", "") | |
| if description: | |
| content += f"\n{description}\n" | |
| # ๊ธ์ง ํ๋ชฉ (prohibited_items) | |
| prohibited = item.get("prohibited_items", []) | |
| if prohibited: | |
| content += "\n## ๊ธ์ง ํ๋ชฉ\n" | |
| for p_item in prohibited: | |
| if isinstance(p_item, dict): | |
| item_name = p_item.get("item", p_item.get("name", "")) | |
| reason = p_item.get("reason", "") | |
| location = p_item.get("location", "") # cabin_only, checked_only, etc. | |
| content += f"- **{item_name}**" | |
| if location: | |
| content += f" ({location})" | |
| if reason: | |
| content += f": {reason}" | |
| content += "\n" | |
| elif isinstance(p_item, str): | |
| content += f"- {p_item}\n" | |
| # ์ ํ ํ๋ชฉ (restricted_items) | |
| restricted = item.get("restricted_items", []) | |
| if restricted: | |
| content += "\n## ์ ํ ํ๋ชฉ\n" | |
| for r_item in restricted: | |
| if isinstance(r_item, dict): | |
| item_name = r_item.get("item", r_item.get("name", "")) | |
| condition = r_item.get("condition", "") | |
| max_qty = r_item.get("max_quantity", "") | |
| content += f"- **{item_name}**" | |
| if max_qty: | |
| content += f" (์ต๋ {max_qty}๊ฐ)" | |
| if condition: | |
| content += f": {condition}" | |
| content += "\n" | |
| elif isinstance(r_item, str): | |
| content += f"- {r_item}\n" | |
| # ๊ธฐ๋ด ์ํ๋ฌผ ์ ์ฑ (carry_on_policy) | |
| carry_on = item.get("carry_on_policy", {}) | |
| if carry_on and isinstance(carry_on, dict): | |
| content += "\n## ๊ธฐ๋ด ์ํ๋ฌผ\n" | |
| max_weight = carry_on.get("max_weight_kg", "") | |
| max_dims = carry_on.get("max_dimensions", "") | |
| max_pieces = carry_on.get("max_pieces", "") | |
| if max_weight: | |
| content += f"- ์ต๋ ๋ฌด๊ฒ: {max_weight}kg\n" | |
| if max_dims: | |
| content += f"- ์ต๋ ๊ท๊ฒฉ: {max_dims}\n" | |
| if max_pieces: | |
| content += f"- ์ต๋ ๊ฐ์: {max_pieces}๊ฐ\n" | |
| # ์ํ ์ํ๋ฌผ ์ ์ฑ (checked_policy) | |
| checked = item.get("checked_policy", {}) | |
| if checked and isinstance(checked, dict): | |
| content += "\n## ์ํ ์ํ๋ฌผ\n" | |
| max_weight = checked.get("max_weight_kg", "") | |
| max_dims = checked.get("max_dimensions", "") | |
| if max_weight: | |
| content += f"- ์ต๋ ๋ฌด๊ฒ: {max_weight}kg\n" | |
| if max_dims: | |
| content += f"- ์ต๋ ๊ท๊ฒฉ: {max_dims}\n" | |
| # ํน๋ณ ๊ท์ (special_rules) | |
| special_rules = item.get("special_rules", []) | |
| if special_rules: | |
| content += "\n## ํน๋ณ ๊ท์ \n" | |
| for rule in special_rules: | |
| if isinstance(rule, dict): | |
| rule_title = rule.get("title", rule.get("rule", "")) | |
| rule_desc = rule.get("description", "") | |
| content += f"- **{rule_title}**" | |
| if rule_desc: | |
| content += f": {rule_desc}" | |
| content += "\n" | |
| elif isinstance(rule, str): | |
| content += f"- {rule}\n" | |
| # ์ฐธ๊ณ ์ฌํญ (notes) | |
| notes = item.get("notes", []) | |
| if notes: | |
| content += "\n## ์ฐธ๊ณ ์ฌํญ\n" | |
| for note in notes: | |
| content += f"- {note}\n" | |
| content += DISCLAIMER_SHORT | |
| if len(content) > 100: | |
| chunks.append({ | |
| "content": content.strip(), | |
| "metadata": { | |
| "type": "baggage_general_rules", | |
| "airline": airline, | |
| "category": category, | |
| "route_type": route_type | |
| } | |
| }) | |
| return chunks | |
| def handle_sports_equipment_rules(data: Any, context: Dict[str, Any]) -> List[Dict[str, Any]]: | |
| """sports_equipment_rules ์ฒ๋ฆฌ - ์คํฌ์ธ ์ฅ๋น ๊ท์ """ | |
| chunks = [] | |
| if not data: | |
| return chunks | |
| items = data if isinstance(data, list) else [data] | |
| airline = context.get("chain", "UNKNOWN") | |
| content = f"# {airline} ์คํฌ์ธ ์ฅ๋น ์ด์ก ๊ท์ \n\n" | |
| for item in items: | |
| rule_name = item.get("rule_name", "") | |
| route_type = item.get("route_type", "") | |
| equipment_type = item.get("equipment_type", "") | |
| if rule_name: | |
| content += f"## {rule_name}\n" | |
| max_weight = item.get("max_weight_kg", "") | |
| max_dims = item.get("max_dimensions_cm", "") | |
| if max_weight: | |
| content += f"- **์ต๋ ๋ฌด๊ฒ**: {max_weight}kg\n" | |
| if max_dims: | |
| note = item.get("dimensions_note", "") | |
| content += f"- **์ต๋ ๊ท๊ฒฉ**: {max_dims}cm ({note})\n" if note else f"- **์ต๋ ๊ท๊ฒฉ**: {max_dims}cm\n" | |
| equipment_types = item.get("equipment_types", []) | |
| if equipment_types: | |
| content += f"- **์ ์ฉ ์ฅ๋น**: {', '.join(equipment_types)}\n" | |
| exceptions = item.get("exceptions", []) | |
| if exceptions: | |
| content += f"\n**์์ธ ๊ท์ **:\n" | |
| for exc in exceptions: | |
| cond = exc.get("condition", "") | |
| waiver = exc.get("waiver", "") | |
| content += f"- {cond}: {waiver}\n" | |
| policy = item.get("policy", "") | |
| if policy: | |
| content += f"- **์ ์ฑ **: {policy}\n" | |
| content += "\n" | |
| content += DISCLAIMER_SHORT | |
| if len(content) > 100: | |
| chunks.append({ | |
| "content": content.strip(), | |
| "metadata": { | |
| "type": "sports_equipment_rules", | |
| "airline": airline | |
| } | |
| }) | |
| return chunks | |
| def handle_pet_transport_rules(data: Any, context: Dict[str, Any]) -> List[Dict[str, Any]]: | |
| """pet_transport_rules ์ฒ๋ฆฌ - ๋ฐ๋ ค๋๋ฌผ ์ด์ก ๊ท์ """ | |
| chunks = [] | |
| if not data: | |
| return chunks | |
| items = data if isinstance(data, list) else [data] | |
| airline = context.get("chain", "UNKNOWN") | |
| content = f"# {airline} ๋ฐ๋ ค๋๋ฌผ ์ด์ก ๊ท์ \n\n" | |
| for item in items: | |
| rule_name = item.get("rule_name", "") | |
| route_type = item.get("route_type", "") | |
| if rule_name: | |
| content += f"## {rule_name}\n" | |
| # Basic conditions | |
| allowed_pets = item.get("allowed_pets", []) | |
| if allowed_pets: | |
| content += f"- **ํ์ฉ ๋๋ฌผ**: {', '.join(allowed_pets)}\n" | |
| cabin_weight = item.get("cabin_max_weight_kg", "") | |
| cargo_weight = item.get("cargo_max_weight_kg", "") | |
| if cabin_weight: | |
| content += f"- **๊ธฐ๋ด ๋ฐ์ ์ต๋ ๋ฌด๊ฒ**: {cabin_weight}kg (์ฉ๊ธฐ ํฌํจ)\n" | |
| if cargo_weight: | |
| content += f"- **ํ๋ฌผ์นธ ์ต๋ ๋ฌด๊ฒ**: {cargo_weight}kg (์ฉ๊ธฐ ํฌํจ)\n" | |
| # Fees | |
| fees = item.get("fees", []) | |
| if fees: | |
| content += f"\n**์๊ธ**:\n" | |
| for fee in fees: | |
| if isinstance(fee, dict): | |
| weight = fee.get("weight_range", "") or fee.get("route_type", "") | |
| route_desc = fee.get("route_description", "") | |
| fee_krw = fee.get("fee_krw", 0) | |
| transport_type = fee.get("transport_type", "") | |
| info = f"- {weight}" | |
| if route_desc: | |
| info += f" ({route_desc})" | |
| if transport_type: | |
| info += f" [{transport_type}]" | |
| info += f": {fee_krw:,}์" | |
| content += info + "\n" | |
| # Container requirements | |
| container = item.get("container_requirements", {}) | |
| if container: | |
| content += f"\n**์ฉ๊ธฐ ์กฐ๊ฑด**:\n" | |
| max_dims = container.get("max_dimensions_cm", "") | |
| max_height = container.get("max_height_cm", "") | |
| material = container.get("material", "") | |
| if max_dims: | |
| content += f"- ์ต๋ ํฌ๊ธฐ: {max_dims}cm\n" | |
| if max_height: | |
| content += f"- ์ต๋ ๋์ด: {max_height}cm\n" | |
| if material: | |
| content += f"- ์ฌ์ง: {material}\n" | |
| # Restrictions | |
| restrictions = item.get("restrictions", {}) | |
| if restrictions: | |
| content += f"\n**์ ํ ์ฌํญ**:\n" | |
| cargo = restrictions.get("cargo_transport", "") | |
| cabin = restrictions.get("cabin_transport", "") | |
| if cargo: | |
| content += f"- ํ๋ฌผ์นธ: {cargo}\n" | |
| if cabin: | |
| content += f"- ๊ธฐ๋ด: {cabin}\n" | |
| content += "\n" | |
| content += DISCLAIMER_SHORT | |
| if len(content) > 100: | |
| chunks.append({ | |
| "content": content.strip(), | |
| "metadata": { | |
| "type": "pet_transport_rules", | |
| "airline": airline | |
| } | |
| }) | |
| return chunks | |
| # =========================================================================== | |
| # ํธ๋ค๋ฌ: ํญ๊ณต ์ด์ ์ถ๊ฐ ์ ๋ณด (์์ฆ, ํด๋์ค, ์ธ๊ธ, ๊ณตํญ, ์ ๋ฅํ ์ฆ๋ฃ, ํฉํธ) | |
| # =========================================================================== | |
| def handle_international_season_definitions(data: Any, context: Dict[str, Any]) -> List[Dict[str, Any]]: | |
| """international_season_definitions ์ฒ๋ฆฌ - ๊ตญ์ ์ ์์ฆ ์ ์""" | |
| chunks = [] | |
| if not data: | |
| return chunks | |
| items = data if isinstance(data, list) else [data] | |
| for item in items: | |
| airline = item.get("airline", context.get("chain", "UNKNOWN")) | |
| description = item.get("description", "") | |
| content = f"# {airline} ๊ตญ์ ์ ์์ฆ ๊ตฌ๋ถ\n\n" | |
| if description: | |
| content += f"{description}\n\n" | |
| seasons = item.get("seasons", []) | |
| for season in seasons: | |
| season_type = season.get("season_type", "") | |
| season_name = season.get("season_name", "") | |
| content += f"## {season_name} ({season_type})\n" | |
| periods = season.get("typical_periods", []) | |
| if periods: | |
| content += "**๊ธฐ๊ฐ**:\n" | |
| for period in periods: | |
| content += f"- {period}\n" | |
| notes = item.get("notes", []) | |
| if notes: | |
| content += "\n**์ฐธ๊ณ ์ฌํญ**:\n" | |
| for note in notes: | |
| content += f"- {note}\n" | |
| content += DISCLAIMER_SHORT | |
| if len(content) > 100: | |
| chunks.append({ | |
| "content": content.strip(), | |
| "metadata": { | |
| "type": "international_season_definitions", | |
| "airline": airline | |
| } | |
| }) | |
| return chunks | |
| def handle_booking_class_definitions(data: Any, context: Dict[str, Any]) -> List[Dict[str, Any]]: | |
| """booking_class_definitions ์ฒ๋ฆฌ - ์์ฝ ํด๋์ค ์ ์""" | |
| chunks = [] | |
| if not data: | |
| return chunks | |
| items = data if isinstance(data, list) else [data] | |
| for item in items: | |
| airline = item.get("airline", context.get("chain", "UNKNOWN")) | |
| cabin_class = item.get("cabin_class", "") | |
| cabin_name = "๋น์ฆ๋์ค์" if cabin_class == "BUSINESS" else "์ผ๋ฐ์" if cabin_class == "ECONOMY" else cabin_class | |
| content = f"# {airline} {cabin_name} ์์ฝ ํด๋์ค ์๋ด\n\n" | |
| classes = item.get("classes", []) | |
| for cls in classes: | |
| code = cls.get("code", "") | |
| name = cls.get("name", "") | |
| desc = cls.get("description", "") | |
| flexibility = cls.get("flexibility", "") | |
| refundable = cls.get("refundable") | |
| changeable = cls.get("changeable") | |
| content += f"## {code} ํด๋์ค - {name}\n" | |
| if desc: | |
| content += f"- {desc}\n" | |
| if flexibility: | |
| flex_label = {"HIGH": "๋์", "MEDIUM": "์ค๊ฐ", "LOW": "๋ฎ์", "VERY_LOW": "๋งค์ฐ ๋ฎ์"}.get(flexibility, flexibility) | |
| content += f"- **์ ์ฐ์ฑ**: {flex_label}\n" | |
| if refundable is not None: | |
| content += f"- **ํ๋ถ ๊ฐ๋ฅ**: {'์' if refundable else '์๋์ค'}\n" | |
| if changeable is not None: | |
| content += f"- **๋ณ๊ฒฝ ๊ฐ๋ฅ**: {'์' if changeable else '์๋์ค'}\n" | |
| content += "\n" | |
| content += DISCLAIMER_SHORT | |
| if len(content) > 100: | |
| chunks.append({ | |
| "content": content.strip(), | |
| "metadata": { | |
| "type": "booking_class_definitions", | |
| "airline": airline, | |
| "cabin_class": cabin_class | |
| } | |
| }) | |
| return chunks | |
| def handle_special_tax_structures(data: Any, context: Dict[str, Any]) -> List[Dict[str, Any]]: | |
| """special_tax_structures ์ฒ๋ฆฌ - ํน์ ์ธ๊ธ ๊ตฌ์กฐ""" | |
| chunks = [] | |
| if not data: | |
| return chunks | |
| items = data if isinstance(data, list) else [data] | |
| airline = context.get("chain", "UNKNOWN") | |
| content = f"# {airline} ๋ ธ์ ๋ณ ํน์ ์ธ๊ธ ์๋ด\n\n" | |
| content += "์ผ๋ถ ๋ ธ์ ์ ๋์ฐฉ ๊ตญ๊ฐ์ ํน๋ณ ์ธ๊ธ์ผ๋ก ์ธํด ํ ๋ ธ์ ๋๋น ์ธ๊ธ์ด ๋์ต๋๋ค.\n\n" | |
| for item in items: | |
| route = item.get("route", "") | |
| route_name = item.get("route_name", "") | |
| tax_note = item.get("tax_note", "") | |
| content += f"## {route_name} ({route})\n" | |
| if tax_note: | |
| content += f"**ํน์ด์ฌํญ**: {tax_note}\n\n" | |
| tax_by_class = item.get("tax_by_class", []) | |
| if tax_by_class: | |
| content += "| ์ข์ ๋ฑ๊ธ | ์ธ๊ธ |\n|----------|------|\n" | |
| for tax in tax_by_class: | |
| cabin = tax.get("cabin_class", "") | |
| cabin_label = "๋น์ฆ๋์ค" if cabin == "BUSINESS" else "์ผ๋ฐ์" if cabin == "ECONOMY" else cabin | |
| amount = tax.get("tax_amount", 0) | |
| currency = tax.get("currency", "KRW") | |
| content += f"| {cabin_label} | {amount:,}์ |\n" | |
| content += "\n" | |
| content += DISCLAIMER_SHORT | |
| if len(content) > 100: | |
| chunks.append({ | |
| "content": content.strip(), | |
| "metadata": { | |
| "type": "special_tax_structures", | |
| "airline": airline | |
| } | |
| }) | |
| return chunks | |
| def handle_multi_airport_routes(data: Any, context: Dict[str, Any]) -> List[Dict[str, Any]]: | |
| """multi_airport_routes ์ฒ๋ฆฌ - ๋ณต์ ๊ณตํญ ๋ ธ์ ์ ๋ณด""" | |
| chunks = [] | |
| if not data: | |
| return chunks | |
| items = data if isinstance(data, list) else [data] | |
| airline = context.get("chain", "UNKNOWN") | |
| content = f"# {airline} ๋ณต์ ๊ณตํญ ๋ ธ์ ์ ๋ณด\n\n" | |
| content += "์ผ๋ถ ๋์๋ ์ฌ๋ฌ ๊ณตํญ์์ ์ดํญ๋ฉ๋๋ค.\n\n" | |
| for item in items: | |
| city = item.get("city", "") | |
| note = item.get("note", "") | |
| content += f"## {city}\n" | |
| airports = item.get("airports", []) | |
| for airport in airports: | |
| code = airport.get("code", "") | |
| name = airport.get("name", "") | |
| ap_type = airport.get("type", "") | |
| type_label = "(๋ฉ์ธ)" if ap_type == "MAIN" else "(์ํฐ)" if ap_type == "CITY" else "" | |
| content += f"- **{code}** - {name} {type_label}\n" | |
| routes_from = item.get("routes_from_gmp", []) | |
| if routes_from: | |
| content += "\n**๊นํฌ ์ถ๋ฐ ๊ฐ๋ฅ ๋ ธ์ **:\n" | |
| for route in routes_from: | |
| dest = route.get("destination", "") | |
| dest_name = route.get("destination_name", "") | |
| content += f"- {dest_name} ({dest})\n" | |
| if note: | |
| content += f"\n**์ฐธ๊ณ **: {note}\n" | |
| content += "\n" | |
| content += DISCLAIMER_SHORT | |
| if len(content) > 100: | |
| chunks.append({ | |
| "content": content.strip(), | |
| "metadata": { | |
| "type": "multi_airport_routes", | |
| "airline": airline | |
| } | |
| }) | |
| return chunks | |
| def handle_fuel_surcharge_tiers(data: Any, context: Dict[str, Any]) -> List[Dict[str, Any]]: | |
| """fuel_surcharge_tiers ์ฒ๋ฆฌ - ์ ๋ฅํ ์ฆ๋ฃ ๋จ๊ณ๋ณ ์ ๋ณด""" | |
| chunks = [] | |
| if not data: | |
| return chunks | |
| items = data if isinstance(data, list) else [data] | |
| for item in items: | |
| airline = item.get("airline", context.get("chain", "UNKNOWN")) | |
| effective_date = item.get("effective_date", "") | |
| currency = item.get("currency", "KRW") | |
| content = f"# {airline} ์ ๋ฅํ ์ฆ๋ฃ ์๋ด\n\n" | |
| if effective_date: | |
| content += f"**์ ์ฉ ๊ธฐ์ค์ผ**: {effective_date}\n\n" | |
| tiers = item.get("tiers", []) | |
| if tiers: | |
| content += "| ๊ตฌ๊ฐ | ์ ๋ฅํ ์ฆ๋ฃ | ํฌํจ ๋ ธ์ |\n|------|----------|----------|\n" | |
| for tier in tiers: | |
| tier_name = tier.get("tier_name", "") | |
| amount = tier.get("amount", 0) | |
| routes = tier.get("routes", []) | |
| desc = tier.get("description", "") | |
| routes_str = ", ".join(routes[:5]) | |
| if len(routes) > 5: | |
| routes_str += f" ์ธ {len(routes)-5}๊ฐ" | |
| if desc: | |
| tier_name = f"{tier_name} ({desc})" | |
| content += f"| {tier_name} | {amount:,}์ | {routes_str} |\n" | |
| content += "\n" | |
| notes = item.get("notes", []) | |
| if notes: | |
| content += "**์ฐธ๊ณ ์ฌํญ**:\n" | |
| for note in notes: | |
| content += f"- {note}\n" | |
| content += DISCLAIMER_SHORT | |
| if len(content) > 100: | |
| chunks.append({ | |
| "content": content.strip(), | |
| "metadata": { | |
| "type": "fuel_surcharge_tiers", | |
| "airline": airline, | |
| "effective_date": effective_date | |
| } | |
| }) | |
| return chunks | |
| def handle_facts_router(data: Any, context: Dict[str, Any]) -> List[Dict[str, Any]]: | |
| """facts ๋ผ์ฐํฐ - ๋๋ฉ์ธ๋ณ๋ก ์ ์ ํ ํธ๋ค๋ฌ๋ก ๋ถ๊ธฐ | |
| ํธํ ๋๋ฉ์ธ: NESTED_HANDLERS๋ก ์์ (facts.pricing_analysis, facts.facilities ๋ฑ) | |
| ํญ๊ณต ๋๋ฉ์ธ: handle_airline_facts๋ก ์ฒ๋ฆฌ (price_analysis, booking_tips, warnings) | |
| ์ด ๋ผ์ฐํฐ๊ฐ ํ์ํ ์ด์ : | |
| - ํธํ ๊ณผ ํญ๊ณต ๋ฌธ์ ๋ชจ๋ 'facts' ํค๋ฅผ ์ฌ์ฉํ์ง๋ง ๋ด๋ถ ๊ตฌ์กฐ๊ฐ ๋ค๋ฆ | |
| - ํธํ facts: facilities, room_types, pricing_analysis ๋ฑ | |
| - ํญ๊ณต facts: price_analysis, booking_tips, warnings ๋ฑ | |
| """ | |
| if not data or not isinstance(data, dict): | |
| return [] | |
| # ๋๋ฉ์ธ ๊ฐ์ง | |
| chain = context.get("chain", "UNKNOWN") | |
| doc_type = context.get("document_type", "") or "" | |
| doc_category = context.get("document_category", "") or "" | |
| # ํญ๊ณต์ฌ ๋๋ฉ์ธ ํ๋ณ | |
| airline_chains = { | |
| "KOREAN_AIR", "ASIANA", "DELTA", "UNITED", "AMERICAN", | |
| "SINGAPORE", "ANA", "JAL", "CATHAY", "EMIRATES", | |
| "AIRLINE", "ALLIANCE" | |
| } | |
| is_airline = ( | |
| chain in airline_chains or | |
| "AIRLINE" in doc_type.upper() or | |
| "AIRLINE" in doc_category.upper() or | |
| "FARE" in doc_type.upper() or | |
| "MILEAGE" in doc_type.upper() | |
| ) | |
| if is_airline: | |
| # ํญ๊ณต facts - ์ ์ฉ ํธ๋ค๋ฌ๋ก ์ฒ๋ฆฌ | |
| return handle_airline_facts(data, context) | |
| # ํธํ /๊ธฐํ ๋๋ฉ์ธ - NESTED_HANDLERS๋ก ์์ | |
| # ๋น ์ฒญํฌ ๋ฐํ โ sync_to_supabase.py์ ์ค์ฒฉ ํธ๋ค๋ฌ ๋ฃจํ์์ ๊ฐ๋ณ ์ฒ๋ฆฌ๋จ | |
| # (facts.pricing_analysis, facts.facilities ๋ฑ์ด ๊ฐ๊ฐ ์ฒ๋ฆฌ๋จ) | |
| return [] | |
| def handle_airline_facts(data: Any, context: Dict[str, Any]) -> List[Dict[str, Any]]: | |
| """facts (ํญ๊ณต ์ด์ ๊ด๋ จ) ์ฒ๋ฆฌ - ๊ฐ๊ฒฉ ๋ถ์, ์์ฝ ํ, ์ฃผ์์ฌํญ | |
| ์ฃผ์: facts๊ฐ dict๊ฐ ์๋ ๊ฒฝ์ฐ (์: string) ๋น ์ฒญํฌ ๋ฐํ | |
| ์ด ํจ์๋ handle_facts_router์์ ํญ๊ณต ๋๋ฉ์ธ์ผ ๋๋ง ํธ์ถ๋จ | |
| """ | |
| chunks = [] | |
| if not data: | |
| return chunks | |
| # ํ์ ์์ ์ฒดํฌ: facts๊ฐ dict๊ฐ ์๋๋ฉด ์ฒ๋ฆฌํ์ง ์์ | |
| if not isinstance(data, dict): | |
| # string์ด๋ list์ธ ๊ฒฝ์ฐ ๋น ์ฒญํฌ ๋ฐํ (์ค๋ฅ ๋ฐฉ์ง) | |
| return chunks | |
| airline = context.get("chain", "UNKNOWN") | |
| # ๊ฐ๊ฒฉ ๋ถ์ | |
| price_analysis = data.get("price_analysis", []) | |
| if price_analysis and isinstance(price_analysis, list): | |
| content = f"# {airline} ๋ ธ์ ๋ณ ๊ฐ๊ฒฉ ๋ถ์\n\n" | |
| for analysis in price_analysis: | |
| # analysis๊ฐ dict๊ฐ ์๋๋ฉด ์คํต | |
| if not isinstance(analysis, dict): | |
| continue | |
| category = analysis.get("category", "") | |
| description = analysis.get("description", "") | |
| if category == "cheapest_international_routes": | |
| content += "## ๊ฐ์ฅ ์ ๋ ดํ ๊ตญ์ ์ ๋ ธ์ \n" | |
| elif category == "most_expensive_routes": | |
| content += "## ๊ฐ์ฅ ๋น์ผ ๊ตญ์ ์ ๋ ธ์ \n" | |
| elif category == "seasonal_price_difference": | |
| content += "## ๋น์๊ธฐ-์ฑ์๊ธฐ ๊ฐ๊ฒฉ ์ฐจ์ด๊ฐ ํฐ ๋ ธ์ \n" | |
| else: | |
| content += f"## {description}\n" | |
| routes = analysis.get("routes", []) | |
| for route in routes: | |
| # route๊ฐ dict๊ฐ ์๋๋ฉด ์คํต | |
| if not isinstance(route, dict): | |
| continue | |
| route_name = route.get("route_name", "") | |
| route_code = route.get("route", "") | |
| min_price = route.get("min_price") | |
| max_price = route.get("max_price") | |
| booking_class = route.get("booking_class", "") | |
| note = route.get("note", "") | |
| content += f"- **{route_name}** ({route_code}): " | |
| if min_price is not None: | |
| content += safe_format_number(min_price, "์") | |
| if max_price is not None: | |
| if min_price is not None: | |
| content += "~" | |
| content += safe_format_number(max_price, "์") | |
| if booking_class: | |
| content += f" ({booking_class} ํด๋์ค)" | |
| if note: | |
| content += f" - {note}" | |
| content += "\n" | |
| content += "\n" | |
| content += DISCLAIMER_SHORT | |
| if len(content) > 100: | |
| chunks.append({ | |
| "content": content.strip(), | |
| "metadata": { | |
| "type": "airline_price_analysis", | |
| "airline": airline | |
| } | |
| }) | |
| # ์์ฝ ํ | |
| booking_tips = data.get("booking_tips", []) | |
| if booking_tips and isinstance(booking_tips, list): | |
| content = f"# {airline} ํญ๊ณต๊ถ ์์ฝ ํ\n\n" | |
| for tip in booking_tips: | |
| # tip์ด dict๊ฐ ์๋๋ฉด ์คํต | |
| if not isinstance(tip, dict): | |
| continue | |
| title = tip.get("title", "") | |
| description = tip.get("description", "") | |
| content += f"## {title}\n" | |
| content += f"{description}\n\n" | |
| content += DISCLAIMER_SHORT | |
| if len(content) > 100: | |
| chunks.append({ | |
| "content": content.strip(), | |
| "metadata": { | |
| "type": "airline_booking_tips", | |
| "airline": airline | |
| } | |
| }) | |
| # ์ฃผ์์ฌํญ | |
| warnings = data.get("warnings", []) | |
| if warnings and isinstance(warnings, list): | |
| content = f"# {airline} ํญ๊ณต๊ถ ๊ตฌ๋งค ์ ์ฃผ์์ฌํญ\n\n" | |
| for warning in warnings: | |
| # warning์ด dict๊ฐ ์๋๋ฉด ์คํต | |
| if not isinstance(warning, dict): | |
| continue | |
| title = warning.get("title", "") | |
| description = warning.get("description", "") | |
| severity = warning.get("severity", "") | |
| severity_icon = "๐ด" if severity == "HIGH" else "๐ก" if severity == "MEDIUM" else "๐ข" | |
| content += f"## {severity_icon} {title}\n" | |
| content += f"{description}\n\n" | |
| content += DISCLAIMER_SHORT | |
| if len(content) > 100: | |
| chunks.append({ | |
| "content": content.strip(), | |
| "metadata": { | |
| "type": "airline_warnings", | |
| "airline": airline | |
| } | |
| }) | |
| return chunks | |
| def handle_unaccompanied_minor_rules(data: Any, context: Dict[str, Any]) -> List[Dict[str, Any]]: | |
| """unaccompanied_minor_rules ์ฒ๋ฆฌ - ๋น๋๋ฐ ์์ ๊ท์ """ | |
| chunks = [] | |
| if not data: | |
| return chunks | |
| items = data if isinstance(data, list) else [data] | |
| airline = context.get("chain", "UNKNOWN") | |
| content = f"# {airline} ๋น๋๋ฐ ์์ (UM) ์๋น์ค ๊ท์ \n\n" | |
| for item in items: | |
| rule_name = item.get("rule_name", "") | |
| route_type = item.get("route_type", "") | |
| age_range = item.get("age_range", "") | |
| route_label = "๊ตญ๋ด์ " if route_type == "DOMESTIC" else "๊ตญ์ ์ " if route_type == "INTERNATIONAL" else route_type | |
| content += f"## {rule_name}\n" if rule_name else f"## {route_label}\n" | |
| content += f"- **๋์ ๋์ด**: {age_range}\n" | |
| fare_rule = item.get("fare_rule", "") | |
| if fare_rule: | |
| content += f"- **์ด์ ๊ท์ **: {fare_rule}\n" | |
| service_fees = item.get("service_fees", []) | |
| if service_fees: | |
| content += f"- **์๋น์ค ์์๋ฃ**:\n" | |
| for fee in service_fees: | |
| currency = fee.get("currency", "") | |
| amount = fee.get("amount", 0) | |
| unit = fee.get("unit", "") | |
| content += f" - {currency} {amount:,} ({unit})\n" | |
| content += "\n" | |
| content += DISCLAIMER_SHORT | |
| if len(content) > 100: | |
| chunks.append({ | |
| "content": content.strip(), | |
| "metadata": { | |
| "type": "unaccompanied_minor_rules", | |
| "airline": airline | |
| } | |
| }) | |
| return chunks | |
| def handle_refund_rules(data: Any, context: Dict[str, Any]) -> List[Dict[str, Any]]: | |
| """refund_rules ์ฒ๋ฆฌ - ํ๋ถ ๊ท์ """ | |
| chunks = [] | |
| if not data: | |
| return chunks | |
| items = data if isinstance(data, list) else [data] | |
| airline = context.get("chain", "UNKNOWN") | |
| for item in items: | |
| rule_name = item.get("rule_name", "ํ๋ถ ๊ท์ ") | |
| route_type = item.get("route_type", "") | |
| cabin_class = item.get("cabin_class", "") | |
| route_category = item.get("route_category", "") | |
| content = f"# {airline} {rule_name}\n\n" | |
| if route_type: | |
| route_label = "๊ตญ๋ด์ " if route_type == "DOMESTIC" else "๊ตญ์ ์ " if route_type == "INTERNATIONAL" else route_type | |
| content += f"**๋ ธ์ ์ ํ**: {route_label}\n" | |
| if cabin_class: | |
| content += f"**์ข์ ๋ฑ๊ธ**: {cabin_class}\n" | |
| if route_category: | |
| content += f"**๋ ธ์ ๊ตฌ๋ถ**: {route_category}\n" | |
| # Required documents | |
| docs = item.get("required_documents", {}) | |
| if docs: | |
| content += f"\n## ํ์ ์๋ฅ\n" | |
| for doc_type, doc_list in docs.items(): | |
| if isinstance(doc_list, list): | |
| content += f"**{doc_type}**:\n" | |
| for doc in doc_list: | |
| content += f"- {doc}\n" | |
| # Fees | |
| fees = item.get("fees", []) | |
| if fees: | |
| content += f"\n## ์์๋ฃ\n" | |
| for fee in fees: | |
| if isinstance(fee, dict): | |
| fare_class = fee.get("fare_class", "") | |
| fare_type = fee.get("fare_type", "") | |
| fee_krw = fee.get("fee_krw", 0) | |
| amount = fee.get("amount", 0) | |
| currency = fee.get("currency", "") | |
| if fare_class: | |
| content += f"- {fare_class} ({fare_type}): {fee_krw:,}์\n" | |
| elif currency: | |
| content += f"- {currency}: {amount:,}\n" | |
| # Penalties | |
| penalties = item.get("penalties", []) | |
| if penalties: | |
| content += f"\n## ํ๋ถ ์์ฝ๊ธ\n" | |
| for penalty in penalties: | |
| timing = penalty.get("timing", "") | |
| # Get all fee values | |
| fee_values = [] | |
| for key, value in penalty.items(): | |
| if key != "timing" and isinstance(value, (int, float)) and value >= 0: | |
| fee_values.append(f"{key}: {value:,}์") | |
| if fee_values: | |
| content += f"- **{timing}**: {', '.join(fee_values)}\n" | |
| # Waivers | |
| waivers = item.get("waivers", {}) | |
| if waivers: | |
| content += f"\n## ๋ฉด์ ์กฐ๊ฑด\n" | |
| for waiver_type, waiver_list in waivers.items(): | |
| if isinstance(waiver_list, list): | |
| content += f"**{waiver_type}**:\n" | |
| for waiver in waiver_list: | |
| content += f"- {waiver}\n" | |
| content += DISCLAIMER_SHORT | |
| if len(content) > 100: | |
| chunks.append({ | |
| "content": content.strip(), | |
| "metadata": { | |
| "type": "refund_rules", | |
| "airline": airline, | |
| "route_type": route_type, | |
| "cabin_class": cabin_class | |
| } | |
| }) | |
| return chunks | |
| def handle_no_show_penalties(data: Any, context: Dict[str, Any]) -> List[Dict[str, Any]]: | |
| """no_show_penalties ์ฒ๋ฆฌ - ์์ฝ๋ถ๋ ์์ฝ๊ธ""" | |
| chunks = [] | |
| if not data: | |
| return chunks | |
| items = data if isinstance(data, list) else [data] | |
| airline = context.get("chain", "UNKNOWN") | |
| content = f"# {airline} ์์ฝ๋ถ๋ (No-Show) ์์ฝ๊ธ\n\n" | |
| for item in items: | |
| rule_name = item.get("rule_name", "") | |
| route_type = item.get("route_type", "") | |
| route_label = "๊ตญ๋ด์ " if route_type == "DOMESTIC" else "๊ตญ์ ์ " if route_type == "INTERNATIONAL" else route_type | |
| content += f"## {rule_name or route_label}\n" | |
| penalty_krw = item.get("penalty_krw", 0) | |
| if penalty_krw: | |
| unit = item.get("unit", "") | |
| content += f"- **์์ฝ๊ธ**: {penalty_krw:,}์ ({unit})\n" | |
| condition = item.get("condition", "") | |
| if condition: | |
| content += f"- **์ ์ฉ ์กฐ๊ฑด**: {condition}\n" | |
| penalties = item.get("penalties", []) | |
| if penalties: | |
| for penalty in penalties: | |
| cabin = penalty.get("cabin_class_name", "") or penalty.get("cabin_class", "") | |
| amount = penalty.get("penalty_krw", 0) | |
| content += f"- {cabin}: {amount:,}์\n" | |
| additional = item.get("additional_penalty", {}) | |
| if additional: | |
| cond = additional.get("condition", "") | |
| add_krw = additional.get("additional_krw", 0) | |
| content += f"- **์ถ๊ฐ ์์ฝ๊ธ**: {cond} - {add_krw:,}์ ์ถ๊ฐ\n" | |
| content += "\n" | |
| content += DISCLAIMER_SHORT | |
| if len(content) > 100: | |
| chunks.append({ | |
| "content": content.strip(), | |
| "metadata": { | |
| "type": "no_show_penalties", | |
| "airline": airline | |
| } | |
| }) | |
| return chunks | |
| def handle_lounge_penalties(data: Any, context: Dict[str, Any]) -> List[Dict[str, Any]]: | |
| """lounge_penalties ์ฒ๋ฆฌ - ๋ผ์ด์ง ์์ฝ๊ธ""" | |
| chunks = [] | |
| if not data: | |
| return chunks | |
| items = data if isinstance(data, list) else [data] | |
| airline = context.get("chain", "UNKNOWN") | |
| content = f"# {airline} ๋ผ์ด์ง ์์ฝ๊ธ\n\n" | |
| for item in items: | |
| rule_name = item.get("rule_name", "") | |
| condition = item.get("condition", "") | |
| if rule_name: | |
| content += f"## {rule_name}\n" | |
| if condition: | |
| content += f"**์ ์ฉ ์กฐ๊ฑด**: {condition}\n" | |
| fees = item.get("fees", []) | |
| if fees: | |
| content += f"\n**์์ฝ๊ธ**:\n" | |
| for fee in fees: | |
| route = fee.get("route_type", "") | |
| fee_krw = fee.get("fee_krw", 0) | |
| fee_usd = fee.get("fee_usd", 0) | |
| content += f"- {route}: {fee_krw:,}์ / USD {fee_usd}\n" | |
| notes = item.get("notes", []) | |
| if notes: | |
| content += f"\n**์ ์์ฌํญ**:\n" | |
| for note in notes: | |
| content += f"- {note}\n" | |
| content += "\n" | |
| content += DISCLAIMER_SHORT | |
| if len(content) > 100: | |
| chunks.append({ | |
| "content": content.strip(), | |
| "metadata": { | |
| "type": "lounge_penalties", | |
| "airline": airline | |
| } | |
| }) | |
| return chunks | |
| def handle_medical_oxygen_rules(data: Any, context: Dict[str, Any]) -> List[Dict[str, Any]]: | |
| """medical_oxygen_rules ์ฒ๋ฆฌ - ๊ธฐ๋ด ์๋ฃ์ฉ ์ฐ์ ๊ท์ """ | |
| chunks = [] | |
| if not data: | |
| return chunks | |
| items = data if isinstance(data, list) else [data] | |
| airline = context.get("chain", "UNKNOWN") | |
| content = f"# {airline} ๊ธฐ๋ด ์๋ฃ์ฉ ์ฐ์ ์๋น์ค\n\n" | |
| for item in items: | |
| rule_name = item.get("rule_name", "") | |
| if rule_name: | |
| content += f"## {rule_name}\n" | |
| fees = item.get("fees", []) | |
| if fees: | |
| content += f"\n**์๊ธ**:\n" | |
| for fee in fees: | |
| route = fee.get("route_type", "") | |
| fee_krw = fee.get("fee_krw", 0) | |
| fee_usd = fee.get("fee_usd", 0) | |
| unit = fee.get("unit", "") | |
| if fee_krw: | |
| content += f"- {route}: {fee_krw:,}์ ({unit})\n" | |
| elif fee_usd: | |
| content += f"- {route}: USD {fee_usd} ({unit})\n" | |
| intl_by_country = item.get("international_by_country", []) | |
| if intl_by_country: | |
| content += f"\n**๊ตญ๊ฐ๋ณ ๊ตญ์ ์ ์๊ธ**:\n" | |
| for country in intl_by_country: | |
| name = country.get("departure_country", "") | |
| fee_cad = country.get("fee_cad", 0) | |
| fee_idr = country.get("fee_idr", 0) | |
| fee_usd = country.get("fee_usd", 0) | |
| if fee_cad: | |
| content += f"- {name}: CAD {fee_cad}\n" | |
| elif fee_idr: | |
| content += f"- {name}: IDR {fee_idr:,}\n" | |
| elif fee_usd: | |
| content += f"- {name}: USD {fee_usd}\n" | |
| additional = item.get("additional_charge", "") | |
| if additional: | |
| content += f"\n**์ถ๊ฐ ๋น์ฉ**: {additional}\n" | |
| content += "\n" | |
| content += DISCLAIMER_SHORT | |
| if len(content) > 100: | |
| chunks.append({ | |
| "content": content.strip(), | |
| "metadata": { | |
| "type": "medical_oxygen_rules", | |
| "airline": airline | |
| } | |
| }) | |
| return chunks | |
| def handle_codeshare_baggage_rules(data: Any, context: Dict[str, Any]) -> List[Dict[str, Any]]: | |
| """codeshare_baggage_rules ์ฒ๋ฆฌ - ๊ณต๋์ดํญ ์ํ๋ฌผ ๊ท์ """ | |
| chunks = [] | |
| if not data: | |
| return chunks | |
| items = data if isinstance(data, list) else [data] | |
| airline = context.get("chain", "UNKNOWN") | |
| content = f"# {airline} ๊ณต๋์ดํญํธ ์ํ๋ฌผ ๊ท์ \n\n" | |
| for item in items: | |
| rule_name = item.get("rule_name", "") | |
| policy_type = item.get("policy_type", "") | |
| if rule_name: | |
| content += f"## {rule_name}\n" | |
| applicable_airlines = item.get("applicable_airlines", []) | |
| if applicable_airlines: | |
| content += f"**์ ์ฉ ํญ๊ณต์ฌ**:\n" | |
| for airline_name in applicable_airlines: | |
| content += f"- {airline_name}\n" | |
| notes = item.get("notes", []) | |
| if notes: | |
| content += f"\n**์ ์์ฌํญ**:\n" | |
| for note in notes: | |
| content += f"- {note}\n" | |
| content += "\n" | |
| content += DISCLAIMER_SHORT | |
| if len(content) > 100: | |
| chunks.append({ | |
| "content": content.strip(), | |
| "metadata": { | |
| "type": "codeshare_baggage_rules", | |
| "airline": airline | |
| } | |
| }) | |
| return chunks | |
| def handle_route_classifications(data: Any, context: Dict[str, Any]) -> List[Dict[str, Any]]: | |
| """route_classifications ์ฒ๋ฆฌ - ๋ ธ์ ๊ตฌ๋ถ""" | |
| chunks = [] | |
| if not data: | |
| return chunks | |
| items = data if isinstance(data, list) else [data] | |
| airline = context.get("chain", "UNKNOWN") | |
| content = f"# {airline} ๋ ธ์ ๊ตฌ๋ถ ๊ธฐ์ค\n\n" | |
| for item in items: | |
| rule_name = item.get("rule_name", "") | |
| classification_type = item.get("classification_type", "") | |
| if rule_name: | |
| content += f"## {rule_name}\n" | |
| routes = item.get("routes", []) | |
| if routes: | |
| for route in routes: | |
| route_type = route.get("route_type", "") | |
| route_name = route.get("route_name", "") | |
| destinations = route.get("destinations", []) | |
| content += f"### {route_name} ({route_type})\n" | |
| if destinations: | |
| content += "- " + ", ".join(destinations) + "\n" | |
| content += "\n" | |
| content += DISCLAIMER_SHORT | |
| if len(content) > 100: | |
| chunks.append({ | |
| "content": content.strip(), | |
| "metadata": { | |
| "type": "route_classifications", | |
| "airline": airline | |
| } | |
| }) | |
| return chunks | |
| # =========================================================================== | |
| # ํธ๋ค๋ฌ: ๋ง์ผ๋ฆฌ์ง ํจ์จ ๋ถ์ (Mileage Efficiency Analysis) | |
| # =========================================================================== | |
| def handle_award_chart_entries(data: Any, context: Dict[str, Any]) -> List[Dict[str, Any]]: | |
| """award_chart_entries ์ฒ๋ฆฌ - ์ด์๋ ์ฐจํธ ์์ธ ํญ๋ชฉ | |
| ๋ ๊ฐ์ง ๊ตฌ์กฐ ์ง์: | |
| 1) Flat ๊ตฌ์กฐ: [{"region": "...", "route_type": "...", "economy_low": ...}, ...] | |
| 2) Nested ๊ตฌ์กฐ: [{"region": "...", "routes": [{"route": "...", ...}]}, ...] | |
| """ | |
| chunks = [] | |
| if not data: | |
| return chunks | |
| items = data if isinstance(data, list) else [data] | |
| airline = context.get("chain", "UNKNOWN") | |
| # ๊ตฌ์กฐ ํ์ง: ์ฒซ ๋ฒ์งธ ํญ๋ชฉ์ 'routes' ํค๊ฐ ์์ผ๋ฉด ์ค์ฒฉ ๊ตฌ์กฐ | |
| is_nested = any(isinstance(item, dict) and "routes" in item for item in items) | |
| if is_nested: | |
| # === ์ค์ฒฉ ๊ตฌ์กฐ ์ฒ๋ฆฌ (Flying Blue ์คํ์ผ) === | |
| for region_data in items: | |
| if not isinstance(region_data, dict): | |
| continue | |
| region = region_data.get("region", "OTHER") | |
| region_name = region_data.get("region_name", region) | |
| routes = region_data.get("routes", []) | |
| if not routes: | |
| continue | |
| content = f"# {airline} ์ด์๋ ์ฐจํธ - {region_name}\n\n" | |
| content += "| ๋ ธ์ | ์ถ๋ฐ | ๋์ฐฉ | ์ด์ฝ๋ ธ๋ฏธ | ๋น์ฆ๋์ค | ๋น๊ณ |\n" | |
| content += "|------|------|------|----------|----------|------|\n" | |
| for route in routes[:50]: # ์ง์ญ๋น ์ต๋ 50๊ฐ ๋ ธ์ | |
| if not isinstance(route, dict): | |
| continue | |
| route_code = route.get("route", "") | |
| origin_name = route.get("origin_name", route.get("origin", "")) | |
| dest_name = route.get("destination_name", route.get("destination", "")) | |
| # ์ด์ฝ๋ ธ๋ฏธ/๋น์ฆ๋์ค ํฌ์ธํธ | |
| eco_low = route.get("economy_low") | |
| eco_high = route.get("economy_high") | |
| biz_low = route.get("business_low") | |
| biz_high = route.get("business_high") | |
| eco_str = f"{eco_low:,}" if eco_low else "N/A" | |
| if eco_high and eco_high != eco_low: | |
| eco_str += f"~{eco_high:,}" | |
| if biz_low: | |
| biz_str = f"{biz_low:,}" | |
| if biz_high and biz_high != biz_low: | |
| biz_str += f"~{biz_high:,}" | |
| else: | |
| biz_str = "N/A" | |
| notes = route.get("notes", "") or "" | |
| content += f"| {route_code} | {origin_name} | {dest_name} | {eco_str} | {biz_str} | {notes} |\n" | |
| content += DISCLAIMER_SHORT | |
| if len(content) > 150: | |
| chunks.append({ | |
| "content": content.strip(), | |
| "metadata": { | |
| "type": "award_chart_entries", | |
| "airline": airline, | |
| "region": region, | |
| "region_name": region_name | |
| } | |
| }) | |
| else: | |
| # === ๊ธฐ์กด Flat ๊ตฌ์กฐ ์ฒ๋ฆฌ === | |
| region_entries = {} | |
| for item in items: | |
| region = item.get("region", "OTHER") | |
| if region not in region_entries: | |
| region_entries[region] = [] | |
| region_entries[region].append(item) | |
| for region, entries in region_entries.items(): | |
| content = f"# {airline} ์ด์๋ ์ฐจํธ - {region}\n\n" | |
| for entry in entries: | |
| route_type = entry.get("route_type", "") | |
| trip_type = entry.get("trip_type", "") | |
| trip_label = "์๋ณต" if trip_type == "ROUND_TRIP" else "ํธ๋" | |
| content += f"## {route_type} ({trip_label})\n" | |
| # ์ข์ ๋ฑ๊ธ๋ณ ๋ง์ผ๋ฆฌ์ง | |
| economy_low = entry.get("economy_low") | |
| economy_high = entry.get("economy_high") | |
| if economy_low or economy_high: | |
| content += f"- **์ผ๋ฐ์**: {economy_low:,}~{economy_high:,} ๋ง์ผ\n" | |
| premium_economy_low = entry.get("premium_economy_low") | |
| premium_economy_high = entry.get("premium_economy_high") | |
| if premium_economy_low or premium_economy_high: | |
| content += f"- **ํ๋ฆฌ๋ฏธ์ ์ด์ฝ๋ ธ๋ฏธ**: {premium_economy_low:,}~{premium_economy_high:,} ๋ง์ผ\n" | |
| business_low = entry.get("business_low") | |
| business_high = entry.get("business_high") | |
| if business_low or business_high: | |
| content += f"- **๋น์ฆ๋์ค/ํ๋ ์คํฐ์ง**: {business_low:,}~{business_high:,} ๋ง์ผ\n" | |
| first_low = entry.get("first_low") | |
| first_high = entry.get("first_high") | |
| if first_low or first_high: | |
| content += f"- **์ผ๋ฑ์**: {first_low:,}~{first_high:,} ๋ง์ผ\n" | |
| notes = entry.get("notes", "") | |
| if notes: | |
| content += f"- ์ฐธ๊ณ : {notes}\n" | |
| content += "\n" | |
| content += DISCLAIMER_SHORT | |
| if len(content) > 100: | |
| chunks.append({ | |
| "content": content.strip(), | |
| "metadata": { | |
| "type": "award_chart_entries", | |
| "airline": airline, | |
| "region": region | |
| } | |
| }) | |
| return chunks | |
| def handle_route_efficiency_data(data: Any, context: Dict[str, Any]) -> List[Dict[str, Any]]: | |
| """route_efficiency_data ์ฒ๋ฆฌ - nested dict ๋ฐ ๋ฆฌ์คํธ ๊ตฌ์กฐ ๋ชจ๋ ์ง์""" | |
| chunks = [] | |
| if not data: | |
| return chunks | |
| # ๋ฐ์ดํฐ ์ ๊ทํ: ๋์ ๋๋ฆฌ(domestic_economy, japan_business ๋ฑ)๋ผ๋ฉด ๋ชจ๋ ๊ฐ์ ํํํ | |
| items = [] | |
| if isinstance(data, dict): | |
| for key, val in data.items(): | |
| if isinstance(val, list): | |
| items.extend(val) | |
| elif isinstance(val, dict): | |
| items.append(val) | |
| elif isinstance(data, list): | |
| items = data | |
| else: | |
| items = [data] | |
| if not items: | |
| return chunks | |
| airline = context.get("chain", "UNKNOWN") | |
| # ์ง์ญ๋ณ๋ก ๊ทธ๋ฃนํ | |
| region_entries = {} | |
| for item in items: | |
| region = item.get("region", "OTHER") | |
| if region not in region_entries: | |
| region_entries[region] = [] | |
| region_entries[region].append(item) | |
| for region, entries in region_entries.items(): | |
| # ์ฒญํฌ๊ฐ ๋๋ฌด ์ปค์ง์ง ์๋๋ก ์ข์ ๋ฑ๊ธ๋ณ๋ก ๋ถํ | |
| class_entries = {} | |
| for entry in entries: | |
| cabin_class = entry.get("cabin_class", "ECONOMY") | |
| if cabin_class not in class_entries: | |
| class_entries[cabin_class] = [] | |
| class_entries[cabin_class].append(entry) | |
| for cabin_class, class_items in class_entries.items(): | |
| cabin_labels = { | |
| "ECONOMY": "์ผ๋ฐ์", | |
| "PREMIUM_ECONOMY": "ํ๋ฆฌ๋ฏธ์ ์ด์ฝ๋ ธ๋ฏธ", | |
| "BUSINESS": "ํ๋ ์คํฐ์ง/๋น์ฆ๋์ค", | |
| "FIRST": "์ผ๋ฑ์" | |
| } | |
| cabin_label = cabin_labels.get(cabin_class, cabin_class) | |
| content = f"# {airline} ๋ง์ผ๋ฆฌ์ง ํจ์จ ๋ถ์ - {region} {cabin_label}\n\n" | |
| content += "| ๋ ธ์ | ์ด์ ์ ํ | ํ๊ธ๊ฐ | ํ์ ๋ง์ผ | ํจ์จ (์/๋ง์ผ) |\n" | |
| content += "|------|----------|--------|----------|---------------|\n" | |
| for item in class_items[:30]: # ์ต๋ 30๊ฐ | |
| route = item.get("route", "") | |
| fare_type = item.get("fare_type", "") | |
| cash_fare = item.get("cash_fare_krw", 0) | |
| required_miles = item.get("required_miles", 0) | |
| efficiency = item.get("efficiency_krw_per_mile", 0) | |
| season = item.get("season", "") | |
| season_label = " (์ฑ์๊ธฐ)" if season == "HIGH" else " (๋น์๊ธฐ)" if season == "LOW" else "" | |
| content += f"| {route} | {fare_type}{season_label} | {cash_fare:,}์ | {required_miles:,} | {efficiency:.2f} |\n" | |
| content += f"\n**ํจ์จ ํด์**: ์ซ์๊ฐ ๋์์๋ก ๋ง์ผ๋ฆฌ์ง ๊ฐ์น๊ฐ ๋์ต๋๋ค.\n" | |
| content += DISCLAIMER_SHORT | |
| if len(content) > 100: | |
| chunks.append({ | |
| "content": content.strip(), | |
| "metadata": { | |
| "type": "route_efficiency_data", | |
| "airline": airline, | |
| "region": region, | |
| "cabin_class": cabin_class | |
| } | |
| }) | |
| return chunks | |
| def handle_efficiency_ranking(data: Any, context: Dict[str, Any]) -> List[Dict[str, Any]]: | |
| """efficiency_ranking ์ฒ๋ฆฌ - nested dict (top_20/worst_20) ๋ฐ ๋ฆฌ์คํธ ๊ตฌ์กฐ ๋ชจ๋ ์ง์""" | |
| chunks = [] | |
| if not data: | |
| return chunks | |
| # ๋ฐ์ดํฐ ์ ๊ทํ: ๋์ ๋๋ฆฌ(top_20, worst_20 ๋ฑ)๋ผ๋ฉด ๋ชจ๋ ๊ฐ์ ํํํ | |
| items = [] | |
| if isinstance(data, dict): | |
| for key, val in data.items(): | |
| if isinstance(val, list): | |
| items.extend(val) | |
| elif isinstance(data, list): | |
| items = data | |
| else: | |
| items = [data] | |
| if not items: | |
| return chunks | |
| airline = context.get("chain", "UNKNOWN") | |
| content = f"# {airline} ๋ง์ผ๋ฆฌ์ง ํจ์จ TOP ๋ญํน\n\n" | |
| content += "| ์์ | ๋ ธ์ | ์ข์ ๋ฑ๊ธ | ํจ์จ (์/๋ง์ผ) |\n" | |
| content += "|------|------|----------|---------------|\n" | |
| for item in items[:20]: | |
| rank = item.get("rank", "") | |
| route = item.get("route", "") | |
| cabin_class = item.get("cabin_class", "") | |
| efficiency = item.get("efficiency_krw_per_mile", 0) | |
| cabin_labels = { | |
| "ECONOMY": "์ผ๋ฐ์", | |
| "PREMIUM_ECONOMY": "ํ๋ฆฌ๋ฏธ์", | |
| "BUSINESS": "ํ๋ ์คํฐ์ง", | |
| "FIRST": "์ผ๋ฑ์" | |
| } | |
| cabin_label = cabin_labels.get(cabin_class, cabin_class) | |
| content += f"| {rank} | {route} | {cabin_label} | {efficiency:.2f} |\n" | |
| content += "\n**ํจ์จ ํด์**: ์ซ์๊ฐ ๋์์๋ก ๋ง์ผ๋ฆฌ์ง ์ฌ์ฉ ์ ๋ ํฐ ๊ฐ์น๋ฅผ ์ป์ต๋๋ค.\n" | |
| content += DISCLAIMER_SHORT | |
| if len(content) > 100: | |
| chunks.append({ | |
| "content": content.strip(), | |
| "metadata": { | |
| "type": "efficiency_ranking", | |
| "airline": airline | |
| } | |
| }) | |
| return chunks | |
| def handle_mileage_efficiency_analysis(data: Any, context: Dict[str, Any]) -> List[Dict[str, Any]]: | |
| """mileage_efficiency_analysis ์ฒ๋ฆฌ - ๋ง์ผ๋ฆฌ์ง ํจ์จ ๋ถ์ ๋ฉํ๋ฐ์ดํฐ""" | |
| chunks = [] | |
| if not data: | |
| return chunks | |
| airline = context.get("chain", "UNKNOWN") | |
| program = data.get("program", "") | |
| content = f"# {airline} {program} ๋ง์ผ๋ฆฌ์ง ํจ์จ ๋ถ์ ๊ฐ์\n\n" | |
| analysis_date = data.get("analysis_date", "") | |
| if analysis_date: | |
| content += f"**๋ถ์ ๊ธฐ์ค์ผ**: {analysis_date}\n\n" | |
| method = data.get("calculation_method", "") | |
| if method: | |
| content += f"**๊ณ์ฐ ๋ฐฉ๋ฒ**: {method}\n\n" | |
| fare_basis_types = data.get("fare_basis_types", []) | |
| if fare_basis_types: | |
| content += "## ์ด์ ๊ธฐ์ค ์ ํ\n" | |
| for fb in fare_basis_types: | |
| basis_type = fb.get("basis_type", "") | |
| description = fb.get("description", "") | |
| applies_to = fb.get("applies_to", "") | |
| content += f"- **{basis_type}**: {description}\n" | |
| if applies_to: | |
| content += f" - ์ ์ฉ ๋์: {applies_to}\n" | |
| content += "\n" | |
| evidence = data.get("evidence", []) | |
| evidence_str = format_evidence(evidence) | |
| if evidence_str: | |
| content += evidence_str | |
| content += DISCLAIMER_SHORT | |
| if len(content) > 100: | |
| chunks.append({ | |
| "content": content.strip(), | |
| "metadata": { | |
| "type": "mileage_efficiency_analysis", | |
| "airline": airline, | |
| "program": program | |
| } | |
| }) | |
| return chunks | |
| def handle_fare_basis_comparison(data: Any, context: Dict[str, Any]) -> List[Dict[str, Any]]: | |
| """fare_basis_comparison ์ฒ๋ฆฌ - ์ด์ ๊ธฐ์ค๋ณ ๋น๊ต""" | |
| chunks = [] | |
| if not data: | |
| return chunks | |
| items = data if isinstance(data, list) else [data] | |
| airline = context.get("chain", "UNKNOWN") | |
| content = f"# {airline} ์ด์ ๊ธฐ์ค๋ณ ๋ง์ผ๋ฆฌ์ง ํจ์จ ๋น๊ต\n\n" | |
| content += "| ์ง์ญ | ์ข์ ๋ฑ๊ธ | ์ต์ ์ด์ ํจ์จ | ์ต๊ณ ์ด์ ํจ์จ | ํจ์จ ํฅ์ |\n" | |
| content += "|------|----------|--------------|--------------|----------|\n" | |
| for item in items: | |
| region = item.get("region", "") | |
| cabin_class = item.get("cabin_class", "") | |
| lowest_efficiency = item.get("lowest_fare_efficiency", 0) | |
| highest_efficiency = item.get("highest_fare_efficiency", 0) | |
| improvement = item.get("efficiency_improvement", "") | |
| cabin_labels = { | |
| "ECONOMY": "์ผ๋ฐ์", | |
| "PREMIUM_ECONOMY": "ํ๋ฆฌ๋ฏธ์", | |
| "BUSINESS": "ํ๋ ์คํฐ์ง", | |
| "FIRST": "์ผ๋ฑ์" | |
| } | |
| cabin_label = cabin_labels.get(cabin_class, cabin_class) | |
| content += f"| {region} | {cabin_label} | {lowest_efficiency:.2f} | {highest_efficiency:.2f} | {improvement} |\n" | |
| content += "\n**๋ถ์**: ์ต๊ณ ์ด์(ํํ๋ ์ค ์๊ธ)์์ ๋ง์ผ๋ฆฌ์ง ํจ์จ์ด ๋ ๋์ต๋๋ค.\n" | |
| content += DISCLAIMER_SHORT | |
| if len(content) > 100: | |
| chunks.append({ | |
| "content": content.strip(), | |
| "metadata": { | |
| "type": "fare_basis_comparison", | |
| "airline": airline | |
| } | |
| }) | |
| return chunks | |
| def handle_key_insights(data: Any, context: Dict[str, Any]) -> List[Dict[str, Any]]: | |
| """key_insights ์ฒ๋ฆฌ - ํต์ฌ ์ธ์ฌ์ดํธ""" | |
| chunks = [] | |
| if not data: | |
| return chunks | |
| items = data if isinstance(data, list) else [data] | |
| airline = context.get("chain", "UNKNOWN") | |
| content = f"# {airline} ๋ง์ผ๋ฆฌ์ง ํจ์จ ํต์ฌ ์ธ์ฌ์ดํธ\n\n" | |
| for item in items[:10]: | |
| if not isinstance(item, dict): | |
| continue | |
| title = item.get("title", "") | |
| insight_content = item.get("content", "") | |
| if title and insight_content: | |
| content += f"## {title}\n" | |
| content += f"{insight_content}\n\n" | |
| content += DISCLAIMER_SHORT | |
| if len(content) > 100: | |
| chunks.append({ | |
| "content": content.strip(), | |
| "metadata": { | |
| "type": "key_insights", | |
| "airline": airline | |
| } | |
| }) | |
| return chunks | |
| def handle_usage_recommendations(data: Any, context: Dict[str, Any]) -> List[Dict[str, Any]]: | |
| """usage_recommendations ์ฒ๋ฆฌ - ๋ง์ผ๋ฆฌ์ง ์ฌ์ฉ ๊ถ์ฅ์ฌํญ | |
| ์ธ ๊ฐ์ง ๊ตฌ์กฐ ์ง์: | |
| 1) ์ต์์ List ๊ตฌ์กฐ: [{"category": "...", "routes": [...]}] | |
| 2) Dict ๊ตฌ์กฐ (๊ฐ์ด dict): {"highly_recommended": {"description": "...", "routes": [...]}} | |
| 3) Dict ๊ตฌ์กฐ (๊ฐ์ด list): {"highly_recommended": [{route_data...}], "recommended": [...]} | |
| """ | |
| chunks = [] | |
| if not data: | |
| return chunks | |
| airline = context.get("chain", "UNKNOWN") | |
| content = f"# {airline} ๋ง์ผ๋ฆฌ์ง ์ฌ์ฉ ๊ถ์ฅ์ฌํญ\n\n" | |
| # ์ต์์๊ฐ ๋ฆฌ์คํธ์ธ ๊ฒฝ์ฐ | |
| if isinstance(data, list): | |
| for idx, item in enumerate(data[:20]): | |
| if isinstance(item, dict): | |
| category = item.get("category", item.get("recommendation_type", f"ํญ๋ชฉ {idx+1}")) | |
| desc = item.get("description", "") | |
| routes = item.get("routes", []) | |
| content += f"## {category}\n" | |
| if desc: | |
| content += f"{desc}\n\n" | |
| for route in routes[:10]: | |
| if isinstance(route, dict): | |
| r_name = route.get("route", route.get("route_code", "")) | |
| cabin = route.get("cabin_class", "") | |
| season = route.get("recommended_season", "") | |
| eff_range = route.get("efficiency_range", route.get("efficiency", "")) | |
| content += f"- **{r_name}** ({cabin}" | |
| if season: | |
| content += f", {season}" | |
| content += f"): {eff_range}\n" | |
| else: | |
| content += f"- {route}\n" | |
| content += "\n" | |
| else: | |
| # ๋จ์ ๋ฌธ์์ด | |
| content += f"- {item}\n" | |
| # ๋์ ๋๋ฆฌ ๊ตฌ์กฐ ์ฒ๋ฆฌ | |
| elif isinstance(data, dict): | |
| categories = [ | |
| ("highly_recommended", "๊ฐ๋ ฅ ์ถ์ฒ", 5), | |
| ("recommended", "์ถ์ฒ", 3), | |
| ("not_recommended", "๋น์ถ์ฒ", 1), | |
| ("booking_strategy", "์์ฝ ์ ๋ต", 4), | |
| ] | |
| for key, default_title, _ in categories: | |
| category_data = data.get(key) | |
| if not category_data: | |
| continue | |
| # ๊ฐ์ด ๋์ ๋๋ฆฌ์ธ ๊ฒฝ์ฐ (๊ธฐ์กด ๊ตฌ์กฐ) | |
| if isinstance(category_data, dict): | |
| desc = category_data.get("description", default_title) | |
| content += f"## {desc}\n" | |
| routes = category_data.get("routes", []) | |
| for route in routes[:10]: | |
| if isinstance(route, dict): | |
| r_name = route.get("route", "") | |
| cabin = route.get("cabin_class", "") | |
| season = route.get("recommended_season", "") | |
| eff_range = route.get("efficiency_range", "") | |
| if key == "not_recommended": | |
| content += f"- **{r_name}** ({cabin}): {eff_range}์/๋ง์ผ\n" | |
| else: | |
| content += f"- **{r_name}** ({cabin}, {season}): {eff_range}์/๋ง์ผ\n" | |
| content += "\n" | |
| # ๊ฐ์ด ๋ฆฌ์คํธ์ธ ๊ฒฝ์ฐ (Aeroplan ๊ตฌ์กฐ) | |
| elif isinstance(category_data, list): | |
| content += f"## {default_title}\n" | |
| for item in category_data[:10]: | |
| if isinstance(item, dict): | |
| # route_category ๋๋ route | |
| route_cat = item.get("route_category", item.get("route", "")) | |
| cabin = item.get("cabin_class", "") | |
| points = item.get("points_required", "") | |
| recommendation = item.get("recommendation", item.get("reason", "")) | |
| title = item.get("title", "") | |
| desc = item.get("description", "") | |
| if title: # booking_strategy ํ์ | |
| content += f"- **{title}**: {desc}\n" | |
| elif route_cat: | |
| content += f"- **{route_cat}** ({cabin}" | |
| if points: | |
| # points๊ฐ ์ซ์์ธ์ง ํ์ธ ํ ์ฒ ๋จ์ ๊ตฌ๋ถ์ ์ ์ฉ | |
| if isinstance(points, (int, float)): | |
| content += f", {points:,}pts" | |
| else: | |
| content += f", {points}pts" | |
| content += f"): {recommendation}\n" | |
| else: | |
| content += f"- {item}\n" | |
| content += "\n" | |
| content += DISCLAIMER_SHORT | |
| if len(content) > 150: | |
| chunks.append({ | |
| "content": content.strip(), | |
| "metadata": { | |
| "type": "usage_recommendations", | |
| "airline": airline | |
| } | |
| }) | |
| return chunks | |
| def handle_regional_efficiency_summary(data: Any, context: Dict[str, Any]) -> List[Dict[str, Any]]: | |
| """regional_efficiency_summary ์ฒ๋ฆฌ - ์ง์ญ๋ณ ํจ์จ ์์ฝ""" | |
| chunks = [] | |
| if not data: | |
| return chunks | |
| items = data if isinstance(data, list) else [data] | |
| airline = context.get("chain", "UNKNOWN") | |
| content = f"# {airline} ์ง์ญ๋ณ ๋ง์ผ๋ฆฌ์ง ํจ์จ ์์ฝ\n\n" | |
| content += "| ์ง์ญ | ์ผ๋ฐ์ ํ๊ท | ๋น์ฆ๋์ค ํ๊ท | ๋น์ฆ๋์ค ์ค๋งํฐ์ ํ๊ท |\n" | |
| content += "|------|------------|--------------|---------------------|\n" | |
| for item in items: | |
| if not isinstance(item, dict): | |
| continue | |
| region_name = item.get("region_name", item.get("region", "")) | |
| economy_avg = item.get("economy_avg", 0) or "-" | |
| business_avg = item.get("business_avg", 0) or "-" | |
| smartium_avg = item.get("business_smartium_avg") or "-" | |
| if isinstance(economy_avg, (int, float)): | |
| economy_avg = f"{economy_avg:.2f}" | |
| if isinstance(business_avg, (int, float)): | |
| business_avg = f"{business_avg:.2f}" | |
| if isinstance(smartium_avg, (int, float)): | |
| smartium_avg = f"{smartium_avg:.2f}" | |
| content += f"| {region_name} | {economy_avg} | {business_avg} | {smartium_avg} |\n" | |
| content += "\n**ํจ์จ ํด์**: ์ซ์๊ฐ ๋์์๋ก ๋ง์ผ๋ฆฌ์ง ๊ฐ์น๊ฐ ๋์ต๋๋ค.\n" | |
| content += DISCLAIMER_SHORT | |
| if len(content) > 100: | |
| chunks.append({ | |
| "content": content.strip(), | |
| "metadata": { | |
| "type": "regional_efficiency_summary", | |
| "airline": airline | |
| } | |
| }) | |
| return chunks | |
| def handle_seasonal_efficiency_summary(data: Any, context: Dict[str, Any]) -> List[Dict[str, Any]]: | |
| """seasonal_efficiency_summary ์ฒ๋ฆฌ - ์์ฆ๋ณ ํจ์จ ์์ฝ""" | |
| chunks = [] | |
| if not data: | |
| return chunks | |
| items = data if isinstance(data, list) else [data] | |
| airline = context.get("chain", "UNKNOWN") | |
| content = f"# {airline} ์์ฆ๋ณ ๋ง์ผ๋ฆฌ์ง ํจ์จ ๋น๊ต\n\n" | |
| content += "| ์์ฆ | ์ผ๋ฐ์ ํ๊ท ํจ์จ | ๋น์ฆ๋์ค ํ๊ท ํจ์จ |\n" | |
| content += "|------|----------------|------------------|\n" | |
| season_labels = { | |
| "LOW": "๋น์๊ธฐ", | |
| "SHOULDER": "์ค์ฑ์๊ธฐ", | |
| "HIGH": "์ฑ์๊ธฐ" | |
| } | |
| for item in items: | |
| if not isinstance(item, dict): | |
| continue | |
| season = item.get("season", "") | |
| season_name = item.get("season_name", season_labels.get(season, season)) | |
| economy_avg = item.get("economy_avg", 0) or 0 | |
| business_avg = item.get("business_avg", 0) or 0 | |
| content += f"| {season_name} | {economy_avg:.2f} | {business_avg:.2f} |\n" | |
| content += "\n**๋ถ์**: ์ฑ์๊ธฐ์๋ ์ถ๊ฐ ๋ง์ผ๋ฆฌ์ง ๊ณต์ ๋ก ํจ์จ์ด ํ๋ฝํฉ๋๋ค.\n" | |
| content += DISCLAIMER_SHORT | |
| if len(content) > 100: | |
| chunks.append({ | |
| "content": content.strip(), | |
| "metadata": { | |
| "type": "seasonal_efficiency_summary", | |
| "airline": airline | |
| } | |
| }) | |
| return chunks | |
| def handle_mileage_usage_strategy(data: Any, context: Dict[str, Any]) -> List[Dict[str, Any]]: | |
| """mileage_usage_strategy ์ฒ๋ฆฌ - ๋ง์ผ๋ฆฌ์ง ์ฌ์ฉ ์ ๋ต""" | |
| chunks = [] | |
| if not data: | |
| return chunks | |
| airline = context.get("chain", "UNKNOWN") | |
| content = f"# {airline} ๋ง์ผ๋ฆฌ์ง ์ฌ์ฉ ์ ๋ต ๊ฐ์ด๋\n\n" | |
| # ๋ฆฌ์คํธ ํํ์ผ ๊ฒฝ์ฐ ์ง์ recommendations๋ก ์ฒ๋ฆฌ | |
| if isinstance(data, list): | |
| recommendations = data | |
| else: | |
| recommendations = data.get("recommendations", []) | |
| if recommendations: | |
| content += "## ์ถ์ฒ ์ ๋ต\n" | |
| for rec in recommendations: | |
| if not isinstance(rec, dict): | |
| content += f"- {rec}\n" | |
| continue | |
| # strategy_id/title/content ํ์ ์ง์ (Asiana ๋ฑ) | |
| if rec.get("strategy_id") or rec.get("title"): | |
| title = rec.get("title", rec.get("strategy_id", "")) | |
| rec_content = rec.get("content", "") | |
| content += f"### {title}\n" | |
| if rec_content: | |
| content += f"{rec_content}\n\n" | |
| # priority/strategy ํ์ ์ง์ (๊ธฐ์กด) | |
| else: | |
| priority = rec.get("priority", "") | |
| strategy = rec.get("strategy", "") | |
| routes = rec.get("routes", []) | |
| efficiency_range = rec.get("efficiency_range", "") | |
| if priority or strategy: | |
| content += f"### {priority}: {strategy}\n" | |
| if routes: | |
| content += f"- **์ถ์ฒ ๋ ธ์ **: {', '.join(routes[:5])}\n" | |
| if efficiency_range: | |
| content += f"- **ํจ์จ ๋ฒ์**: {efficiency_range}\n" | |
| content += "\n" | |
| # ๋์ ๋๋ฆฌ์ผ ๊ฒฝ์ฐ์๋ง ์ถ๊ฐ ํ๋ ์ฒ๋ฆฌ | |
| if isinstance(data, dict): | |
| # Efficiency Tiers | |
| efficiency_tiers = data.get("efficiency_tiers", []) | |
| if efficiency_tiers: | |
| content += "## ํจ์จ ๋ฑ๊ธ\n" | |
| for tier in efficiency_tiers: | |
| if isinstance(tier, dict): | |
| tier_name = tier.get("tier", "") | |
| efficiency_range = tier.get("efficiency_range", "") | |
| description = tier.get("description", "") | |
| content += f"- **{tier_name}** ({efficiency_range}): {description}\n" | |
| content += "\n" | |
| # Seasonal Insight | |
| seasonal_insight = data.get("seasonal_insight", {}) | |
| if seasonal_insight and isinstance(seasonal_insight, dict): | |
| content += "## ์์ฆ๋ณ ์ธ์ฌ์ดํธ\n" | |
| low_season = seasonal_insight.get("low_season_tip", "") | |
| high_season = seasonal_insight.get("high_season_tip", "") | |
| if low_season: | |
| content += f"- **๋น์๊ธฐ**: {low_season}\n" | |
| if high_season: | |
| content += f"- **์ฑ์๊ธฐ**: {high_season}\n" | |
| content += DISCLAIMER_SHORT | |
| if len(content) > 100: | |
| chunks.append({ | |
| "content": content.strip(), | |
| "metadata": { | |
| "type": "mileage_usage_strategy", | |
| "airline": airline | |
| } | |
| }) | |
| return chunks | |
| # =========================================================================== | |
| # ํธ๋ค๋ฌ: ๋ง์ผ๋ฆฌ์ง ๊ท์ (Mileage Rules) | |
| # =========================================================================== | |
| def handle_mileage_rules_generic(data: Any, context: Dict[str, Any], rule_type: str, title: str) -> List[Dict[str, Any]]: | |
| """๋ฒ์ฉ ๋ง์ผ๋ฆฌ์ง ๊ท์ ํธ๋ค๋ฌ""" | |
| chunks = [] | |
| if not data: | |
| return chunks | |
| airline = context.get("chain", "UNKNOWN") | |
| content = f"# {airline} {title}\n\n" | |
| if isinstance(data, list): | |
| for item in data[:20]: | |
| if isinstance(item, dict): | |
| for k, v in item.items(): | |
| content += f"- **{k}**: {v}\n" | |
| else: | |
| content += f"- {item}\n" | |
| elif isinstance(data, dict): | |
| for k, v in data.items(): | |
| if isinstance(v, list): | |
| content += f"## {k}\n" | |
| for item in v[:10]: | |
| if isinstance(item, dict): | |
| desc = item.get("description", item.get("name", str(item))) | |
| content += f"- {desc}\n" | |
| else: | |
| content += f"- {item}\n" | |
| content += "\n" | |
| elif isinstance(v, dict): | |
| content += f"## {k}\n" | |
| for sub_k, sub_v in v.items(): | |
| content += f"- **{sub_k}**: {sub_v}\n" | |
| content += "\n" | |
| else: | |
| content += f"- **{k}**: {v}\n" | |
| else: | |
| content += str(data) | |
| content += DISCLAIMER_SHORT | |
| if len(content) > 100: | |
| chunks.append({ | |
| "content": content.strip(), | |
| "metadata": { | |
| "type": rule_type, | |
| "airline": airline | |
| } | |
| }) | |
| return chunks | |
| def handle_peak_season_rules(data: Any, context: Dict[str, Any]) -> List[Dict[str, Any]]: | |
| """peak_seasons, peak_season_rules, peak_season_exemptions ์ฒ๋ฆฌ""" | |
| return handle_mileage_rules_generic(data, context, "peak_season_rules", "์ฑ์๊ธฐ ๊ท์ ") | |
| def handle_refund_change_fees(data: Any, context: Dict[str, Any]) -> List[Dict[str, Any]]: | |
| """refund_fees, change_fees ๋ฑ ํ๋ถ/๋ณ๊ฒฝ ์์๋ฃ ์ฒ๋ฆฌ""" | |
| return handle_mileage_rules_generic(data, context, "refund_change_fees", "ํ๋ถ/๋ณ๊ฒฝ ์์๋ฃ") | |
| def handle_mileage_booking_rules(data: Any, context: Dict[str, Any]) -> List[Dict[str, Any]]: | |
| """booking_process, booking_notes ๋ฑ ์์ฝ ๊ท์ ์ฒ๋ฆฌ""" | |
| return handle_mileage_rules_generic(data, context, "mileage_booking_rules", "๋ง์ผ๋ฆฌ์ง ํญ๊ณต๊ถ ์์ฝ ๊ท์ ") | |
| def handle_mileage_deduction_rules(data: Any, context: Dict[str, Any]) -> List[Dict[str, Any]]: | |
| """business_smartium_rules, child_mileage_deduction_rules ๋ฑ ๋ง์ผ๋ฆฌ์ง ์ฐจ๊ฐ ๊ท์ ์ฒ๋ฆฌ""" | |
| return handle_mileage_rules_generic(data, context, "mileage_deduction_rules", "๋ง์ผ๋ฆฌ์ง ์ฐจ๊ฐ ๊ท์ ") | |
| def handle_route_transfer_rules(data: Any, context: Dict[str, Any]) -> List[Dict[str, Any]]: | |
| """route_restrictions, transfer_rules ๋ฑ ๋ ธ์ /ํ์น ๊ท์ ์ฒ๋ฆฌ""" | |
| return handle_mileage_rules_generic(data, context, "route_transfer_rules", "๋ ธ์ /ํ์น ๊ท์ ") | |
| def handle_no_show_rules(data: Any, context: Dict[str, Any]) -> List[Dict[str, Any]]: | |
| """no_show_definitions, no_show_post_processing ์ฒ๋ฆฌ""" | |
| return handle_mileage_rules_generic(data, context, "no_show_rules", "๋ ธ์ผ ๊ท์ ") | |
| def handle_itinerary_change_rules(data: Any, context: Dict[str, Any]) -> List[Dict[str, Any]]: | |
| """itinerary_change_rules, itinerary_change_examples ์ฒ๋ฆฌ""" | |
| return handle_mileage_rules_generic(data, context, "itinerary_change_rules", "์ฌ์ ๋ณ๊ฒฝ ๊ท์ ") | |
| def handle_mileage_program_rules(data: Any, context: Dict[str, Any]) -> List[Dict[str, Any]]: | |
| """mileage_now_program, taxes_and_fees ๋ฑ ๊ธฐํ ํ๋ก๊ทธ๋จ ๊ท์ ์ฒ๋ฆฌ""" | |
| return handle_mileage_rules_generic(data, context, "mileage_program_rules", "๋ง์ผ๋ฆฌ์ง ํ๋ก๊ทธ๋จ ๊ท์ ") | |
| def handle_excluded_routes(data: Any, context: Dict[str, Any]) -> List[Dict[str, Any]]: | |
| """excluded_routes ์ฒ๋ฆฌ - ์ ์ธ ๋ ธ์ """ | |
| chunks = [] | |
| if not data: | |
| return chunks | |
| airline = context.get("chain", "UNKNOWN") | |
| content = f"# {airline} ๋ง์ผ๋ฆฌ์ง ํญ๊ณต๊ถ ์ ์ธ/์ฃผ์ ๋ ธ์ \n\n" | |
| if isinstance(data, list): | |
| for route in data[:30]: | |
| if isinstance(route, dict): | |
| origin = route.get("origin", "") | |
| dest = route.get("destination", "") | |
| reason = route.get("reason", route.get("note", "")) | |
| content += f"- **{origin} โ {dest}**: {reason}\n" | |
| else: | |
| content += f"- {route}\n" | |
| elif isinstance(data, dict): | |
| for region, routes in data.items(): | |
| content += f"\n## {region}\n" | |
| if isinstance(routes, list): | |
| for route in routes[:10]: | |
| content += f"- {route}\n" | |
| content += DISCLAIMER_SHORT | |
| if len(content) > 100: | |
| chunks.append({ | |
| "content": content.strip(), | |
| "metadata": { | |
| "type": "excluded_routes", | |
| "airline": airline | |
| } | |
| }) | |
| return chunks | |
| def handle_regional_efficiency_data(data: Any, context: Dict[str, Any]) -> List[Dict[str, Any]]: | |
| """route_efficiency_data_* ์ง์ญ๋ณ ํจ์จ ๋ฐ์ดํฐ ์ฒ๋ฆฌ (domestic, japan, ๋ฑ)""" | |
| chunks = [] | |
| if not data: | |
| return chunks | |
| airline = context.get("chain", "UNKNOWN") | |
| # ์ง์ญ๋ช ์ถ์ถ ์๋ | |
| region_name = context.get("current_key", "").replace("route_efficiency_data_", "").replace("_", " ").title() | |
| if not region_name: | |
| region_name = "Unknown Region" | |
| content = f"# {airline} {region_name} ์ง์ญ ๋ง์ผ๋ฆฌ์ง ํจ์จ ๋ฐ์ดํฐ\n\n" | |
| if isinstance(data, list): | |
| content += "| ๋ ธ์ | ์ข์ | ์์ฆ | ํจ์จ |\n" | |
| content += "|------|------|------|------|\n" | |
| for item in data[:30]: | |
| if isinstance(item, dict): | |
| route = item.get("route", item.get("destination", "-")) | |
| cabin = item.get("cabin_class", item.get("class", "-")) | |
| season = item.get("season", "-") | |
| efficiency = item.get("efficiency", item.get("value", "-")) | |
| content += f"| {route} | {cabin} | {season} | {efficiency} |\n" | |
| elif isinstance(data, dict): | |
| for cabin_class, routes in data.items(): | |
| content += f"\n## {cabin_class}\n" | |
| if isinstance(routes, list): | |
| for route in routes[:10]: | |
| if isinstance(route, dict): | |
| route_name = route.get("route", route.get("destination", "-")) | |
| eff = route.get("efficiency", route.get("value", "-")) | |
| content += f"- **{route_name}**: {eff}\n" | |
| content += DISCLAIMER_SHORT | |
| if len(content) > 100: | |
| chunks.append({ | |
| "content": content.strip(), | |
| "metadata": { | |
| "type": "regional_efficiency_data", | |
| "airline": airline, | |
| "region": region_name | |
| } | |
| }) | |
| return chunks | |
| # =========================================================================== | |
| # ํธ๋ค๋ฌ: ๋ง์ผ๋ฆฌ์ง ๊ตฌ๋งค ํ๋ก๊ทธ๋จ (Mileage Purchase Programs) | |
| # =========================================================================== | |
| def handle_mileage_purchase_programs(data: Any, context: Dict[str, Any]) -> List[Dict[str, Any]]: | |
| """mileage_purchase_programs ์ฒ๋ฆฌ - ํญ๊ณต์ฌ๋ณ ๋ง์ผ๋ฆฌ์ง ๊ตฌ๋งค ๊ฐ๊ฒฉ ๋ถ์""" | |
| chunks = [] | |
| if not data: | |
| return chunks | |
| programs = data if isinstance(data, list) else [data] | |
| for program in programs: | |
| if not isinstance(program, dict): | |
| continue | |
| program_name = program.get("program_name", "Unknown Program") | |
| airline = program.get("airline", context.get("chain", "UNKNOWN")) | |
| alliance = program.get("alliance", "") | |
| currency_name = program.get("currency_name", "๋ง์ผ") | |
| content = f"# {program_name} ({airline}) ๋ง์ผ๋ฆฌ์ง ๊ตฌ๋งค ๊ฐ๊ฒฉ ๋ถ์\n\n" | |
| if alliance: | |
| content += f"**์ ํด**: {alliance}\n\n" | |
| # ๊ธฐ๋ณธ ๊ฐ๊ฒฉ ์ ๋ณด | |
| base_price = program.get("base_price_cents") | |
| if base_price: | |
| content += f"**๊ธฐ๋ณธ ๊ตฌ๋งค๊ฐ**: {base_price}์ผํธ/{currency_name}\n" | |
| # ๊ตฌ๋งค ํ๋ | |
| limits = program.get("purchase_limits", {}) | |
| if limits: | |
| content += "\n## ๊ตฌ๋งค ํ๋\n" | |
| if limits.get("minimum"): | |
| content += f"- ์ต์ ๊ตฌ๋งค: {limits.get('minimum'):,}\n" | |
| if limits.get("maximum_annual"): | |
| content += f"- ์ฐ๊ฐ ์ต๋: {limits.get('maximum_annual'):,}\n" | |
| if limits.get("maximum_monthly"): | |
| content += f"- ์๊ฐ ์ต๋: {limits.get('maximum_monthly'):,}\n" | |
| # ๋ณด๋์ค ํฐ์ด | |
| bonus_tiers = program.get("bonus_tiers", []) | |
| if bonus_tiers: | |
| content += "\n## ๋ณด๋์ค ํฐ์ด\n" | |
| content += "| ๊ตฌ๋งค๋ | ๋ณด๋์ค์จ |\n|--------|----------|\n" | |
| for tier in bonus_tiers[:10]: | |
| if isinstance(tier, dict): | |
| min_amt = tier.get("purchase_amount_min", 0) | |
| max_amt = tier.get("purchase_amount_max", "โ") | |
| bonus_pct = tier.get("bonus_percent", 0) | |
| if max_amt is None: | |
| max_amt = "โ" | |
| content += f"| {min_amt:,}~{max_amt} | {bonus_pct}% |\n" | |
| # ๊ถ์ฅ ๊ตฌ๋งค ๊ฐ๊ฒฉ | |
| recommended = program.get("recommended_price", {}) | |
| if not recommended: | |
| # ๊ตฌ/์ ์ฒด๊ณ๊ฐ ์๋ ๊ฒฝ์ฐ | |
| old_system = program.get("old_system", {}) | |
| new_system = program.get("new_system", {}) | |
| if old_system.get("recommended_price"): | |
| rec = old_system["recommended_price"] | |
| content += "\n## ๊ถ์ฅ ๊ตฌ๋งค ๊ฐ๊ฒฉ (๊ตฌ ์ฒด๊ณ)\n" | |
| content += f"- **๊ถ์ฅ๊ฐ**: {rec.get('standard_cents', 'N/A')}์ผํธ\n" | |
| content += f"- **์ต์ ๊ฐ**: {rec.get('lowest_cents', 'N/A')}์ผํธ\n" | |
| if rec.get("rationale"): | |
| content += f"- **๊ทผ๊ฑฐ**: {rec.get('rationale')}\n" | |
| if new_system.get("recommended_price"): | |
| rec = new_system["recommended_price"] | |
| content += "\n## ๊ถ์ฅ ๊ตฌ๋งค ๊ฐ๊ฒฉ (์ ์ฒด๊ณ)\n" | |
| content += f"- **๊ถ์ฅ๊ฐ**: {rec.get('standard_cents', 'N/A')}์ผํธ\n" | |
| content += f"- **์ต์ ๊ฐ**: {rec.get('lowest_cents', 'N/A')}์ผํธ\n" | |
| if rec.get("rationale"): | |
| content += f"- **๊ทผ๊ฑฐ**: {rec.get('rationale')}\n" | |
| else: | |
| content += "\n## ๊ถ์ฅ ๊ตฌ๋งค ๊ฐ๊ฒฉ\n" | |
| content += f"- **๊ถ์ฅ๊ฐ**: {recommended.get('standard_cents', 'N/A')}์ผํธ\n" | |
| content += f"- **์ต์ ๊ฐ**: {recommended.get('lowest_cents', 'N/A')}์ผํธ\n" | |
| if recommended.get("lowest_condition"): | |
| content += f"- **์ต์ ์กฐ๊ฑด**: {recommended.get('lowest_condition')}\n" | |
| if recommended.get("rationale"): | |
| content += f"- **๊ทผ๊ฑฐ**: {recommended.get('rationale')}\n" | |
| # ํ๋ก๋ชจ์ ํ์คํ ๋ฆฌ ์์ฝ | |
| promo_history = program.get("promotion_history", []) | |
| if not promo_history: | |
| old_system = program.get("old_system", {}) | |
| new_system = program.get("new_system", {}) | |
| promo_history = old_system.get("promotion_history", []) + new_system.get("promotion_history", []) | |
| if promo_history: | |
| content += "\n## ์ต๊ทผ ํ๋ก๋ชจ์ (์ต๊ทผ 5๊ฑด)\n" | |
| content += "| ์์์ผ | ์ข ๋ฃ์ผ | ๋ณด๋์ค์จ | ์ค์ง ๋จ๊ฐ |\n|--------|--------|----------|----------|\n" | |
| for promo in promo_history[:5]: | |
| if isinstance(promo, dict): | |
| start = promo.get("start_date", "-") | |
| end = promo.get("end_date", "-") | |
| bonus = promo.get("bonus_percent", "-") | |
| eff_price = promo.get("effective_price_cents", "-") | |
| content += f"| {start} | {end} | {bonus}% | {eff_price}์ผํธ |\n" | |
| # ๋ง๋ฃ ์ ์ฑ | |
| expiration = program.get("expiration_policy", {}) | |
| if expiration: | |
| content += "\n## ๋ง๋ฃ ์ ์ฑ \n" | |
| if expiration.get("expires"): | |
| content += f"- **๋ง๋ฃ ๊ธฐ๊ฐ**: {expiration.get('expiration_period', 'N/A')}\n" | |
| if expiration.get("extension_method"): | |
| content += f"- **์ฐ์ฅ ๋ฐฉ๋ฒ**: {expiration.get('extension_method')}\n" | |
| else: | |
| content += "- ๋ง์ผ๋ฆฌ์ง ๋ง๋ฃ ์์\n" | |
| # Clube Smiles ๋ฑ ํน๋ณ ํ๋ก๊ทธ๋จ | |
| clube = program.get("clube_smiles", {}) | |
| if clube: | |
| content += "\n## Clube Smiles ๊ตฌ๋ ํ๋\n" | |
| plans = clube.get("plans", []) | |
| for plan in plans[:5]: | |
| if isinstance(plan, dict): | |
| plan_name = plan.get("plan_name", "") | |
| bonus_pct = plan.get("purchase_bonus_percent", 0) | |
| max_bonus = plan.get("max_total_bonus_percent", 0) | |
| content += f"- **{plan_name}**: ๊ตฌ๋งค ๋ณด๋์ค +{bonus_pct}%, ์ต๋ {max_bonus}%\n" | |
| content += DISCLAIMER_SHORT | |
| if len(content) > 150: | |
| chunks.append({ | |
| "content": content.strip(), | |
| "metadata": { | |
| "type": "mileage_purchase_program", | |
| "airline": airline, | |
| "program_name": program_name, | |
| "alliance": alliance | |
| } | |
| }) | |
| return chunks | |
| def handle_mileage_price_summary(data: Any, context: Dict[str, Any]) -> List[Dict[str, Any]]: | |
| """price_summary ์ฒ๋ฆฌ - ํ๋ก๊ทธ๋จ๋ณ ๊ถ์ฅ ๊ตฌ๋งค ๊ฐ๊ฒฉ ์์ฝ""" | |
| chunks = [] | |
| if not data: | |
| return chunks | |
| content = "# ํญ๊ณต์ฌ ๋ง์ผ๋ฆฌ์ง ๊ตฌ๋งค ๊ฐ๊ฒฉ ์์ฝ\n\n" | |
| content += "| ํ๋ก๊ทธ๋จ | ๊ถ์ฅ๊ฐ | ์ต์ ๊ฐ | ๋ณด๋์ค์จ | ๋น๊ณ |\n" | |
| content += "|----------|--------|--------|----------|------|\n" | |
| programs = data if isinstance(data, list) else [data] | |
| for program in programs: | |
| if not isinstance(program, dict): | |
| continue | |
| name = program.get("program_name", "-") | |
| rec_price = program.get("recommended_price_cents", "-") | |
| low_price = program.get("lowest_price_cents", "-") | |
| bonus = program.get("typical_bonus_percent", "-") | |
| notes = program.get("notes", "-") | |
| content += f"| {name} | {rec_price}ยข | {low_price}ยข | {bonus}% | {notes} |\n" | |
| content += "\n> **์ฐธ๊ณ **: ๊ฐ๊ฒฉ์ ์ผํธ/๋ง์ผ ๊ธฐ์ค์ด๋ฉฐ, ์ธ๊ธ ๋ณ๋์ ๋๋ค.\n" | |
| content += DISCLAIMER_SHORT | |
| if len(content) > 150: | |
| chunks.append({ | |
| "content": content.strip(), | |
| "metadata": { | |
| "type": "mileage_price_summary" | |
| } | |
| }) | |
| return chunks | |
| def handle_mileage_insights(data: Any, context: Dict[str, Any]) -> List[Dict[str, Any]]: | |
| """insights ์ฒ๋ฆฌ - ๋ง์ผ๋ฆฌ์ง ๊ตฌ๋งค ์ฃผ์ ์ธ์ฌ์ดํธ""" | |
| chunks = [] | |
| if not data or not isinstance(data, dict): | |
| return chunks | |
| content = "# ๋ง์ผ๋ฆฌ์ง ๊ตฌ๋งค ์ฃผ์ ์ธ์ฌ์ดํธ\n\n" | |
| # ํ๋ก๊ทธ๋จ๋ณ ์ ๋ต | |
| strategies = data.get("program_strategies", []) | |
| if strategies: | |
| content += "## ํ๋ก๊ทธ๋จ๋ณ ๊ตฌ๋งค ์ ๋ต\n" | |
| for strategy in strategies[:10]: | |
| if isinstance(strategy, dict): | |
| program = strategy.get("program", "") | |
| strat = strategy.get("strategy", "") | |
| best_use = strategy.get("best_use", "") | |
| risk = strategy.get("risk", "") | |
| content += f"\n### {program}\n" | |
| if strat: | |
| content += f"- **์ ๋ต**: {strat}\n" | |
| if best_use: | |
| content += f"- **์ต์ ์ฌ์ฉ์ฒ**: {best_use}\n" | |
| if risk: | |
| content += f"- **๋ฆฌ์คํฌ**: {risk}\n" | |
| # ์๊ธฐ๋ณ ์ ๋ต | |
| timing = data.get("timing_strategies", []) | |
| if timing: | |
| content += "\n## ๊ตฌ๋งค ์๊ธฐ ์ ๋ต\n" | |
| for t in timing[:5]: | |
| if isinstance(t, dict): | |
| period = t.get("period", "") | |
| tip = t.get("tip", t.get("strategy", "")) | |
| content += f"- **{period}**: {tip}\n" | |
| # ์ผ๋ฐ ํ | |
| general_tips = data.get("general_tips", []) | |
| if general_tips: | |
| content += "\n## ์ผ๋ฐ ํ\n" | |
| for tip in general_tips[:10]: | |
| if isinstance(tip, dict): | |
| content += f"- {tip.get('tip', str(tip))}\n" | |
| else: | |
| content += f"- {tip}\n" | |
| # ์ฃผ์์ฌํญ | |
| warnings = data.get("warnings", []) | |
| if warnings: | |
| content += "\n## โ ๏ธ ์ฃผ์์ฌํญ\n" | |
| for w in warnings[:5]: | |
| if isinstance(w, dict): | |
| content += f"- {w.get('warning', str(w))}\n" | |
| else: | |
| content += f"- {w}\n" | |
| content += DISCLAIMER_SHORT | |
| if len(content) > 150: | |
| chunks.append({ | |
| "content": content.strip(), | |
| "metadata": { | |
| "type": "mileage_purchase_insights" | |
| } | |
| }) | |
| return chunks | |
| # =========================================================================== | |
| # ํธ๋ค๋ฌ: ๋/ํ๋ก๋ชจ์ (Phase 5) | |
| # =========================================================================== | |
| def handle_deal_alerts(data: Any, context: Dict[str, Any]) -> List[Dict[str, Any]]: | |
| """deal_alerts ๋๋ deal_alert ์ฒ๋ฆฌ""" | |
| chunks = [] | |
| deals = data if isinstance(data, list) else [data] | |
| for deal in deals[:10]: | |
| if not isinstance(deal, dict): | |
| continue | |
| title = deal.get("title", "N/A") | |
| category = deal.get("category", "OTHER") | |
| domain = deal.get("domain", "GENERAL") | |
| content = f""" | |
| ๋/ํ๋ก๋ชจ์ : {title} | |
| ์นดํ ๊ณ ๋ฆฌ: {category} | |
| ๋๋ฉ์ธ: {domain} | |
| """ | |
| # ๊ด๋ จ ํ๋ก๊ทธ๋จ | |
| if deal.get("hotel_chain"): | |
| content += f"ํธํ : {deal.get('hotel_chain')}\n" | |
| if deal.get("airline"): | |
| content += f"ํญ๊ณต์ฌ: {deal.get('airline')}\n" | |
| if deal.get("card_issuer"): | |
| content += f"์นด๋์ฌ: {deal.get('card_issuer')}\n" | |
| # ๊ธฐ๊ฐ | |
| start_date = deal.get("start_date", "N/A") | |
| end_date = deal.get("end_date", "N/A") | |
| content += f"\n๊ธฐ๊ฐ: {start_date} ~ {end_date}\n" | |
| if deal.get("registration_deadline"): | |
| content += f"๋ฑ๋ก ๋ง๊ฐ: {deal.get('registration_deadline')}\n" | |
| # ์์ฝ ๋ฐ ์ค๋ช | |
| summary = deal.get("summary", "") | |
| if summary: | |
| content += f"\n์์ฝ: {summary}\n" | |
| description = deal.get("description", "") | |
| if description: | |
| content += f"\n์์ธ:\n{description}\n" | |
| # ํํ | |
| benefits = [] | |
| if deal.get("bonus_percentage"): | |
| benefits.append(f"๋ณด๋์ค {deal.get('bonus_percentage')}%") | |
| if deal.get("bonus_points"): | |
| benefits.append(f"+{deal.get('bonus_points'):,} ํฌ์ธํธ") | |
| if deal.get("bonus_miles"): | |
| benefits.append(f"+{deal.get('bonus_miles'):,} ๋ง์ผ") | |
| if deal.get("discount_percentage"): | |
| benefits.append(f"ํ ์ธ {deal.get('discount_percentage')}%") | |
| if benefits: | |
| content += f"\nํํ: {', '.join(benefits)}\n" | |
| # ์กฐ๊ฑด | |
| conditions = deal.get("conditions", []) | |
| if conditions: | |
| content += "\n์กฐ๊ฑด:\n" | |
| for cond in conditions[:5]: | |
| content += f" - {cond}\n" | |
| # ํ๋ก๋ชจ์ ์ฝ๋ | |
| if deal.get("promo_code"): | |
| content += f"\nํ๋ก๋ชจ์ ์ฝ๋: {deal.get('promo_code')}\n" | |
| # ํ๊ฒ/์ค๋ณต ๊ฐ๋ฅ ์ฌ๋ถ | |
| flags = [] | |
| if deal.get("is_targeted"): | |
| flags.append("ํ๊ฒ ์คํผ") | |
| if deal.get("is_stackable"): | |
| flags.append("์ค๋ณต ๊ฐ๋ฅ") | |
| if flags: | |
| content += f"์ฐธ๊ณ : {', '.join(flags)}\n" | |
| content += DISCLAIMER_SHORT | |
| chunks.append({ | |
| "content": content.strip(), | |
| "metadata": { | |
| "type": "deal_alert", | |
| "title": title, | |
| "category": category, | |
| "end_date": str(end_date) if end_date else None | |
| } | |
| }) | |
| return chunks | |
| def handle_news_updates(data: Any, context: Dict[str, Any]) -> List[Dict[str, Any]]: | |
| """news_updates ๋๋ news_update ์ฒ๋ฆฌ""" | |
| chunks = [] | |
| news_list = data if isinstance(data, list) else [data] | |
| for news in news_list[:10]: | |
| if not isinstance(news, dict): | |
| continue | |
| title = news.get("title", "N/A") | |
| category = news.get("category", "OTHER") | |
| domain = news.get("domain", "GENERAL") | |
| content = f""" | |
| ๋ด์ค/์ ๋ฐ์ดํธ: {title} | |
| ์นดํ ๊ณ ๋ฆฌ: {category} | |
| ๋๋ฉ์ธ: {domain} | |
| """ | |
| # ๊ด๋ จ ํ๋ก๊ทธ๋จ | |
| if news.get("hotel_chain"): | |
| content += f"ํธํ : {news.get('hotel_chain')}\n" | |
| if news.get("airline"): | |
| content += f"ํญ๊ณต์ฌ: {news.get('airline')}\n" | |
| if news.get("card_issuer"): | |
| content += f"์นด๋์ฌ: {news.get('card_issuer')}\n" | |
| # ๋ ์ง | |
| if news.get("published_date"): | |
| content += f"๋ฐํ์ผ: {news.get('published_date')}\n" | |
| if news.get("effective_date"): | |
| content += f"์ํ์ผ: {news.get('effective_date')}\n" | |
| # ์์ฝ ๋ฐ ๋ด์ฉ | |
| summary = news.get("summary", "") | |
| if summary: | |
| content += f"\n์์ฝ: {summary}\n" | |
| full_content = news.get("content", "") | |
| if full_content: | |
| # ๊ธด ๋ด์ฉ์ ์ผ๋ถ๋ง | |
| if len(full_content) > 500: | |
| content += f"\n๋ด์ฉ:\n{full_content[:500]}...\n" | |
| else: | |
| content += f"\n๋ด์ฉ:\n{full_content}\n" | |
| # ์ํฅ๋ | |
| impact = news.get("impact") | |
| impact_summary = news.get("impact_summary") | |
| if impact or impact_summary: | |
| content += "\n์ํฅ:\n" | |
| if impact: | |
| impact_labels = { | |
| "positive": "๊ธ์ ์ โ ", | |
| "negative": "๋ถ์ ์ โ", | |
| "neutral": "์ค๋ฆฝ โช" | |
| } | |
| content += f" - ์ํฅ๋: {impact_labels.get(impact, impact)}\n" | |
| if impact_summary: | |
| content += f" - {impact_summary}\n" | |
| # ํ๊ทธ | |
| tags = news.get("tags", []) | |
| if tags: | |
| content += f"\nํ๊ทธ: {', '.join(tags[:5])}\n" | |
| content += DISCLAIMER_SHORT | |
| chunks.append({ | |
| "content": content.strip(), | |
| "metadata": { | |
| "type": "news_update", | |
| "title": title, | |
| "category": category, | |
| "impact": impact | |
| } | |
| }) | |
| return chunks | |
| # =========================================================================== | |
| # ํธ๋ค๋ฌ: ์๋ณ ๊ฐ๊ฒฉ ์์ธ (ํ๊ธ/ํฌ์ธํธ) | |
| # =========================================================================== | |
| def handle_monthly_prices_detailed(data: Any, context: Dict[str, Any]) -> List[Dict[str, Any]]: | |
| """monthly_cash_prices_detailed, monthly_points_prices_detailed ์ฒ๋ฆฌ | |
| ์๋ณ ๋ ์ง๋ณ ์์ธ ๊ฐ๊ฒฉ ๋ฐ์ดํฐ๋ฅผ ์์ฆ๋ณ๋ก ์์ฝํ์ฌ ์ฒญํฌ ์์ฑ | |
| """ | |
| chunks = [] | |
| hotel_name, hotel_name_ko, chain = get_hotel_info(context) | |
| if not isinstance(data, dict): | |
| return chunks | |
| # ๊ฐ๊ฒฉ ์ ํ ํ๋จ (ํฌ์ธํธ vs ํ๊ธ) | |
| is_points = any("points" in str(list(v[0].keys()) if isinstance(v, list) and v else []) | |
| for v in data.values() if v) | |
| price_type = "ํฌ์ธํธ" if is_points else "ํ๊ธ" | |
| content_type = "monthly_points_prices" if is_points else "monthly_cash_prices" | |
| content = format_hotel_header(hotel_name, hotel_name_ko, chain) | |
| content += f"์๋ณ {price_type} ๊ฐ๊ฒฉ ์์ธ:\n\n" | |
| # ์๋ณ๋ก ํต๊ณ ๊ณ์ฐ | |
| for month_key, prices_list in list(data.items())[:12]: | |
| if not isinstance(prices_list, list) or not prices_list: | |
| continue | |
| # ์ ์ด๋ฆ ํฌ๋งท | |
| month_name = month_key.replace("_", " ").title() | |
| # ๊ฐ๊ฒฉ/ํฌ์ธํธ ์ถ์ถ | |
| if is_points: | |
| values = [p.get("points", 0) for p in prices_list if isinstance(p, dict) and p.get("points")] | |
| else: | |
| values = [p.get("price_krw", 0) for p in prices_list if isinstance(p, dict) and p.get("price_krw")] | |
| if not values: | |
| continue | |
| min_val = min(values) | |
| max_val = max(values) | |
| if is_points: | |
| content += f"โข {month_name}: {min_val:,}P ~ {max_val:,}P\n" | |
| else: | |
| content += f"โข {month_name}: โฉ{min_val:,} ~ โฉ{max_val:,}\n" | |
| content += DISCLAIMER_PRICE | |
| if len(content) > 150: | |
| chunks.append({ | |
| "content": content.strip(), | |
| "metadata": { | |
| "type": content_type, | |
| "hotel_name": hotel_name | |
| } | |
| }) | |
| return chunks | |
| # =========================================================================== | |
| # ํธ๋ค๋ฌ: ๊ฐ์ค ์ ํ๋ณ ์๊ธ | |
| # =========================================================================== | |
| def handle_room_rates_by_type(data: Any, context: Dict[str, Any]) -> List[Dict[str, Any]]: | |
| """room_rates_by_type ์ฒ๋ฆฌ - ๊ฐ์ค ์ ํ๋ณ ๋ฉค๋ฒ ์๊ธ""" | |
| chunks = [] | |
| hotel_name, hotel_name_ko, chain = get_hotel_info(context) | |
| if not isinstance(data, list) or not data: | |
| return chunks | |
| content = format_hotel_header(hotel_name, hotel_name_ko, chain) | |
| content += "๊ฐ์ค ์ ํ๋ณ ๋ฉค๋ฒ ์๊ธ:\n\n" | |
| for room in data[:15]: | |
| if not isinstance(room, dict): | |
| continue | |
| room_type = room.get("room_type", "N/A") | |
| rate = room.get("member_rate_krw", room.get("rate", "N/A")) | |
| notes = room.get("notes", "") | |
| if isinstance(rate, (int, float)): | |
| content += f"โข {room_type}: โฉ{rate:,}" | |
| else: | |
| content += f"โข {room_type}: {rate}" | |
| if notes: | |
| content += f" ({notes})" | |
| content += "\n" | |
| content += DISCLAIMER_PRICE | |
| chunks.append({ | |
| "content": content.strip(), | |
| "metadata": { | |
| "type": "room_rates_by_type", | |
| "hotel_name": hotel_name | |
| } | |
| }) | |
| return chunks | |
| # =========================================================================== | |
| # ํค-ํธ๋ค๋ฌ ๋งคํ | |
| # =========================================================================== | |
| # ํค ์ด๋ฆ โ ํธ๋ค๋ฌ ๋งคํ | |
| # ๊ฐ์ ํธ๋ค๋ฌ๋ฅผ ์ฌ๋ฌ ํค์ ๋งคํ ๊ฐ๋ฅ | |
| CHUNK_HANDLERS = { | |
| # ๋ก์ดํฐ ํ๋ก๊ทธ๋จ | |
| "loyalty_programs": handle_loyalty_programs, | |
| "loyalty_program": handle_loyalty_programs, | |
| # ๋ฉค๋ฒ์ญ ๋ฑ๊ธ | |
| "membership_tiers": handle_membership_tiers, | |
| # ๋ง์ผ์คํค ํ๋ก๊ทธ๋จ | |
| "milestone_program": handle_milestone_programs, | |
| "milestone_programs": handle_milestone_programs, | |
| "milestones": handle_milestone_programs, | |
| # ํํธ๋ ๋ฑ๊ธ ๋งค์นญ | |
| "partner_status_matches": handle_partner_status_matches, | |
| "partner_status_match": handle_partner_status_matches, | |
| # ํฌ์ธํธ ์์คํ | |
| "points_systems": handle_points_systems, | |
| "points_system": handle_points_systems, | |
| # ํธํ | |
| "hotel_properties": handle_hotel_properties, | |
| "hotel_brands": handle_hotel_brands, | |
| # ๋ฑ๊ธ๋ณ ๊ตฌํ | |
| "tier_implementations": handle_tier_implementations, | |
| # ํฌ๋ ๋ง ์นด๋ | |
| "credit_cards": handle_credit_cards, | |
| # ๊ตฌ๋ ํ๋ก๊ทธ๋จ | |
| "subscription_programs": handle_subscription_programs, | |
| # ํ๋ก๋ชจ์ | |
| "promotions": handle_promotions, | |
| # ๊ฐ๊ฒฉ/ํฌ์ธํธ | |
| "pricing": handle_pricing, | |
| "pricing_analysis": handle_pricing_analysis, | |
| "pricing_2025": handle_pricing, # ์ฐ๋๋ณ ์๊ธ ์ ๋ณด | |
| "pricing_2026": handle_pricing, | |
| "pricing_2027": handle_pricing, | |
| # ํํ | |
| "benefits": handle_benefits, | |
| # Best Rate Guarantee | |
| "best_rate_guarantee": handle_best_rate_guarantee, | |
| # ์ฑ๋ ๊ตฌํ | |
| "channel_implementations": handle_channel_implementations, | |
| # ์ฑ๋ ํํ ํจํค์ง | |
| "channel_benefit_packages": handle_channel_benefit_packages, | |
| # ํธํ ์์ค | |
| "hotel_facilities": handle_hotel_facilities, | |
| "other_facilities": handle_hotel_facilities, | |
| # ์ด๊ทธ์ ํํฐ๋ธ ๋ผ์ด์ง - hotel_facilities ํธ๋ค๋ฌ๋ก ํตํฉ ์ฒ๋ฆฌ | |
| "executive_lounge": handle_hotel_facilities, | |
| # ์ฐ๋์ค ์์ค - hotel_facilities ํธ๋ค๋ฌ๋ก ํตํฉ ์ฒ๋ฆฌ | |
| "pulse8_wellness": handle_hotel_facilities, | |
| "wellness_facility": handle_hotel_facilities, | |
| "spa_wellness": handle_hotel_facilities, | |
| # ํธํ ์ด๋ฉ๋ํฐ ์์ฝ - hotel_facilities ํธ๋ค๋ฌ๋ก ํตํฉ ์ฒ๋ฆฌ | |
| "hotel_amenities_summary": handle_hotel_facilities, | |
| "amenities_summary": handle_hotel_facilities, | |
| # ์๋ณ ๊ฐ๊ฒฉ ์์ธ (๊ณ ์ ๊ตฌ์กฐ๋ก ๋ณ๋ ํธ๋ค๋ฌ ์ ์ง) | |
| "monthly_cash_prices_detailed": handle_monthly_prices_detailed, | |
| "monthly_points_prices_detailed": handle_monthly_prices_detailed, | |
| # ๊ฐ์ค ์ ํ๋ณ ์๊ธ (๊ณ ์ ๊ตฌ์กฐ๋ก ๋ณ๋ ํธ๋ค๋ฌ ์ ์ง) | |
| "room_rates_by_type": handle_room_rates_by_type, | |
| # ๊ฐ์ค ์ ํ | |
| "room_types": handle_room_types, | |
| # ๊ฐ์ค ์ด๋ฉ๋ํฐ | |
| "room_common_amenities": handle_room_amenities, | |
| "room_common_features": handle_room_amenities, | |
| # ๋ ์คํ ๋/๋ค์ด๋ | |
| "restaurants": handle_restaurants, | |
| "breakfast_options": handle_breakfast, | |
| "breakfast_special_features": handle_breakfast, | |
| "dining_programs": handle_dining_programs, | |
| # ํ์ | |
| "ratings": handle_ratings, | |
| # ๊ฐ๊ฒฉ ๋ถ์ (Core ํค) | |
| "price_analysis": handle_pricing_analysis, | |
| # ํ์ ์๊ธ | |
| "member_rates": handle_member_rates, | |
| # ์ ์ฑ ๊ด๋ จ (ํํ ํธ๋ค๋ฌ๋ก ์ฒ๋ฆฌ) | |
| "blackout_periods": handle_benefits, | |
| "excluded_rate_types": handle_benefits, | |
| "excluded_rate_evidence": handle_benefits, | |
| "excluded_properties": handle_benefits, | |
| "regional_exceptions": handle_benefits, | |
| "elite_qualifying_points_rules": handle_benefits, | |
| "rate_classifications": handle_benefits, | |
| "rollover_nights_policy": handle_benefits, | |
| "sub_programs": handle_benefits, | |
| "point_exclusions": handle_benefits, | |
| "general_policies": handle_benefits, | |
| "tier_upgrade_exclusion_rules": handle_benefits, | |
| "overseas_usage_info": handle_benefits, | |
| "performance_calculation_rules": handle_benefits, | |
| "family_card_info": handle_benefits, | |
| "customer_service": handle_benefits, | |
| "common_benefits": handle_benefits, | |
| "elite_night_credit_rules": handle_benefits, | |
| "amex_common_benefits": handle_benefits, | |
| "annual_choice_benefits": handle_benefits, | |
| "partner_programs": handle_benefits, | |
| "non_qualifying_rates": handle_benefits, | |
| "key_policies": handle_benefits, | |
| # ์นด๋ ํํ/ํ ์ธ ๊ด๋ จ (Phase 3 ํ์ฅ) | |
| "club_lounge_services": handle_benefits, # ํด๋ฝ ๋ผ์ด์ง ์๋น์ค | |
| "spending_exclusions": handle_benefits, # ์ ๋ฆฝ ์ ์ธ ํญ๋ชฉ | |
| "account_linking_guide": handle_benefits, # ๊ณ์ ์ฐ๋ ๊ฐ์ด๋ | |
| "hotel_discounts": handle_benefits, # ํธํ ํ ์ธ ํํ | |
| "spa_discounts": handle_benefits, # ์คํ ํ ์ธ ํํ | |
| "airport_lounges": handle_benefits, # ๊ณตํญ ๋ผ์ด์ง ํํ | |
| # ์์ค/ํธํ ์ ๋ณด ๊ด๋ จ | |
| "dining_venues": handle_restaurants, | |
| "dining_details": handle_restaurants, | |
| "room_service": handle_restaurants, | |
| "policies": handle_hotel_facilities, | |
| "facilities": handle_hotel_facilities, | |
| # FHR/์๋ฉ์ค ๊ด๋ จ (์ฑ๋ ๊ตฌํ ๋๋ ํํ์ผ๋ก ์ฒ๋ฆฌ) | |
| "partner_program": handle_benefits, | |
| "channel_comparison": handle_benefits, | |
| "booking_payment_rules": handle_benefits, | |
| "eligible_cards": handle_benefits, | |
| "korea_cards": handle_benefits, | |
| "korea_booking": handle_benefits, | |
| "price_comparison": handle_pricing, | |
| "point_earning_rules": handle_benefits, | |
| "korea_amex_usage": handle_benefits, | |
| "korea_cardholder_guides": handle_benefits, | |
| "terms_and_conditions": handle_benefits, | |
| # --- ํญ๊ณต ํ๋ก๊ทธ๋จ (Phase 2) --- | |
| "airline_programs": handle_airline_programs, | |
| "airline_program": handle_airline_programs, | |
| "airline_partnership": handle_airline_partnership, # Flying Blue โ Korean Air ๋ฑ | |
| "airline_tiers": handle_airline_tiers, | |
| "airline_tier": handle_airline_tiers, | |
| "award_charts": handle_award_charts, | |
| "award_chart": handle_award_charts, | |
| "airline_earning_rules": handle_airline_earning_rules, | |
| # --- ํญ๊ณต ์ด์ (Airline Fares) --- | |
| "airline_fare_tables": handle_airline_fare_tables, | |
| "domestic_fare_rules": handle_domestic_fare_rules, | |
| "domestic_peak_seasons": handle_domestic_peak_seasons, | |
| "domestic_route_fares": handle_domestic_route_fares, | |
| "codeshare_domestic_fares": handle_codeshare_domestic_fares, | |
| "international_route_fares": handle_international_route_fares, | |
| "fare_comparison_summary": handle_fare_comparison_summary, | |
| "important_notes": handle_important_notes, | |
| # --- ํญ๊ณต ๋ถ๊ฐ ์๊ธ ๊ท์ (Extra Fee Rules) --- | |
| "baggage_rules": handle_baggage_rules, | |
| "baggage_general_rules": handle_baggage_general_rules, | |
| "sports_equipment_rules": handle_sports_equipment_rules, | |
| "pet_transport_rules": handle_pet_transport_rules, | |
| "unaccompanied_minor_rules": handle_unaccompanied_minor_rules, | |
| "refund_rules": handle_refund_rules, | |
| "no_show_penalties": handle_no_show_penalties, | |
| "lounge_penalties": handle_lounge_penalties, | |
| "medical_oxygen_rules": handle_medical_oxygen_rules, | |
| "codeshare_baggage_rules": handle_codeshare_baggage_rules, | |
| "route_classifications": handle_route_classifications, | |
| # --- ํญ๊ณต ์ด์ ์ถ๊ฐ ์ ๋ณด (์์ฆ, ํด๋์ค, ์ธ๊ธ, ๊ณตํญ, ์ ๋ฅํ ์ฆ๋ฃ, ํฉํธ) --- | |
| "international_season_definitions": handle_international_season_definitions, | |
| "booking_class_definitions": handle_booking_class_definitions, | |
| "special_tax_structures": handle_special_tax_structures, | |
| "multi_airport_routes": handle_multi_airport_routes, | |
| "fuel_surcharge_tiers": handle_fuel_surcharge_tiers, | |
| "facts": handle_facts_router, # ๋๋ฉ์ธ๋ณ ๋ผ์ฐํ (ํธํ โ NESTED, ํญ๊ณต โ handle_airline_facts) | |
| # --- ๋/๋ด์ค (Phase 5) --- | |
| "deal_alerts": handle_deal_alerts, | |
| "deal_alert": handle_deal_alerts, | |
| "news_updates": handle_news_updates, | |
| "news_update": handle_news_updates, | |
| # --- ๋ง์ผ๋ฆฌ์ง ํจ์จ ๋ถ์ (Mileage Efficiency) --- | |
| "award_chart_entries": handle_award_chart_entries, | |
| "mileage_efficiency_analysis": handle_mileage_efficiency_analysis, | |
| "route_efficiency_data_lowest_fare": handle_route_efficiency_data, | |
| "route_efficiency_data_highest_fare": handle_route_efficiency_data, | |
| "efficiency_ranking_lowest_fare_low_season": handle_efficiency_ranking, | |
| "efficiency_ranking_highest_fare_low_season": handle_efficiency_ranking, | |
| "efficiency_ranking_lowest_fare_high_season": handle_efficiency_ranking, | |
| "efficiency_ranking_highest_fare_high_season": handle_efficiency_ranking, | |
| "fare_basis_comparison": handle_fare_basis_comparison, | |
| "mileage_usage_strategy": handle_mileage_usage_strategy, | |
| # --- ๋๋ฝ๋ ๋ง์ผ๋ฆฌ์ง ํจ์จ ๋ถ์ ํค ๋งคํ ์ถ๊ฐ --- | |
| "route_efficiency_data": handle_route_efficiency_data, # nested dict ๊ตฌ์กฐ ์ง์ | |
| "efficiency_ranking": handle_efficiency_ranking, # nested dict (top_20/worst_20) ์ง์ | |
| "key_insights": handle_key_insights, # ํต์ฌ ์ธ์ฌ์ดํธ | |
| "usage_recommendations": handle_usage_recommendations, # ์ฌ์ฉ ๊ถ์ฅ์ฌํญ | |
| "strategy_recommendations": handle_mileage_usage_strategy, # ๊ธฐ์กด ํธ๋ค๋ฌ ์ฌ์ฌ์ฉ | |
| "regional_efficiency_summary": handle_regional_efficiency_summary, # ์ง์ญ๋ณ ํจ์จ ์์ฝ | |
| "seasonal_efficiency_summary": handle_seasonal_efficiency_summary, # ์์ฆ๋ณ ํจ์จ ์์ฝ | |
| # --- ์ถ๊ฐ ๋ง์ผ๋ฆฌ์ง ํจ์จ ํค ๋งคํ --- | |
| "route_efficiency_evidence": handle_route_efficiency_data, # ๋ ธ์ ๋ณ ํจ์จ ์ฆ๊ฑฐ | |
| "business_vs_smartium_comparison": handle_fare_basis_comparison, # ๋น์ฆ๋์ค vs ์ค๋งํฐ์ ๋น๊ต | |
| "weekday_weekend_comparison": handle_fare_basis_comparison, # ์ฃผ์ค/์ฃผ๋ง ํจ์จ ๋น๊ต | |
| "usage_recommendations_detailed": handle_usage_recommendations, # ์์ธ ๊ถ์ฅ์ฌํญ | |
| # --- ๋ง์ผ๋ฆฌ์ง ๊ท์ ๊ด๋ จ ํค ๋งคํ (์ ๊ท ์ถ๊ฐ) --- | |
| # ์ฑ์๊ธฐ/์์ฆ ๊ท์ | |
| "peak_seasons": handle_peak_season_rules, | |
| "peak_season_rules": handle_peak_season_rules, | |
| "peak_season_exemptions": handle_peak_season_rules, | |
| # ๋ง์ผ๋ฆฌ์ง ์ฐจ๊ฐ ๊ท์ | |
| "business_smartium_rules": handle_mileage_deduction_rules, | |
| "child_mileage_deduction_rules": handle_mileage_deduction_rules, | |
| # ๋ ธ์ /ํ์น ๊ท์ | |
| "route_restrictions": handle_route_transfer_rules, | |
| "transfer_rules": handle_route_transfer_rules, | |
| # ํ๋ถ/๋ณ๊ฒฝ ์์๋ฃ | |
| "refund_fees": handle_refund_change_fees, | |
| "change_fees": handle_refund_change_fees, | |
| "fee_exemptions": handle_refund_change_fees, | |
| "refund_channel_restrictions": handle_refund_change_fees, | |
| "online_change_fee_rules": handle_refund_change_fees, | |
| "mileage_refund_calculation": handle_refund_change_fees, | |
| "mileage_validity_on_refund": handle_refund_change_fees, | |
| "change_additional_costs": handle_refund_change_fees, | |
| "fuel_surcharge_change_rules": handle_refund_change_fees, | |
| # ๋ ธ์ผ ๊ท์ | |
| "no_show_definitions": handle_no_show_rules, | |
| "no_show_post_processing": handle_no_show_rules, | |
| # ์ฌ์ ๋ณ๊ฒฝ ๊ท์ | |
| "itinerary_change_rules": handle_itinerary_change_rules, | |
| "itinerary_change_examples": handle_itinerary_change_rules, | |
| # ํ๋ก๊ทธ๋จ/์ธ๊ธ ๊ท์ | |
| "mileage_now_program": handle_mileage_program_rules, | |
| "taxes_and_fees": handle_mileage_program_rules, | |
| # ์์ฝ ๊ท์ | |
| "booking_process": handle_mileage_booking_rules, | |
| "booking_notes": handle_mileage_booking_rules, | |
| # ์ ์ธ ๋ ธ์ | |
| "excluded_routes": handle_excluded_routes, | |
| # --- ์ง์ญ๋ณ ํจ์จ ๋ฐ์ดํฐ (Asiana ๋ฑ) --- | |
| "route_efficiency_data_domestic": handle_regional_efficiency_data, | |
| "route_efficiency_data_japan": handle_regional_efficiency_data, | |
| "route_efficiency_data_china_northeast_asia": handle_regional_efficiency_data, | |
| "route_efficiency_data_southeast_asia": handle_regional_efficiency_data, | |
| "route_efficiency_data_south_asia_cis": handle_regional_efficiency_data, | |
| "route_efficiency_data_oceania": handle_regional_efficiency_data, | |
| "route_efficiency_data_americas": handle_regional_efficiency_data, | |
| "route_efficiency_data_europe": handle_regional_efficiency_data, | |
| "efficiency_ranking_low_season": handle_efficiency_ranking, | |
| # --- ๋ง์ผ๋ฆฌ์ง ๊ตฌ๋งค ํ๋ก๊ทธ๋จ (Mileage Purchase Programs) --- | |
| "mileage_purchase_programs": handle_mileage_purchase_programs, | |
| "price_summary": handle_mileage_price_summary, | |
| "insights": handle_mileage_insights, | |
| # --- ๋ฏธ์ฒ๋ฆฌ ํค๋ค์ ์ ์ ํ ํธ๋ค๋ฌ์ ๋งคํ --- | |
| "raw_pricing_data": handle_pricing, # ClassicTravel ์์ ๊ฐ๊ฒฉ ๋ฐ์ดํฐ | |
| "rate_descriptions": handle_benefits, # ClassicTravel ์๊ธ ์ค๋ช | |
| "brunch_options": handle_breakfast, # ClassicTravel ๋ธ๋ฐ์น ์ต์ | |
| "regional_info": handle_hotel_facilities, # ClassicTravel ์ง์ญ ์ ๋ณด | |
| "breakfast_benefit_participating_brands": handle_benefits, # GHA Discovery ์กฐ์ ํํ ์ฐธ์ฌ ๋ธ๋๋ | |
| "direct_channels": handle_benefits, # GHA Discovery ์ง์ ์ฑ๋ | |
| "irregular_prices_observed": handle_pricing, # ํญ๊ณต์ฌ ๋ถ๊ท์น ๊ฐ๊ฒฉ ๊ด์ฐฐ | |
| "route_availability_patterns": handle_airline_programs, # ํญ๊ณต์ฌ ๋ ธ์ ๊ฐ์ฉ์ฑ ํจํด | |
| "booking_policies": handle_benefits, # WhataHotel ์์ฝ ์ ์ฑ (์๋น์ค ์์๋ฃ, ๊ฒฐ์ ์ ์ฑ , ์ทจ์ ์ ์ฑ ) | |
| # --- AMEX Platinum ์นด๋ ์ ์ฉ ํค ๋งคํ --- | |
| "travel_programs": handle_benefits, # FHR, THC ๋ฑ ์ฌํ ํ๋ก๊ทธ๋จ | |
| "statement_credit_timing": handle_benefits, # ์คํ ์ดํธ๋จผํธ ํฌ๋ ๋ง ์ฒ๋ฆฌ ์๊ฐ | |
| "statement_credit_troubleshooting": handle_benefits, # ํฌ๋ ๋ง ๋ฌธ์ ํด๊ฒฐ ํ | |
| "tier_eligibility_comparison": handle_benefits, # ๋ฑ๊ธ ์๊ฒฉ ๋น๊ตํ | |
| "benefit_exclusions": handle_benefits, # ํํ ์ ์ธ ํญ๋ชฉ | |
| "benefit_calendar": handle_benefits, # ํํ ์ผ์ (์๊ฐ/๋ถ๊ธฐ/์ฐ๊ฐ) | |
| "contact_information": handle_benefits, # ์ฐ๋ฝ์ฒ ์ ๋ณด | |
| "important_notices": handle_benefits, # ์ค์ ๊ณต์ง์ฌํญ | |
| "benefit_value_summary": handle_benefits, # ํํ ๊ฐ์น ์์ฝ | |
| # --- Chase ์นด๋ ๊ด๋ จ ํค ๋งคํ --- | |
| "lounge_access_details": handle_benefits, # ๊ณตํญ ๋ผ์ด์ง ์ ๊ทผ ์ ๋ณด | |
| "spending_tier_benefits": handle_benefits, # ์ง์ถ ๋ฑ๊ธ๋ณ ํํ | |
| "balance_transfer_conditions": handle_benefits, # ์์ก ์ด์ฒด ์กฐ๊ฑด | |
| "chase_pay_over_time": handle_benefits, # Chase ํ ๋ถ ๊ฒฐ์ ์ต์ | |
| "state_notices": handle_benefits, # ์ฃผ๋ณ ๊ณ ์ง์ฌํญ | |
| "mileageplus_integration": handle_benefits, # MileagePlus ์ฐ๋ ์ ๋ณด | |
| "privacy_legal": handle_benefits, # ๊ฐ์ธ์ ๋ณด/๋ฒ์ ๊ณ ์ง | |
| "concierge_services": handle_benefits, # ์ปจ์์ด์ง ์๋น์ค | |
| "other_info": handle_benefits, # ๊ธฐํ ์ ๋ณด | |
| # --- ์ถ๊ฐ ๋ฏธ์ฒ๋ฆฌ ํค ๋งคํ (credit card ๊ด๋ จ) --- | |
| "ihg_membership_earning_rules": handle_benefits, # IHG ๋ฉค๋ฒ์ญ ์ ๋ฆฝ ๊ท์น | |
| "points_expiration_policy": handle_benefits, # ํฌ์ธํธ ๋ง๋ฃ ์ ์ฑ | |
| "terms_summary": handle_benefits, # ์ฝ๊ด ์์ฝ | |
| "hyatt_brands_reference": handle_hotel_brands, # Hyatt ๋ธ๋๋ ๋ชฉ๋ก | |
| "ihg_program_earning_info": handle_benefits, # IHG ํ๋ก๊ทธ๋จ ์ ๋ฆฝ ์ ๋ณด | |
| "related_cards_comparison": handle_benefits, # ๊ด๋ จ ์นด๋ ๋น๊ต | |
| "value_analysis": handle_benefits, # ๊ฐ์น ๋ถ์ | |
| # --- Disney ์นด๋ ์ ์ฉ ํค ๋งคํ --- | |
| "dining_eligible_restaurants": handle_benefits, # Disney ์๋น ํ ์ธ ๋์ ๋ ์คํ ๋ | |
| "tour_eligible_experiences": handle_benefits, # Disney ํฌ์ด/์ฒดํ ํ ์ธ ๋์ | |
| "photo_opportunities": handle_benefits, # Disney ์นด๋ ํฌํ ๊ธฐํ | |
| # --- IHG/United ์นด๋ ๊ด๋ จ ํค ๋งคํ --- | |
| "airline_program_integration": handle_benefits, # ํญ๊ณต์ฌ ํ๋ก๊ทธ๋จ ์ฐ๋ | |
| # --- ์ถ๊ฐ ๋ฏธ์ฒ๋ฆฌ ํค ๋งคํ (Citi/Barclays/BoA ์นด๋) --- | |
| "aadvantage_program_tiers": handle_benefits, # AAdvantage ํ๋ก๊ทธ๋จ ๋ฑ๊ธ | |
| "american_eagle_operators": handle_benefits, # American Eagle ์ดํญ์ฌ | |
| "additional_benefits": handle_benefits, # ์ถ๊ฐ ํํ | |
| "insurance_benefits": handle_benefits, # ๋ณดํ ํํ | |
| "security_features": handle_benefits, # ๋ณด์ ๊ธฐ๋ฅ | |
| "entertainment_program": handle_benefits, # ์ํฐํ ์ธ๋จผํธ ํ๋ก๊ทธ๋จ | |
| "annual_benefit_value_summary": handle_benefits, # ์ฐ๊ฐ ํํ ๊ฐ์น ์์ฝ | |
| "card_benefits": handle_benefits, # ์นด๋ ํํ (์ต์์ ํค) | |
| "eligibility_requirements": handle_benefits, # ์๊ฒฉ ์๊ฑด | |
| "balance_transfer_terms": handle_benefits, # ์์ก ์ด์ฒด ์กฐ๊ฑด | |
| "cash_equivalent_transactions": handle_benefits, # ํ๊ธ ๋ฑ๊ฐ ๊ฑฐ๋ | |
| "points_forfeiture_conditions": handle_benefits, # ํฌ์ธํธ ๋ชฐ์ ์กฐ๊ฑด | |
| "trueblue_integration": handle_benefits, # TrueBlue ์ฐ๋ | |
| "trueblue_points_important_info": handle_benefits, # TrueBlue ํฌ์ธํธ ์ค์ ์ ๋ณด | |
| "additional_info": handle_benefits, # ์ถ๊ฐ ์ ๋ณด | |
| "partnership_programs": handle_benefits, # ํํธ๋์ญ ํ๋ก๊ทธ๋จ | |
| } | |
| # ์ค์ฒฉ ํค ์ง์ (์: facts.pricing_analysis) | |
| NESTED_HANDLERS = { | |
| # facts.* ํค๋ค (๋ฒ์ฉ ํธ๋ค๋ฌ๋ก ์ฒ๋ฆฌ) | |
| "facts.pricing_analysis": handle_pricing_analysis, | |
| "facts.pricing_insights": handle_pricing_analysis, | |
| "facts.pricing_2025": handle_pricing, # ์ฐ๋๋ณ ๊ฐ๊ฒฉ ์ ๋ณด | |
| "facts.pricing_2026": handle_pricing, | |
| "facts.pricing_2027": handle_pricing, | |
| "facts.user_tips": None, # ๋ฒ์ฉ ํ ์คํธ๋ก ์ฒ๋ฆฌ ์์ | |
| "facts.pros_cons": None, | |
| "facts.dining_venues": handle_restaurants, | |
| "facts.facilities": handle_hotel_facilities, | |
| "facts.room_types": handle_room_types, | |
| } | |
| # ๋ฌด์ํ ํค (๋ฉํ๋ฐ์ดํฐ ๋ฑ - ๊ฒ์ ๊ฐ์น ๋ฎ์) | |
| IGNORED_KEYS = { | |
| # ๋ฉํ๋ฐ์ดํฐ | |
| "extraction_timestamp", | |
| "extractor_model", | |
| "schema_version", | |
| "document_reference", | |
| "identity", | |
| "source", | |
| "version", | |
| "document_metadata", | |
| "extraction_metadata", | |
| "extraction_notes", | |
| "analysis_metadata", # ๋ถ์ ๋ฉํ๋ฐ์ดํฐ | |
| # ๋ด๋ถ/์ฐธ์กฐ์ฉ | |
| "evidence", | |
| "extra_attributes", | |
| "data_sources", | |
| "verification_needed", | |
| "price_summary_evidence", # ๊ฐ๊ฒฉ ์์ฝ ์ฆ๊ฑฐ (๋ณ๋ ํธ๋ค๋ฌ ๋ถํ์) | |
| # ์ค์ฒฉ ํธ๋ค๋ฌ๋ก ์ฒ๋ฆฌ (ํธํ facts๋ NESTED_HANDLERS์์ ์ฒ๋ฆฌ) | |
| # "facts" - ํญ๊ณต ์ด์์ฉ์ handle_airline_facts์์ ์ฒ๋ฆฌ | |
| # Core ํค์ ํฌํจ๋๋ฏ๋ก ๋ณ๋ ์ฒ๋ฆฌ ๋ถํ์ | |
| # "hotel_brands", # ์ด์ handle_hotel_brands๋ก ์ฒ๋ฆฌ | |
| "nearby_attractions", # extra_attributes์ ํฌํจ | |
| # ๊ฒ์ ๊ฐ์น ๋ฎ์ | |
| "loyalty_program_features", # loyalty_program์ผ๋ก ์ปค๋ฒ | |
| "breakfast_special_features", # breakfast_options์ผ๋ก ์ปค๋ฒ | |
| "unverified_items", | |
| "unverified_items_evidence", | |
| "pending_items", | |
| "to_verify", | |
| "sources", | |
| "legal_provisions", | |
| "external_references", | |
| "general_terms", | |
| "pros_cons", | |
| "verification_needed", # ํ์ธ ํ์ ์ฌํญ (๊ฒ์ ๊ฐ์น ๋ฎ์) | |
| "collection_date", # ์์ง์ผ ๋ฉํ๋ฐ์ดํฐ | |
| "related_documents", # ๊ด๋ จ ๋ฌธ์ ์ฐธ์กฐ | |
| # AMEX ์นด๋ ๊ด๋ จ ์ฆ๊ฑฐ์๋ฃ ๋ฐ ๋ฒ์ ์ ๋ณด (๊ฒ์ ๊ฐ์น ๋ฎ์) | |
| "seller_of_travel", # ์ฌํ ํ๋งค์ ๋ฒ์ ์ ๋ณด | |
| "statement_credit_evidence", # ์คํ ์ดํธ๋จผํธ ํฌ๋ ๋ง ์ฆ๊ฑฐ | |
| "verification_needed_evidence", # ํ์ธ ํ์ ์ฌํญ ์ฆ๊ฑฐ | |
| # Chase ์นด๋ ๊ด๋ จ ๋ฉํ๋ฐ์ดํฐ (๊ฒ์ ๊ฐ์น ๋ฎ์) | |
| "related_links", # ๊ด๋ จ ๋งํฌ (URL ์ฐธ์กฐ์ฉ) | |
| "benefit_value_summary_total", # ํํ ๊ฐ์น ์ดํฉ (๋ฉํ๋ฐ์ดํฐ) | |
| "spending_tier_120k_additional", # ์ถ๊ฐ ์ง์ถ ๋ฑ๊ธ (์์ธ ๋ด์ฉ ์์) | |
| "benefit_value_evidence", # ํํ ๊ฐ์น ์ฆ๊ฑฐ (๋ด๋ถ์ฉ) | |
| "legal_notices", # ๋ฒ์ ๊ณ ์ง์ฌํญ | |
| # Deprecated ํค (์ฌ์ฉํ์ง ์์) | |
| # "loyalty_programs" (๋ณต์) - ๋จ, ํ์ ํธํ์ฑ ์ํด ํธ๋ค๋ฌ๋ ์ ์ง | |
| } | |
| def get_handler(key: str): | |
| """ํค์ ๋ํ ํธ๋ค๋ฌ ๋ฐํ""" | |
| return CHUNK_HANDLERS.get(key) | |
| def get_nested_handler(parent_key: str, child_key: str): | |
| """์ค์ฒฉ ํค์ ๋ํ ํธ๋ค๋ฌ ๋ฐํ""" | |
| nested_key = f"{parent_key}.{child_key}" | |
| return NESTED_HANDLERS.get(nested_key) | |
| def is_ignored(key: str) -> bool: | |
| """๋ฌด์ํด์ผ ํ ํค์ธ์ง ํ์ธ""" | |
| return key in IGNORED_KEYS | |
| def get_all_handler_keys() -> set: | |
| """์ฒ๋ฆฌ ๊ฐ๋ฅํ ๋ชจ๋ ํค ๋ฐํ""" | |
| keys = set(CHUNK_HANDLERS.keys()) | |
| keys.update(IGNORED_KEYS) | |
| return keys | |