Skip to content

Commit 2e0e73e

Browse files
authored
배포 v1.3.5
배포 v1.3.5
2 parents 11270df + 95e3da9 commit 2e0e73e

File tree

5 files changed

+396
-219
lines changed

5 files changed

+396
-219
lines changed

ELK/app/main.py

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1-
from fastapi import FastAPI, HTTPException
1+
from fastapi import FastAPI, HTTPException, Query
22
from contextlib import asynccontextmanager
33
from datetime import datetime
4+
from typing import List
45

5-
from app.schema.search_schemas import SearchResponse
6+
from app.schema.search_schemas import SearchResponse, LLMToolResponse
67
from app.services.elasticsearch_service import ElasticsearchService
78
from app.schema.log_schemas import LogRequest, LogResponse
89

@@ -38,6 +39,27 @@ async def search_places(query: str, max_results: int = 23):
3839
except Exception as e:
3940
raise HTTPException(status_code=500, detail=str(e))
4041

42+
@app.get("/api/place/search/llm-tool", response_model=LLMToolResponse)
43+
async def search_places_for_llm_tool(region: str, categories: List[str] = Query(..., min_length=3)):
44+
"""LLM 도구를 위한 장소 검색 API"""
45+
try:
46+
if not elasticsearch_service.is_connected():
47+
raise HTTPException(status_code=503, detail="Elasticsearch 연결 실패")
48+
49+
uuids, total = elasticsearch_service.search_places_for_llm_tool(
50+
region=region,
51+
categories=categories
52+
)
53+
54+
return LLMToolResponse(
55+
success=True,
56+
uuids=uuids,
57+
total=total
58+
)
59+
60+
except Exception as e:
61+
raise HTTPException(status_code=500, detail=str(e))
62+
4163
@app.post("/api/chatbot")
4264
async def insert_chatbot_log(log_data: LogRequest):
4365
"""챗봇 로그 삽입"""

ELK/app/schema/search_schemas.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,8 @@ class SearchResponse(BaseModel):
1212
success: bool
1313
places: List[Place]
1414
total: int
15+
16+
class LLMToolResponse(BaseModel):
17+
success: bool
18+
uuids: List[str]
19+
total: int

ELK/app/services/elasticsearch_service.py

Lines changed: 108 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
from elasticsearch import Elasticsearch
2-
from typing import List, Dict, Any
2+
from typing import List, Dict, Any, Tuple
33
from datetime import datetime
44

55

@@ -8,7 +8,7 @@ class ElasticsearchService:
88

99
def __init__(self, host: str = "elasticsearch", port: int = 9200):
1010
self.es = Elasticsearch([{'host': host, 'port': port, 'scheme': 'http'}])
11-
self.index_name = "place_data_v3"
11+
self.index_name = "place_data"
1212
self.log_index_name = "chatbot_log"
1313

1414
def is_connected(self) -> bool:
@@ -48,6 +48,91 @@ def search_places(self, query: str, max_results: int = 23) -> List[str]:
4848
]
4949

5050
return places
51+
52+
def search_places_chatbot(self, query: str, max_results: int = 100):
53+
"""챗봇 장소 검색"""
54+
search_body = {
55+
"query": {
56+
"match": {
57+
"name": {
58+
"query": query,
59+
"fuzziness": "AUTO"
60+
}
61+
}
62+
},
63+
"sort": [{"_score": {"order": "desc"}}],
64+
"size": max_results,
65+
"_source": ["uuid", "name", "category", "subcategory", "gu", "dong", "ro", "station", "location", "opentime", "breaktime", "closedate", "phone", "alias", "address", "content"]
66+
}
67+
68+
response = self.es.search(index=self.index_name, body=search_body)
69+
70+
hits = response['hits']['hits']
71+
places = [
72+
{
73+
'uuid': hit['_source']['uuid'],
74+
'name': hit['_source']['name'],
75+
'category': hit['_source']['category'],
76+
'subcategory': hit['_source']['subcategory'],
77+
'gu': hit['_source']['gu'],
78+
'dong': hit['_source']['dong'],
79+
'ro': hit['_source']['ro'],
80+
'station': hit['_source']['station'],
81+
'location': hit['_source']['location'],
82+
'opentime': hit['_source']['opentime'],
83+
'breaktime': hit['_source']['breaktime'],
84+
'closedate': hit['_source']['closedate'],
85+
'phone': hit['_source']['phone'],
86+
'alias': hit['_source']['alias'],
87+
'address': hit['_source']['address'],
88+
'content': hit['_source']['content']
89+
}
90+
for hit in hits
91+
]
92+
return places
93+
94+
def search_places_for_llm_tool(self, region: str, categories: List[str]) -> Tuple[List[str], int]:
95+
"""
96+
LLM 도구를 위한 장소 검색.
97+
지역과 카테고리 정보를 바탕으로 장소 uuid 목록과 총 개수를 반환합니다.
98+
"""
99+
query_body = {
100+
"query": {
101+
"bool": {
102+
"must": [],
103+
"filter": []
104+
}
105+
},
106+
"size": 10000,
107+
"_source": ["uuid"],
108+
"track_total_hits": True
109+
}
110+
111+
if region:
112+
query_body["query"]["bool"]["must"].append({
113+
"multi_match": {
114+
"query": region,
115+
"fields": ["gu", "dong", "ro", "station", "address"]
116+
}
117+
})
118+
119+
if categories:
120+
query_body["query"]["bool"]["filter"].append({
121+
"bool": {
122+
"should": [
123+
{"terms": {"category.keyword": categories}},
124+
{"terms": {"subcategory.keyword": categories}}
125+
],
126+
"minimum_should_match": 1
127+
}
128+
})
129+
130+
response = self.es.search(index=self.index_name, body=query_body)
131+
132+
uuids = [hit['_source']['uuid'] for hit in response['hits']['hits']]
133+
total = response['hits']['total']['value']
134+
135+
return uuids, total
51136

52137
def create_log_index_if_not_exists(self):
53138
"""로그 인덱스가 없으면 생성"""
@@ -57,30 +142,30 @@ def create_log_index_if_not_exists(self):
57142
mapping = {
58143
"mappings": {
59144
"properties": {
60-
"userId": {"type": "keyword"},
61-
"question": {"type": "text", "analyzer": "nori"},
62-
"answer": {
63-
"properties": {
64-
"title": {"type": "text"},
65-
"placeList": {
66-
"properties": {
67-
"placeId": {"type": "keyword"},
68-
"name": {"type": "text", "analyzer": "nori"},
69-
"address": {"type": "text", "analyzer": "nori"},
70-
"imgUrl": {"type": "keyword"},
71-
"location": {
72-
"lat": {"type": "float"},
73-
"lng": {"type": "float"}
74-
}
145+
"userId": {"type": "keyword"},
146+
"question": {"type": "text", "analyzer": "nori"},
147+
"answer": {
148+
"properties": {
149+
"title": {"type": "text"},
150+
"placeList": {
151+
"properties": {
152+
"placeId": {"type": "keyword"},
153+
"name": {"type": "text", "analyzer": "nori"},
154+
"address": {"type": "text", "analyzer": "nori"},
155+
"imgUrl": {"type": "keyword"},
156+
"location": {
157+
"lat": {"type": "float"},
158+
"lng": {"type": "float"}
75159
}
76-
},
77-
"detail": {"type": "text", "analyzer": "nori"}
78-
}
79-
},
80-
"createAt": {"type": "date"}
81-
}
160+
}
161+
},
162+
"detail": {"type": "text", "analyzer": "nori"}
163+
}
164+
},
165+
"createAt": {"type": "date"}
82166
}
83167
}
168+
}
84169
self.es.indices.create(index=self.log_index_name, body=mapping)
85170
print(f"로그 인덱스 '{self.log_index_name}' 생성 완료")
86171
except Exception as e:

data/congestion_preprocessing.py

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import json
2+
import pandas as pd
3+
from datetime import datetime
4+
5+
class CrowdPreprocessor:
6+
def __init__(self):
7+
self.df = None
8+
9+
def safe_float_convert(self, value, default=0.0):
10+
try:
11+
return float(value)
12+
except (ValueError, TypeError):
13+
return default
14+
15+
def process_json(self, json_data):
16+
try:
17+
ppltn = json_data['CITYDATA']['LIVE_PPLTN_STTS'][0]
18+
road = json_data['CITYDATA']['ROAD_TRAFFIC_STTS']['AVG_ROAD_DATA']
19+
weather = json_data['CITYDATA']['WEATHER_STTS'][0]
20+
except (KeyError, IndexError, TypeError):
21+
return None
22+
23+
# 기본 데이터 추출
24+
data = {
25+
'AREA_NM': ppltn.get('AREA_NM'),
26+
'AREA_CONGEST_LVL': ppltn.get('AREA_CONGEST_LVL'),
27+
'ROAD_TRAFFIC_IDX': road.get('ROAD_TRAFFIC_IDX'),
28+
'PPLTN_RATE_20': self.safe_float_convert(ppltn.get('PPLTN_RATE_20')),
29+
'PPLTN_RATE_30': self.safe_float_convert(ppltn.get('PPLTN_RATE_30')),
30+
'PPLTN_RATE_40': self.safe_float_convert(ppltn.get('PPLTN_RATE_40')),
31+
'TEMP': self.safe_float_convert(weather.get('TEMP')),
32+
'HUMIDITY': self.safe_float_convert(weather.get('HUMIDITY')),
33+
'ROAD_TRAFFIC_SPD': self.safe_float_convert(road.get('ROAD_TRAFFIC_SPD')),
34+
'CALL_API_TIME': datetime.now()
35+
}
36+
37+
# 데이터프레임 생성
38+
self.df = pd.DataFrame([data])
39+
40+
# 시간 특성 생성
41+
self.df['hour'] = self.df['CALL_API_TIME'].dt.hour
42+
self.df['day_of_week'] = self.df['CALL_API_TIME'].dt.dayofweek
43+
self.df['is_rush_hour'] = ((self.df['hour'].between(7, 9)) |
44+
(self.df['hour'].between(17, 19))).astype(int)
45+
46+
return self.df
47+
48+
# 사용 예시
49+
if __name__ == "__main__":
50+
# JSON 파일 읽기
51+
with open('서울대공원_20250520_000003.json', 'r', encoding='utf-8') as f:
52+
json_data = json.load(f)
53+
54+
# 데이터 처리
55+
preprocessor = CrowdPreprocessor()
56+
processed_data = preprocessor.process_json(json_data)
57+
print(processed_data)

0 commit comments

Comments
 (0)