feat(agent-office-telegram): realestate match formatter + keyboard
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
93
agent-office/app/telegram/realestate_message.py
Normal file
93
agent-office/app/telegram/realestate_message.py
Normal file
@@ -0,0 +1,93 @@
|
||||
"""청약 매칭 알림 — 텔레그램 메시지 포맷터 + 인라인 키보드 빌더."""
|
||||
import os
|
||||
from html import escape as _h
|
||||
from typing import Optional
|
||||
|
||||
DASHBOARD_URL = os.getenv("REALESTATE_DASHBOARD_URL", "https://example.com/realestate")
|
||||
|
||||
|
||||
def _format_one_compact(m: dict) -> str:
|
||||
score = m.get("match_score", 0)
|
||||
name = _h(m.get("house_nm") or "(제목 없음)")
|
||||
district = m.get("district") or ""
|
||||
region = m.get("region_name") or ""
|
||||
where = f"{region.split()[0] if region else ''} {district}".strip() or "위치 미상"
|
||||
rstart = m.get("receipt_start") or ""
|
||||
rend = m.get("receipt_end") or ""
|
||||
return (
|
||||
f"⭐ {score}점 — <b>{name}</b>\n"
|
||||
f"📍 {_h(where)} 📅 {_h(rstart)} ~ {_h(rend)}"
|
||||
)
|
||||
|
||||
|
||||
def _format_one_full(m: dict) -> str:
|
||||
score = m.get("match_score", 0)
|
||||
name = _h(m.get("house_nm") or "(제목 없음)")
|
||||
district = m.get("district") or ""
|
||||
region = m.get("region_name") or ""
|
||||
flags = []
|
||||
if m.get("is_speculative_area") == "Y":
|
||||
flags.append("투기과열")
|
||||
if m.get("is_price_cap") == "Y":
|
||||
flags.append("분양가상한제")
|
||||
flag_str = f" ({', '.join(flags)})" if flags else ""
|
||||
|
||||
rstart = m.get("receipt_start") or ""
|
||||
rend = m.get("receipt_end") or ""
|
||||
elig = m.get("eligible_types") or []
|
||||
reasons = m.get("match_reasons") or []
|
||||
|
||||
where = f"{region.split()[0] if region else ''} {district}".strip() or "위치 미상"
|
||||
|
||||
lines = [
|
||||
f"⭐ {score}점 — <b>{name}</b>",
|
||||
f"📍 {_h(where)}{_h(flag_str)}",
|
||||
f"📅 청약 {_h(rstart)} ~ {_h(rend)}",
|
||||
]
|
||||
if elig:
|
||||
lines.append(f"✓ 자격: {_h(', '.join(elig))}")
|
||||
if reasons:
|
||||
lines.append(f"💡 {_h(' / '.join(reasons[:4]))}")
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
def format_realestate_matches(matches: list[dict]) -> str:
|
||||
"""매칭 목록을 텔레그램 HTML 메시지로 변환.
|
||||
1~2건은 풀 카드, 3건 이상은 묶음 카드(상위 5건).
|
||||
"""
|
||||
if not matches:
|
||||
return "🏢 새 청약 매칭이 없습니다."
|
||||
|
||||
if len(matches) <= 2:
|
||||
body = "\n\n".join(_format_one_full(m) for m in matches)
|
||||
return f"🏢 <b>새 청약 매칭 {len(matches)}건</b>\n━━━━━━━━━━\n\n{body}"
|
||||
|
||||
top = matches[:5]
|
||||
body = "\n\n".join(_format_one_compact(m) for m in top)
|
||||
suffix = f"\n\n…외 {len(matches) - 5}건" if len(matches) > 5 else ""
|
||||
return f"🏢 <b>새 청약 매칭 {len(matches)}건</b>\n━━━━━━━━━━\n\n{body}{suffix}"
|
||||
|
||||
|
||||
def build_match_keyboard(matches: list[dict]) -> Optional[dict]:
|
||||
"""1~2건: 매치별 [북마크][공고 보기] 행. 3건 이상: [전체 보기] 단일 행."""
|
||||
if not matches:
|
||||
return None
|
||||
|
||||
if len(matches) <= 2:
|
||||
rows = []
|
||||
for m in matches:
|
||||
buttons = [{
|
||||
"text": "🔖 북마크",
|
||||
"callback_data": f"realestate_bookmark_{m['id']}",
|
||||
}]
|
||||
url = m.get("pblanc_url")
|
||||
if url:
|
||||
buttons.append({"text": "📄 공고 보기", "url": url})
|
||||
rows.append(buttons)
|
||||
return {"inline_keyboard": rows}
|
||||
|
||||
return {
|
||||
"inline_keyboard": [[
|
||||
{"text": "📋 전체 보기", "url": DASHBOARD_URL},
|
||||
]],
|
||||
}
|
||||
Reference in New Issue
Block a user