diff --git a/docs/superpowers/specs/2026-04-05-realestate-lab-design.md b/docs/superpowers/specs/2026-04-05-realestate-lab-design.md new file mode 100644 index 0000000..4d49db9 --- /dev/null +++ b/docs/superpowers/specs/2026-04-05-realestate-lab-design.md @@ -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 (알림 모듈 분리 구조 대비) +- **경쟁률 조회**: 청약 접수 기간 중 경쟁률 실시간 수집 +- **실거래가 비교**: 주변 시세와 분양가 비교 분석