Spaces:
Sleeping
Sleeping
File size: 7,499 Bytes
131589b | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 | """모두의 빛길 — 노드/엣지 데이터 스키마 정의.
이 모듈은 추천 그래프를 구성하는 모든 노드 타입을 dataclass로 정의한다.
PyG HeteroData에 그대로 매핑할 수 있도록 단순한 평면 구조로 유지한다.
노드 타입
---------
- VenueNode : 문화시설 (박물관/미술관/공연장/도서관 등)
- EventNode : 공연·전시 이벤트
- TransitNode : 지하철역·버스정류장
- AmenityNode : 화장실/쉼터/벤치/CCTV
- HazardNode : 사고다발지역/공사구간/저조도
엣지 타입
---------
- (VENUE)-hosts->(EVENT)
- (VENUE)<->(TRANSIT) near
- (VENUE)<->(AMENITY) has_amenity
- (TRANSIT)<->(TRANSIT) walkable / connects
- (TRANSIT)->(HAZARD) passes_hazard
"""
from __future__ import annotations
from dataclasses import dataclass, field
from typing import List, Optional
# ============================================================
# 노드 dataclass
# ============================================================
@dataclass
class VenueNode:
"""문화시설 노드."""
id: int
name: str
lat: float
lng: float
venue_type: str # 박물관/미술관/공연장/도서관/카페/문화센터
accessibility_score: float = 0.0 # 0~1, 무장애 종합점수
has_elevator: bool = False
has_disabled_toilet: bool = False
has_ramp: bool = False
free: bool = False
indoor: bool = True
age_friendly: bool = False # 어르신 무료/할인 등
booking_url: str = ""
phone: str = ""
def feature_vec(self) -> List[float]:
"""GNN 입력 feature 벡터."""
return [
self.accessibility_score,
float(self.has_elevator),
float(self.has_disabled_toilet),
float(self.has_ramp),
float(self.free),
float(self.indoor),
float(self.age_friendly),
# venue_type one-hot (8종)
*self._type_onehot(),
]
def _type_onehot(self) -> List[float]:
types = ["박물관", "미술관", "공연장", "도서관", "카페", "문화센터", "체험관", "기타"]
v = [0.0] * len(types)
if self.venue_type in types:
v[types.index(self.venue_type)] = 1.0
else:
v[-1] = 1.0
return v
@dataclass
class EventNode:
"""공연·전시 이벤트 노드."""
id: int
venue_id: int
title: str
start_time: str # "HH:MM"
end_time: str
age_limit: Optional[int] = None
price: int = 0
booking_url: str = ""
event_type: str = "공연" # 공연/전시/체험/강좌
has_subtitle: bool = False # 자막
has_sign_lang: bool = False # 수어 통역
has_audio_guide: bool = False # 음성해설/점자
def feature_vec(self) -> List[float]:
sh, sm = map(int, self.start_time.split(":"))
eh, em = map(int, self.end_time.split(":"))
return [
sh + sm / 60.0,
eh + em / 60.0,
float(self.age_limit or 0),
float(self.price),
float(self.has_subtitle),
float(self.has_sign_lang),
float(self.has_audio_guide),
float(self.event_type == "공연"),
float(self.event_type == "전시"),
float(self.event_type == "체험"),
float(self.event_type == "강좌"),
]
@dataclass
class TransitNode:
"""지하철역·버스정류장 노드."""
id: int
name: str
lat: float
lng: float
transit_type: str = "지하철" # 지하철/버스/저상버스
has_elevator: bool = False
has_toilet: bool = False
line: str = ""
def feature_vec(self) -> List[float]:
return [
float(self.has_elevator),
float(self.has_toilet),
float(self.transit_type == "지하철"),
float(self.transit_type == "저상버스"),
float(self.transit_type == "버스"),
]
@dataclass
class AmenityNode:
"""편의시설 노드 — 화장실/쉼터/벤치/CCTV."""
id: int
name: str
lat: float
lng: float
amenity_type: str # 공공화장실/장애인화장실/무더위쉼터/벤치/CCTV/가로등
accessible: bool = False
open_24h: bool = False
def feature_vec(self) -> List[float]:
return [
float(self.accessible),
float(self.open_24h),
float(self.amenity_type == "공공화장실"),
float(self.amenity_type == "장애인화장실"),
float(self.amenity_type == "무더위쉼터"),
float(self.amenity_type == "벤치"),
float(self.amenity_type == "CCTV"),
float(self.amenity_type == "가로등"),
]
@dataclass
class HazardNode:
"""위험구간 노드 — 사고다발지역/공사구간/저조도."""
id: int
lat: float
lng: float
hazard_type: str # 보행자사고다발/노인사고다발/공사/저조도/계단/턱
severity: float = 0.5 # 0~1
night_only: bool = False # 야간에만 위험
def feature_vec(self) -> List[float]:
return [
self.severity,
float(self.night_only),
float(self.hazard_type == "보행자사고다발"),
float(self.hazard_type == "노인사고다발"),
float(self.hazard_type == "공사"),
float(self.hazard_type == "저조도"),
float(self.hazard_type == "계단"),
float(self.hazard_type == "턱"),
]
# ============================================================
# 컬렉션 컨테이너
# ============================================================
@dataclass
class GraphData:
"""GraphBuilder에 입력될 컬렉션."""
venues: List[VenueNode] = field(default_factory=list)
events: List[EventNode] = field(default_factory=list)
transits: List[TransitNode] = field(default_factory=list)
amenities: List[AmenityNode] = field(default_factory=list)
hazards: List[HazardNode] = field(default_factory=list)
def stats(self) -> dict:
return {
"VENUE": len(self.venues),
"EVENT": len(self.events),
"TRANSIT": len(self.transits),
"AMENITY": len(self.amenities),
"HAZARD": len(self.hazards),
}
# ============================================================
# 슬롯 정의 (slot_extractor와 공유)
# ============================================================
SLOT_NAMES = [
"USER_TYPE", # 고령자/휠체어사용자/임산부/시각장애/청각장애/어린이동반/보행보조기
"COMPANION", # 가족/보호자/활동지원사/단체
"ORIGIN", # 병원/복지관/지하철역/자택
"WALK_LIMIT", # 짧음/거리지정/환승최소
"AVOID", # 위험회피/계단회피/경사회피/혼잡회피/사고다발회피/야외회피
"CULTURE_PREF", # 전시/공연/박물관/미술관/도서관/카페/체험/문화일반/미디어아트/전통문화
"WEATHER", # 폭염/한파/강우/미세먼지
"TIME_WINDOW", # 오전/낮/오후/저녁(야간)/시간한정
"BUDGET", # 무료/할인/저예산
"SPECIAL_NEEDS", # 화장실/수유실/안내견/점자/수어자막/엘리베이터/장애인화장실
]
__all__ = [
"VenueNode", "EventNode", "TransitNode", "AmenityNode", "HazardNode",
"GraphData", "SLOT_NAMES",
]
|