add region convert module

master
jaehwang 2025-08-19 13:51:16 +09:00
parent d3371fd7b6
commit 5bf283c5d4
9 changed files with 243 additions and 23 deletions

View File

@ -6,7 +6,13 @@ FastAPI와 OpenAI를 활용한 부동산 자연어 검색 웹 애플리케이션
- 자연어로 부동산 조건 입력
- OpenAI API를 통한 자동 정보 추출
- 가격, 위치, 면적, 방 수, 거래 유형 파싱
- 추출 정보:
- 매물 형태 (아파트, 오피스텔, 빌라, 주택 등)
- 거래 유형 (전세, 월세, 매매)
- 가격
- 위치
- 면적
- 방 개수
## 설치
@ -32,6 +38,8 @@ python main.py
브라우저에서 http://localhost:20001 접속
## 입력 예시
- "강남역 근처 전세 2억 이하 투룸"
- "서초동 30평대 아파트 매매 10억 이하"
- "판교 방 3개 월세 100/50"
- "강남역 근처 아파트 전세 2억 이하 투룸"
- "서초동 30평대 오피스텔 매매 10억 이하"
- "판교 빌라 방 3개 월세 100/50"
- "잠실 주상복합 전세 5억"
- "분당 단독주택 매매 20억"

View File

@ -8,6 +8,7 @@ import uvicorn
from models import RealEstateQuery, ParsedRealEstate
from openai_parser import OpenAIParser
from region_converter import RegionCodeConverter
load_dotenv()
@ -29,6 +30,9 @@ except ValueError as e:
print(f"Warning: {e}")
parser = None
# 지역 코드 변환기 초기화
region_converter = RegionCodeConverter()
@app.get("/")
async def serve_index():
"""메인 페이지 제공"""
@ -45,7 +49,13 @@ async def parse_real_estate(query: RealEstateQuery):
try:
result = await parser.parse_real_estate_query(query.text)
print(result)
# 위치 정보를 시군구 코드로 변환
if result.location:
region_code, region_name = region_converter.get_region_code(result.location)
result.region_code = region_code
result.region_name = region_name
return result
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))

View File

@ -10,6 +10,9 @@ class ParsedRealEstate(BaseModel):
price: Optional[str] = None
location: Optional[str] = None
area: Optional[str] = None
rooms: Optional[str] = None
rooms: Optional[int] = None
transaction_type: Optional[str] = None # 전세, 월세, 매매
property_type: Optional[str] = None # 아파트, 오피스텔, 주택, 빌라 등
region_code: Optional[str] = None # 시군구 코드 (5자리)
region_name: Optional[str] = None # 매칭된 정식 지역명
raw_text: str

View File

@ -20,16 +20,17 @@ class OpenAIParser:
system_prompt = """
당신은 부동산 정보를 추출하는 전문가입니다.
사용자의 자연어 입력에서 다음 정보를 추출하세요.
단위는 붙여서 표기하세요. :
1. price: 가격 (전세금, 월세, 매매가 ) (string)
2. location: 위치 (지역명, , ) (string)
3. area: 면적 (평수, 제곱미터) (string)
4. rooms: 개수 (string)
5. transaction_type: 거래 유형 (전세, 월세, 매매) (string)
사용자의 자연어 입력에서 다음 정보를 추출하세요:
1. price: 가격 (전세금, 월세, 매매가 )
2. location: 위치 (지역명, , )
3. area: 면적 (평수, 제곱미터 )
4. rooms: 개수 (숫자만)
5. transaction_type: 거래 유형 (전세, 월세, 매매)
6. property_type: 매물 형태 (아파트, 오피스텔, 주택, 빌라, 원룸, 투룸, 쓰리룸, 단독주택, 다가구주택, 연립주택, 상가주택 )
JSON 형식으로만 응답하세요.
정보가 없는 항목은 null로 표시하세요.
rooms는 숫자(integer)로만 표현하세요.
"""
try:
@ -44,7 +45,6 @@ class OpenAIParser:
)
result = json.loads(response.choices[0].message.content)
print(result)
return ParsedRealEstate(
price=result.get("price"),
@ -52,11 +52,13 @@ class OpenAIParser:
area=result.get("area"),
rooms=result.get("rooms"),
transaction_type=result.get("transaction_type"),
property_type=result.get("property_type"),
region_code=None, # 메인 서버에서 변환
region_name=None, # 메인 서버에서 변환
raw_text=text
)
except Exception as e:
print(e)
# 에러 발생 시 기본값 반환
return ParsedRealEstate(
raw_text=text,
@ -64,5 +66,8 @@ class OpenAIParser:
location=None,
area=None,
rooms=None,
transaction_type=None
transaction_type=None,
property_type=None,
region_code=None,
region_name=None
)

124
backend/region_converter.py Normal file
View File

@ -0,0 +1,124 @@
import json
import os
from typing import Optional, Tuple
class RegionCodeConverter:
"""위치 정보를 시군구 코드로 변환하는 유틸리티"""
def __init__(self):
"""지역 코드 데이터 로드"""
base_path = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
# 전체 지역 코드 로드
with open(os.path.join(base_path, 'data', 'region_codes.json'), 'r', encoding='utf-8') as f:
self.full_codes = json.load(f)
# 간략 지역 코드 로드
with open(os.path.join(base_path, 'data', 'region_codes_simple.json'), 'r', encoding='utf-8') as f:
self.simple_codes = json.load(f)
def get_region_code(self, location: str) -> Tuple[Optional[str], Optional[str]]:
"""
위치 정보를 시군구 코드로 변환
Args:
location: 위치 텍스트 (: "강남구", "서울 강남", "서울특별시 강남구")
Returns:
(지역코드, 매칭된 지역명) 튜플
"""
if not location:
return None, None
location = location.strip()
# 1. 정확한 매칭 시도 (전체 이름)
for full_name, code in self.full_codes.items():
if location in full_name or full_name in location:
return code, full_name
# 2. 간략 이름으로 매칭 시도
for simple_name, code in self.simple_codes.items():
if simple_name in location:
# 전체 이름 찾기
for full_name, full_code in self.full_codes.items():
if full_code == code:
return code, full_name
return code, simple_name
# 3. 부분 매칭 시도 (구, 시, 군 단위)
location_parts = location.split()
for part in location_parts:
# 구/시/군으로 끝나는 부분 찾기
if part.endswith('') or part.endswith('') or part.endswith(''):
# 간략 코드에서 찾기
if part in self.simple_codes:
code = self.simple_codes[part]
# 전체 이름 찾기
for full_name, full_code in self.full_codes.items():
if full_code == code and part in full_name:
return code, full_name
return code, part
# 전체 코드에서 찾기
for full_name, code in self.full_codes.items():
if part in full_name:
return code, full_name
# 4. 동 이름으로 구 추정 (주요 동 매핑)
dong_to_gu = {
# 서울 강남구
'역삼': '강남구', '삼성': '강남구', '청담': '강남구', '논현': '강남구', '대치': '강남구',
'도곡': '강남구', '개포': '강남구', '일원': '강남구', '수서': '강남구', '세곡': '강남구',
# 서울 서초구
'반포': '서초구', '서초': '서초구', '방배': '서초구', '양재': '서초구', '잠원': '서초구',
'우면': '서초구', '내곡': '서초구',
# 서울 송파구
'잠실': '송파구', '신천': '송파구', '석촌': '송파구', '송파': '송파구', '가락': '송파구',
'문정': '송파구', '장지': '송파구', '방이': '송파구', '오금': '송파구', '풍납': '송파구',
# 서울 강동구
'천호': '강동구', '성내': '강동구', '길동': '강동구', '둔촌': '강동구', '암사': '강동구',
'명일': '강동구', '고덕': '강동구', '상일': '강동구',
# 서울 노원구
'상계': '노원구', '중계': '노원구', '하계': '노원구', '월계': '노원구', '공릉': '노원구',
# 서울 양천구
'목동': '양천구', '신정': '양천구', '신월': '양천구',
# 서울 영등포구
'여의도': '영등포구', '당산': '영등포구', '영등포': '영등포구', '문래': '영등포구',
'양평': '영등포구', '신길': '영등포구', '대림': '영등포구',
# 서울 마포구
'홍대': '마포구', '합정': '마포구', '상수': '마포구', '망원': '마포구', '연남': '마포구',
'서교': '마포구', '공덕': '마포구', '아현': '마포구', '도화': '마포구', '용강': '마포구',
# 서울 성동구
'성수': '성동구', '왕십리': '성동구', '행당': '성동구', '금호': '성동구', '옥수': '성동구',
# 서울 용산구
'이태원': '용산구', '한남': '용산구', '동빙고': '용산구', '서빙고': '용산구', '이촌': '용산구',
'한강로': '용산구', '효창': '용산구', '용문': '용산구',
# 서울 종로구
'광화문': '종로구', '종로': '종로구', '인사동': '종로구', '삼청': '종로구', '평창': '종로구',
'부암': '종로구', '무악': '종로구', '교남': '종로구',
# 서울 중구
'명동': '중구', '충무로': '중구', '을지로': '중구', '남대문': '중구', '회현': '중구',
'필동': '중구', '장충': '중구', '신당': '중구', '다산': '중구',
# 경기도 성남시 분당구
'판교': '분당구', '정자': '분당구', '서현': '분당구', '수내': '분당구', '야탑': '분당구',
'이매': '분당구', '분당': '분당구', '구미': '분당구', '금곡': '분당구',
# 경기도 용인시
'동백': '기흥구', '보정': '기흥구', '죽전': '수지구', '수지': '수지구',
# 경기도 수원시
'영통': '영통구', '광교': '영통구', '원천': '영통구', '매탄': '영통구',
# 경기도 고양시
'일산': '일산동구', '백석': '일산동구', '마두': '일산서구', '주엽': '일산서구'
}
for dong, gu in dong_to_gu.items():
if dong in location:
if gu in self.simple_codes:
code = self.simple_codes[gu]
# 전체 이름 찾기
for full_name, full_code in self.full_codes.items():
if full_code == code:
return code, full_name
return code, gu
return None, None

View File

@ -47,6 +47,50 @@
- docs/project_plan.md 업데이트 (API 가이드 문서 추가)
- 웹 검색 및 브라우저를 통한 실제 API 정보 수집
[2025-08-19 13:40:37] 위치 정보를 시군구 코드로 변환하는 기능 추가 완료
- backend/region_converter.py 생성
- RegionCodeConverter 클래스 구현
- 전체 지역명, 간략 지역명, 동 이름 매칭
- 100개 이상의 주요 동 이름 매핑
- backend/models.py 수정
- region_code, region_name 필드 추가
- backend/main.py 수정
- 지역 코드 변환기 적용
- OpenAI 파싱 후 자동 변환
- backend/openai_parser.py 수정
- 새 필드 추가
- frontend/script.js 수정
- 지역 코드 표시 기능 추가
- frontend/style.css 수정
- 지역 코드 스타일 추가
- 변환 우선순위:
1. 정확한 전체 이름 매칭
2. 간략 이름 매칭
3. 구/시/군 단위 부분 매칭
4. 동 이름으로 구 추정
[2025-08-19 13:36:33] 위치 정보를 시군구 코드로 변환하는 기능 추가 시작
- 위치 텍스트를 행정표준코드로 변환
- region_codes.json 파일 활용
[2025-08-19 13:30:01] 매물 형태 필드 추가 작업 완료
- backend/models.py: property_type 필드 추가
- backend/openai_parser.py:
- 시스템 프롬프트에 property_type 추가
- 아파트, 오피스텔, 빌라, 주택 등 구분
- rooms 타입을 integer로 수정
- frontend/script.js: 결과 표시에 매물 형태 추가
- frontend/index.html: 예시 텍스트에 매물 형태 포함
- README.md: 기능 설명 및 예시 업데이트
- 추출 가능한 매물 형태:
- 아파트, 오피스텔, 빌라, 원룸, 투룸
- 단독주택, 다가구주택, 연립주택
- 주상복합, 상가주택 등
[2025-08-19 13:27:22] 매물 형태 필드 추가 작업 시작
- 분석 결과에 property_type 필드 추가
- 아파트, 오피스텔, 주택, 빌라 등 구분
[2025-08-19 13:15:44] 한국 시군구 코드 JSON 파일 생성 작업 완료
- data 폴더 생성
- data/region_codes.json 생성 (전체 시군구 코드)

View File

@ -15,18 +15,21 @@
<main>
<div class="search-section">
<textarea id="searchInput" placeholder="예: 2
서초동 30평대 아파트 매매 10억 이하
방 3개짜리 월세 100/50 이하 판교"></textarea>
<textarea id="searchInput" placeholder="예: 2
서초동 30평대 오피스텔 매매 10억 이하
판교 빌라 방 3개 월세 100/50
역삼동 원룸 오피스텔 전세 1억5천"></textarea>
<button id="searchBtn" onclick="parseQuery()">분석하기</button>
</div>
<div class="examples">
<h3>입력 예시:</h3>
<div class="example-chips">
<span class="chip" onclick="setExample(this)">강남 전세 3억 방 2개</span>
<span class="chip" onclick="setExample(this)">판교 30평 아파트 매매</span>
<span class="chip" onclick="setExample(this)">서초동 월세 200/100</span>
<span class="chip" onclick="setExample(this)">강남 아파트 전세 3억 방 2개</span>
<span class="chip" onclick="setExample(this)">판교 30평 오피스텔 매매</span>
<span class="chip" onclick="setExample(this)">서초동 빌라 월세 200/100</span>
<span class="chip" onclick="setExample(this)">잠실 주상복합 전세 5억</span>
<span class="chip" onclick="setExample(this)">분당 단독주택 매매 20억</span>
</div>
</div>

View File

@ -48,6 +48,12 @@ function displayResults(data) {
let html = '';
if (data.property_type) {
html += `<div class="result-item">
<strong>매물 형태:</strong> ${data.property_type}
</div>`;
}
if (data.transaction_type) {
html += `<div class="result-item">
<strong>거래 유형:</strong> ${data.transaction_type}
@ -64,6 +70,14 @@ function displayResults(data) {
html += `<div class="result-item">
<strong>위치:</strong> ${data.location}
</div>`;
// 지역 코드가 있으면 표시
if (data.region_code) {
html += `<div class="result-item">
<strong>지역 코드:</strong> ${data.region_code}
${data.region_name ? ` (${data.region_name})` : ''}
</div>`;
}
}
if (data.area) {
@ -74,7 +88,7 @@ function displayResults(data) {
if (data.rooms) {
html += `<div class="result-item">
<strong> 개수:</strong> ${data.rooms}
<strong> 개수:</strong> ${data.rooms}
</div>`;
}

View File

@ -131,6 +131,15 @@ header p {
width: 100px;
}
.result-item .code-badge {
background: #f0f0f0;
padding: 2px 8px;
border-radius: 4px;
font-family: monospace;
font-size: 0.9em;
margin-left: 10px;
}
.loading {
text-align: center;
padding: 40px;