# 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 (알림 모듈 분리 구조 대비) - **경쟁률 조회**: 청약 접수 기간 중 경쟁률 실시간 수집 - **실거래가 비교**: 주변 시세와 분양가 비교 분석