docs: realestate-lab 설계 스펙 문서 추가
청약 공고 자동 수집 + 프로필 기반 자격 매칭 서비스 설계. 공공데이터포털 API 연동, 독립 서비스 분리, 매칭 엔진 정의. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
342
docs/superpowers/specs/2026-04-05-realestate-lab-design.md
Normal file
342
docs/superpowers/specs/2026-04-05-realestate-lab-design.md
Normal file
@@ -0,0 +1,342 @@
|
||||
# realestate-lab 설계 스펙
|
||||
|
||||
> 부동산 청약 공고 자동 수집 + 프로필 기반 자격 매칭 서비스
|
||||
|
||||
---
|
||||
|
||||
## 1. 개요
|
||||
|
||||
공공데이터포털(한국부동산원 청약홈 분양정보 API)에서 청약 공고를 자동 수집하고, 사용자 프로필 기반으로 지원 가능 여부를 자동 판별하는 독립 서비스.
|
||||
|
||||
**핵심 목표:**
|
||||
- 수동 공고 등록 없이 자동 수집 → DB 저장
|
||||
- 프로필 기반 자격 매칭 → 지원 가능한 청약만 필터링
|
||||
- 프론트에서 "새 공고 N건" 확인 → 향후 텔레그램 알림 확장
|
||||
|
||||
---
|
||||
|
||||
## 2. 서비스 아키텍처
|
||||
|
||||
### 독립 서비스 구조
|
||||
|
||||
```
|
||||
realestate-lab/ # 포트 18800
|
||||
├── app/
|
||||
│ ├── main.py # FastAPI 앱 + APScheduler
|
||||
│ ├── db.py # SQLite CRUD (realestate.db)
|
||||
│ ├── collector.py # 공공데이터포털 API 수집기
|
||||
│ ├── matcher.py # 프로필 기반 자격 매칭 엔진
|
||||
│ └── models.py # Pydantic 요청/응답 모델
|
||||
├── Dockerfile
|
||||
└── requirements.txt
|
||||
```
|
||||
|
||||
### 수집 흐름
|
||||
|
||||
```
|
||||
APScheduler (매일 09:00)
|
||||
→ collector.py: 청약홈 API 5개 엔드포인트 호출
|
||||
→ DB에 신규 공고 upsert (HOUSE_MANAGE_NO + PBLANC_NO 기준)
|
||||
→ matcher.py: 프로필 매칭 → 적격 공고에 match_status 부여
|
||||
→ 신규 매칭 공고 카운트 → (향후) 텔레그램 알림
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. 데이터 소스
|
||||
|
||||
### 공공데이터포털 — 한국부동산원_청약홈 분양정보 조회 서비스
|
||||
|
||||
- **Base URL**: `https://api.odcloud.kr/api`
|
||||
- **서비스 키**: `DATA_GO_KR_API_KEY` 환경변수
|
||||
- **일 호출 제한**: 40,000건
|
||||
- **데이터 포맷**: JSON
|
||||
|
||||
### 수집 대상 API 엔드포인트
|
||||
|
||||
| 엔드포인트 | 설명 |
|
||||
|-----------|------|
|
||||
| `/ApplyhomeInfoDetailSvc/v1/getAPTLttotPblancDetail` | APT 분양정보 상세 |
|
||||
| `/ApplyhomeInfoDetailSvc/v1/getUrbtyOfctlLttotPblancDetail` | 오피스텔/도시형/민간임대 상세 |
|
||||
| `/ApplyhomeInfoDetailSvc/v1/getRemndrLttotPblancDetail` | 잔여세대 상세 |
|
||||
| `/ApplyhomeInfoDetailSvc/v1/getPblPvtRentLttotPblancDetail` | 공공지원 민간임대 상세 |
|
||||
| `/ApplyhomeInfoDetailSvc/v1/getOPTLttotPblancDetail` | 임의공급 상세 |
|
||||
|
||||
### 주택형별 상세 API (모델별 세대수·분양가)
|
||||
|
||||
| 엔드포인트 | 설명 |
|
||||
|-----------|------|
|
||||
| `/ApplyhomeInfoDetailSvc/v1/getAPTLttotPblancMdl` | APT 주택형별 |
|
||||
| `/ApplyhomeInfoDetailSvc/v1/getUrbtyOfctlLttotPblancMdl` | 오피스텔 주택형별 |
|
||||
| `/ApplyhomeInfoDetailSvc/v1/getRemndrLttotPblancMdl` | 잔여세대 주택형별 |
|
||||
| `/ApplyhomeInfoDetailSvc/v1/getPblPvtRentLttotPblancMdl` | 공공지원 민간임대 주택형별 |
|
||||
| `/ApplyhomeInfoDetailSvc/v1/getOPTLttotPblancMdl` | 임의공급 주택형별 |
|
||||
|
||||
### 공통 쿼리 파라미터
|
||||
|
||||
- `page` (기본: 1), `perPage` (기본: 100)
|
||||
- `serviceKey` — 인코딩된 API 키
|
||||
- `cond[RCRIT_PBLANC_DE::GTE]` / `cond[RCRIT_PBLANC_DE::LTE]` — 모집공고일 범위 필터
|
||||
|
||||
---
|
||||
|
||||
## 4. DB 스키마 (realestate.db)
|
||||
|
||||
### announcements (청약 공고)
|
||||
|
||||
| 컬럼 | 타입 | 설명 |
|
||||
|------|------|------|
|
||||
| id | INTEGER PK | 자동 증가 |
|
||||
| house_manage_no | TEXT NOT NULL | 주택관리번호 |
|
||||
| pblanc_no | TEXT NOT NULL | 공고번호 |
|
||||
| house_nm | TEXT | 주택명 |
|
||||
| house_secd | TEXT | 주택구분코드 (01:APT, 02:오피스텔, 04:무순위 등) |
|
||||
| house_dtl_secd | TEXT | 주택상세구분코드 (01:민영, 03:국민 등) |
|
||||
| rent_secd | TEXT | 분양구분 (0:분양, 1:임대) |
|
||||
| region_code | TEXT | 공급지역코드 |
|
||||
| region_name | TEXT | 공급지역명 |
|
||||
| address | TEXT | 공급위치 |
|
||||
| total_units | INTEGER | 공급규모 |
|
||||
| rcrit_date | TEXT | 모집공고일 |
|
||||
| receipt_start | TEXT | 청약접수시작일 |
|
||||
| receipt_end | TEXT | 청약접수종료일 |
|
||||
| spsply_start | TEXT | 특별공급 접수시작일 |
|
||||
| spsply_end | TEXT | 특별공급 접수종료일 |
|
||||
| gnrl_rank1_start | TEXT | 1순위 접수시작일 |
|
||||
| gnrl_rank1_end | TEXT | 1순위 접수종료일 |
|
||||
| winner_date | TEXT | 당첨자발표일 |
|
||||
| contract_start | TEXT | 계약시작일 |
|
||||
| contract_end | TEXT | 계약종료일 |
|
||||
| homepage_url | TEXT | 홈페이지 |
|
||||
| pblanc_url | TEXT | 공고 URL |
|
||||
| constructor | TEXT | 시공사 |
|
||||
| developer | TEXT | 시행사 |
|
||||
| move_in_month | TEXT | 입주예정월 |
|
||||
| is_speculative_area | TEXT | 투기과열지구 |
|
||||
| is_price_cap | TEXT | 분양가상한제 |
|
||||
| contact | TEXT | 문의처 |
|
||||
| status | TEXT | 청약예정/청약중/결과발표/완료 (자동 계산) |
|
||||
| source | TEXT | auto/manual |
|
||||
| created_at | TEXT | |
|
||||
| updated_at | TEXT | |
|
||||
|
||||
- UNIQUE 제약: `(house_manage_no, pblanc_no)`
|
||||
- INDEX: `idx_realestate_status` on `status`
|
||||
- INDEX: `idx_realestate_region` on `region_name`
|
||||
|
||||
### announcement_models (주택형별 상세)
|
||||
|
||||
| 컬럼 | 타입 | 설명 |
|
||||
|------|------|------|
|
||||
| id | INTEGER PK | |
|
||||
| house_manage_no | TEXT | FK → announcements |
|
||||
| pblanc_no | TEXT | FK → announcements |
|
||||
| model_no | TEXT | 모델번호 |
|
||||
| house_ty | TEXT | 주택형 (84A 등) |
|
||||
| supply_area | REAL | 공급면적(㎡) |
|
||||
| general_units | INTEGER | 일반공급 세대수 |
|
||||
| special_units | INTEGER | 특별공급 세대수 |
|
||||
| multi_child_units | INTEGER | 다자녀 |
|
||||
| newlywed_units | INTEGER | 신혼부부 |
|
||||
| first_life_units | INTEGER | 생애최초 |
|
||||
| old_parent_units | INTEGER | 노부모부양 |
|
||||
| institution_units | INTEGER | 기관추천 |
|
||||
| youth_units | INTEGER | 청년 |
|
||||
| newborn_units | INTEGER | 신생아 |
|
||||
| top_amount | INTEGER | 분양최고금액(만원) |
|
||||
|
||||
- UNIQUE: `(house_manage_no, pblanc_no, model_no)`
|
||||
|
||||
### user_profile (사용자 청약 프로필)
|
||||
|
||||
| 컬럼 | 타입 | 설명 |
|
||||
|------|------|------|
|
||||
| id | INTEGER PK | 항상 1 (단일 사용자) |
|
||||
| name | TEXT | 이름 |
|
||||
| age | INTEGER | 나이 |
|
||||
| is_homeless | BOOLEAN | 무주택 여부 |
|
||||
| is_householder | BOOLEAN | 세대주 여부 |
|
||||
| subscription_months | INTEGER | 청약통장 가입개월수 |
|
||||
| subscription_amount | INTEGER | 청약통장 납입총액(만원) |
|
||||
| family_members | INTEGER | 세대원 수 |
|
||||
| has_dependents | BOOLEAN | 부양가족 유무 |
|
||||
| children_count | INTEGER | 미성년 자녀수 |
|
||||
| is_newlywed | BOOLEAN | 신혼부부 여부 |
|
||||
| marriage_months | INTEGER | 혼인기간(개월) |
|
||||
| has_newborn | BOOLEAN | 2세 이하 자녀 유무 |
|
||||
| is_first_home | BOOLEAN | 생애최초 해당 여부 |
|
||||
| income_level | TEXT | 소득수준 (100%이하/100~130%/130~160%) |
|
||||
| preferred_regions | TEXT | 관심지역 JSON 배열 |
|
||||
| preferred_types | TEXT | 관심주택유형 JSON 배열 |
|
||||
| min_area | REAL | 최소 희망면적(㎡) |
|
||||
| max_area | REAL | 최대 희망면적(㎡) |
|
||||
| max_price | INTEGER | 최대 분양가(만원) |
|
||||
| updated_at | TEXT | |
|
||||
|
||||
### match_results (매칭 결과)
|
||||
|
||||
| 컬럼 | 타입 | 설명 |
|
||||
|------|------|------|
|
||||
| id | INTEGER PK | |
|
||||
| announcement_id | INTEGER | FK → announcements |
|
||||
| model_id | INTEGER | FK → announcement_models (nullable) |
|
||||
| match_score | INTEGER | 매칭 점수 (0~100) |
|
||||
| match_reasons | TEXT | 매칭 사유 JSON 배열 |
|
||||
| eligible_types | TEXT | 지원 가능 유형 JSON 배열 |
|
||||
| is_new | BOOLEAN | 신규 매칭 여부 (알림용) |
|
||||
| created_at | TEXT | |
|
||||
|
||||
- UNIQUE: `(announcement_id, model_id)`
|
||||
|
||||
---
|
||||
|
||||
## 5. API 엔드포인트
|
||||
|
||||
### 청약 공고
|
||||
|
||||
| 메서드 | 경로 | 설명 |
|
||||
|--------|------|------|
|
||||
| GET | `/api/realestate/announcements` | 공고 목록 (필터: region, status, house_type, matched_only, sort, page, size) |
|
||||
| GET | `/api/realestate/announcements/{id}` | 공고 상세 (주택형별 포함) |
|
||||
| POST | `/api/realestate/announcements` | 수동 공고 등록 |
|
||||
| PUT | `/api/realestate/announcements/{id}` | 공고 수정 |
|
||||
| DELETE | `/api/realestate/announcements/{id}` | 공고 삭제 |
|
||||
|
||||
### 수집 관리
|
||||
|
||||
| 메서드 | 경로 | 설명 |
|
||||
|--------|------|------|
|
||||
| POST | `/api/realestate/collect` | 수동 수집 트리거 |
|
||||
| GET | `/api/realestate/collect/status` | 마지막 수집 결과 (수집일시, 신규건수, 에러) |
|
||||
|
||||
### 프로필
|
||||
|
||||
| 메서드 | 경로 | 설명 |
|
||||
|--------|------|------|
|
||||
| GET | `/api/realestate/profile` | 내 프로필 조회 |
|
||||
| PUT | `/api/realestate/profile` | 프로필 수정 (upsert) |
|
||||
|
||||
### 매칭
|
||||
|
||||
| 메서드 | 경로 | 설명 |
|
||||
|--------|------|------|
|
||||
| GET | `/api/realestate/matches` | 매칭 결과 목록 (점수순, 신규 우선) |
|
||||
| POST | `/api/realestate/matches/refresh` | 매칭 재계산 |
|
||||
| PATCH | `/api/realestate/matches/{id}/read` | 신규 알림 읽음 처리 |
|
||||
|
||||
### 대시보드
|
||||
|
||||
| 메서드 | 경로 | 설명 |
|
||||
|--------|------|------|
|
||||
| GET | `/api/realestate/dashboard` | 요약 (진행중 공고수, 신규 매칭수, 다가오는 일정) |
|
||||
|
||||
---
|
||||
|
||||
## 6. 매칭 엔진
|
||||
|
||||
### 점수 산출 (0~100)
|
||||
|
||||
| 기준 | 가중치 | 로직 |
|
||||
|------|--------|------|
|
||||
| 지역 매칭 | 30 | preferred_regions에 포함 → 30점 |
|
||||
| 주택유형 매칭 | 10 | preferred_types에 포함 → 10점 |
|
||||
| 면적 매칭 | 15 | min_area~max_area 범위 내 주택형 존재 → 15점 |
|
||||
| 가격 매칭 | 15 | max_price 이하 주택형 존재 → 15점 |
|
||||
| 자격 매칭 | 30 | 지원 가능 공급유형 수에 비례 |
|
||||
|
||||
### 자격 매칭 세부
|
||||
|
||||
| 공급유형 | 판별 조건 |
|
||||
|----------|----------|
|
||||
| 일반 1순위 | 무주택 + 세대주 + 청약통장 가입기간 충족 (투기과열 24개월, 그 외 12개월) |
|
||||
| 일반 2순위 | 1순위 미충족 시 |
|
||||
| 특별-신혼부부 | is_newlywed + 무주택 + 소득기준 |
|
||||
| 특별-생애최초 | is_first_home + 무주택 + 소득기준 |
|
||||
| 특별-다자녀 | children_count >= 2 + 무주택 |
|
||||
| 특별-노부모부양 | has_dependents + 무주택 |
|
||||
| 특별-청년 | age 19~39 + 무주택 |
|
||||
| 특별-신생아 | has_newborn + 무주택 |
|
||||
|
||||
- 1개 유형 → 10점, 2개 → 20점, 3개 이상 → 30점
|
||||
- `eligible_types`: 지원 가능 유형 목록 저장
|
||||
- `match_reasons`: 각 판별 사유 저장
|
||||
|
||||
### 상태 자동 계산
|
||||
|
||||
```
|
||||
오늘 < receipt_start → 청약예정
|
||||
receipt_start ≤ 오늘 ≤ receipt_end → 청약중
|
||||
receipt_end < 오늘 ≤ winner_date → 결과발표
|
||||
오늘 > winner_date → 완료
|
||||
```
|
||||
|
||||
### 매칭 실행 시점
|
||||
|
||||
- 신규 공고 수집 후 자동 실행
|
||||
- 프로필 변경 시 `POST /matches/refresh`로 재계산
|
||||
- 매일 00:00 상태 갱신 시 재매칭
|
||||
|
||||
---
|
||||
|
||||
## 7. 인프라 통합
|
||||
|
||||
### Docker Compose
|
||||
|
||||
```yaml
|
||||
realestate-lab:
|
||||
build: ./realestate-lab
|
||||
container_name: realestate-lab
|
||||
ports:
|
||||
- "18800:8000"
|
||||
volumes:
|
||||
- ${RUNTIME_PATH:-.}/data:/app/data
|
||||
environment:
|
||||
- DATA_GO_KR_API_KEY=${DATA_GO_KR_API_KEY}
|
||||
restart: unless-stopped
|
||||
```
|
||||
|
||||
### Nginx
|
||||
|
||||
```nginx
|
||||
location /api/realestate/ {
|
||||
proxy_pass http://realestate-lab:8000;
|
||||
}
|
||||
```
|
||||
|
||||
### APScheduler
|
||||
|
||||
| 시간 | Job | 설명 |
|
||||
|------|-----|------|
|
||||
| 매일 09:00 | `run_collection` | 5개 API 수집 → 매칭 |
|
||||
| 매일 00:00 | `update_statuses` | 날짜 기반 상태 갱신 |
|
||||
|
||||
### 배포
|
||||
|
||||
- `scripts/deploy-nas.sh`에 `realestate-lab/` rsync 대상 추가
|
||||
|
||||
---
|
||||
|
||||
## 8. lotto-backend 제거 대상
|
||||
|
||||
| 파일 | 제거 항목 |
|
||||
|------|----------|
|
||||
| `backend/app/db.py` | `realestate_complexes` 테이블 생성, CRUD 함수 5개 |
|
||||
| `backend/app/main.py` | `ComplexCreate`/`ComplexUpdate` 모델, `/api/realestate/complexes` 라우트 4개 |
|
||||
|
||||
기존 `realestate_complexes` 테이블 데이터는 마이그레이션 불필요 (스키마 완전 상이).
|
||||
|
||||
---
|
||||
|
||||
## 9. 환경변수
|
||||
|
||||
| 변수 | 설명 | 필수 |
|
||||
|------|------|------|
|
||||
| `DATA_GO_KR_API_KEY` | 공공데이터포털 API 키 | 선택 (미설정 시 수동 등록만 가능) |
|
||||
|
||||
---
|
||||
|
||||
## 10. 향후 확장
|
||||
|
||||
- **텔레그램 알림**: 신규 매칭 공고 발생 시 텔레그램 봇으로 push (알림 모듈 분리 구조 대비)
|
||||
- **경쟁률 조회**: 청약 접수 기간 중 경쟁률 실시간 수집
|
||||
- **실거래가 비교**: 주변 시세와 분양가 비교 분석
|
||||
Reference in New Issue
Block a user