diff --git a/README.md b/README.md index 3790a8c..cfaf5bd 100644 --- a/README.md +++ b/README.md @@ -8,11 +8,13 @@ FastAPI와 OpenAI를 활용한 부동산 실거래가 검색 웹 애플리케이 - OpenAI API를 통한 자동 정보 추출 - 국토교통부 공공데이터 API 연동 - 실제 실거래가 데이터 조회 및 표시 +- **AI 기반 매물 추천**: 사용자 요구사항과 가장 일치하는 TOP 10 매물 선별 - 지원 정보: - 매물 형태 (아파트, 오피스텔, 빌라, 주택 등) - 거래 유형 (전세, 월세, 매매) - 지역 자동 인식 및 코드 변환 - 실거래가 목록 표시 + - AI 필터링을 통한 최적 매물 추천 ## 설치 diff --git a/backend/main.py b/backend/main.py index 789bee6..b3f58af 100644 --- a/backend/main.py +++ b/backend/main.py @@ -39,7 +39,7 @@ async def serve_index(): return FileResponse("../frontend/index.html") @app.post("/api/search") -async def search_real_estate(query: RealEstateQuery): +async def search_real_estate(query: RealEstateQuery, filter_results: bool = True): """자연어 검색 후 실거래가 데이터 조회""" if not parser: raise HTTPException( @@ -60,10 +60,55 @@ async def search_real_estate(query: RealEstateQuery): region_code=parsed.region_code ) + # 3. OpenAI로 필터링 (옵션) + if filter_results and listings: + filtered_listings = await parser.filter_listings( + user_query=query.text, + listings=listings, + top_k=10 + ) + return { + "parsed": parsed, + "listings": filtered_listings, + "count": len(filtered_listings), + "total_count": len(listings), + "filtered": True + } + return { "parsed": parsed, - "listings": listings, - "count": len(listings) + "listings": listings[:20], # 필터링 안 할 경우 상위 20개만 + "count": min(len(listings), 20), + "total_count": len(listings), + "filtered": False + } + except Exception as e: + raise HTTPException(status_code=500, detail=str(e)) + +@app.post("/api/filter") +async def filter_real_estate_listings( + user_query: str, + listings: list, + top_k: int = 10 +): + """공공데이터 결과를 OpenAI로 필터링하여 최적 매물 선별""" + if not parser: + raise HTTPException( + status_code=500, + detail="OpenAI API key not configured" + ) + + try: + filtered = await parser.filter_listings( + user_query=user_query, + listings=listings, + top_k=top_k + ) + + return { + "filtered_listings": filtered, + "count": len(filtered), + "original_count": len(listings) } except Exception as e: raise HTTPException(status_code=500, detail=str(e)) diff --git a/backend/openai_parser.py b/backend/openai_parser.py index 75a07a7..4969845 100644 --- a/backend/openai_parser.py +++ b/backend/openai_parser.py @@ -136,4 +136,97 @@ class OpenAIParser: property_type=None, region_code=None, region_name=None - ) \ No newline at end of file + ) + + async def filter_listings(self, user_query: str, listings: list, top_k: int = 10) -> list: + """ + OpenAI를 사용하여 실거래가 목록에서 사용자 요구사항과 가장 일치하는 매물 선별 + + Args: + user_query: 사용자의 원본 검색 쿼리 + listings: 공공데이터 API에서 받은 실거래가 목록 + top_k: 선별할 매물 개수 (기본값: 10) + + Returns: + 필터링된 매물 목록 + """ + + if not listings: + return [] + + # 매물 정보를 간략하게 정리 + simplified_listings = [] + for idx, item in enumerate(listings[:50]): # 최대 50개만 처리 (토큰 제한) + simplified = { + "index": idx, + "거래유형": item.get("거래유형", ""), + "매물형태": item.get("매물형태", ""), + "거래금액": item.get("거래금액", ""), + "보증금액": item.get("보증금액", ""), + "월세금액": item.get("월세금액", ""), + "전용면적": item.get("전용면적", ""), + "층": item.get("층", ""), + "건축년도": item.get("건축년도", ""), + "법정동": item.get("법정동", ""), + "거래일": f"{item.get('년', '')}.{item.get('월', '')}.{item.get('일', '')}" + } + # 빈 값 제거 + simplified = {k: v for k, v in simplified.items() if v} + simplified_listings.append(simplified) + + system_prompt = """ + 당신은 부동산 전문가입니다. + 사용자의 요구사항과 실거래가 데이터를 비교하여 가장 적합한 매물을 선별해주세요. + + 평가 기준: + 1. 가격 조건 일치도 (사용자가 언급한 가격 범위) + 2. 면적 조건 일치도 (평수, 방 개수 등) + 3. 층수 선호도 + 4. 건축년도 (신축/구축 선호도) + 5. 위치 적합성 + + JSON 형식으로 응답하세요: + { + "selected_indices": [가장 적합한 매물의 index 10개], + "reason": "선별 이유 간단 설명" + } + """ + + user_prompt = f""" + 사용자 요구사항: {user_query} + + 실거래가 데이터: + {json.dumps(simplified_listings, ensure_ascii=False, indent=2)} + + 위 데이터에서 사용자 요구사항과 가장 잘 맞는 매물 {top_k}개를 선별해주세요. + """ + + try: + response = self.client.chat.completions.create( + model="gpt-3.5-turbo", + messages=[ + {"role": "system", "content": system_prompt}, + {"role": "user", "content": user_prompt} + ], + temperature=0.1, + response_format={"type": "json_object"} + ) + + result = json.loads(response.choices[0].message.content) + selected_indices = result.get("selected_indices", []) + + # 선별된 매물 반환 + filtered_listings = [] + for idx in selected_indices: + if 0 <= idx < len(listings): + listing = listings[idx].copy() + listing["match_reason"] = result.get("reason", "") + listing["rank"] = len(filtered_listings) + 1 + filtered_listings.append(listing) + + return filtered_listings[:top_k] + + except Exception as e: + print(f"Error filtering listings: {e}") + # 에러 시 원본 데이터의 상위 10개 반환 + return listings[:top_k] \ No newline at end of file diff --git a/docs/project_logs.txt b/docs/project_logs.txt index 62fe61e..d4ad0c6 100644 --- a/docs/project_logs.txt +++ b/docs/project_logs.txt @@ -47,6 +47,37 @@ - docs/project_plan.md 업데이트 (API 가이드 문서 추가) - 웹 검색 및 브라우저를 통한 실제 API 정보 수집 +[2025-08-19 15:22:10] OpenAI 기반 매물 필터링 기능 추가 완료 +- backend/openai_parser.py 수정 + - filter_listings 메서드 추가 + - 공공데이터 결과를 OpenAI로 분석 + - 사용자 요구사항과 일치도 평가 + - 가격, 면적, 층수, 건축년도 등 종합 평가 + - 상위 10개 매물 선별 +- backend/main.py 수정 + - /api/search에 filter_results 파라미터 추가 + - 필터링 옵션 적용 (기본값: True) + - /api/filter 엔드포인트 추가 (별도 필터링 API) +- frontend/script.js 수정 + - 필터링 결과 표시 UI 개선 + - AI 추천 매물 TOP 10 표시 + - 순위 배지 추가 +- frontend/style.css 수정 + - 필터링 정보 스타일 추가 + - 순위 배지 디자인 + - 추천 매물 강조 효과 +- README.md 및 문서 업데이트 +- 필터링 평가 기준: + - 가격 조건 일치도 + - 면적 조건 일치도 + - 층수 선호도 + - 건축년도 (신축/구축) + - 위치 적합성 + +[2025-08-19 15:17:59] OpenAI 기반 매물 필터링 기능 추가 시작 +- 공공데이터 결과를 OpenAI로 분석 +- 사용자 요구사항과 가장 일치하는 10개 선별 + [2025-08-19 15:12:44] 사용하지 않는 API 엔드포인트 제거 작업 완료 - backend/main.py 정리 - /api/parse 엔드포인트 제거 (사용 안함) diff --git a/docs/project_plan.md b/docs/project_plan.md index a14e7fc..ab15cb7 100644 --- a/docs/project_plan.md +++ b/docs/project_plan.md @@ -52,8 +52,14 @@ C:\o2o\RealEstateSearch\ ### 2. POST `/api/search` - 자연어 검색 및 실거래가 조회 -- 요청: `{"text": "강남 아파트 전세"}` -- 응답: 파싱 결과 + 실거래가 목록 +- AI 필터링 옵션 (filter_results 파라미터) +- 요청: `{"text": "강남 아파트 전세 3억"}` +- 응답: 파싱 결과 + 실거래가 목록 (필터링 포함) -### 3. GET `/static/*` +### 3. POST `/api/filter` +- 공공데이터 결과를 OpenAI로 필터링 +- 사용자 요구사항과 가장 일치하는 매물 선별 +- 파라미터: user_query, listings, top_k + +### 4. GET `/static/*` - CSS, JS 등 정적 파일 제공 diff --git a/frontend/script.js b/frontend/script.js index 16839eb..4708233 100644 --- a/frontend/script.js +++ b/frontend/script.js @@ -75,18 +75,25 @@ function displayResults(data) { // 실거래가 목록 if (data.listings && data.listings.length > 0) { html += '
AI가 요구사항과 가장 일치하는 매물을 선별했습니다.
'; + } else { + html += `... 외 ${data.listings.length - 20}건
`; + html += '... 외 ${data.total_count - data.count}건
`; } html += '