# 법원경매 물건 자동 분석·리포팅 시스템 (AX) 전체 아키텍처

**버전**: 1.0 / **작성일**: 2026-07-03
**목표**: 사람 개입 없이 매일 08:00, 경쟁력 있는 경매물건 상위 10건의 수익률·리스크 분석 보고서를 이메일로 자동 수신

---

## 1. 시스템 개요

본 시스템은 법원경매정보 사이트의 공개 데이터를 매일 자동 수집하고, 규칙 기반 필터링과 머신러닝 스코어링, LLM 기반 권리분석을 거쳐 상위 10건을 선정한 뒤, PDF 보고서를 생성하여 지정 시각에 이메일로 발송하는 완전 자동화 파이프라인이다.

전체 흐름은 다음 5단계로 구성된다.

```
[01:00] 수집(Crawler) → [03:00] 필터링/스코어링(Scorer) → [04:00] 권리분석(LLM)
      → [06:00] 수익률 계산(ROI Engine) → [07:30] 보고서 생성 → [08:00] 이메일 발송
```

각 단계는 독립 실행 가능한 Python 모듈로 분리되며, 단계 간 데이터 전달은 MySQL을 통해 이루어진다. 특정 단계가 실패해도 전날 데이터로 보고서를 생성하는 폴백(fallback) 로직을 두어 "매일 아침 반드시 메일이 온다"를 보장한다.

### 1.1 기술 스택

수집·분석은 Python 3.11 (requests, BeautifulSoup4/Playwright, pandas, XGBoost), 권리분석·보고서 문구 생성은 ChatGPT(OpenAI) API (JSON mode), 데이터 저장은 MySQL 8.0, 보고서는 HTML → PDF (WeasyPrint), 발송은 Python smtplib (또는 기존 SENDUS 인프라 재활용), 스케줄링은 Linux cron, 서버는 기존 Bicorn 서버 또는 별도 VPS 1대로 충분하다.

### 1.2 서버 요구사항

CPU 2코어 / RAM 4GB / 디스크 50GB 수준이면 충분하다. 일일 처리량이 전국 신건 기준 수백~수천 건이고 배치 처리라 부하가 낮다. Playwright 사용 시 RAM 4GB 이상을 권장한다.

---

## 2. 1단계 — 데이터 수집 (Crawler)

### 2.1 수집 대상

법원경매정보(www.courtauction.go.kr)에서 다음을 수집한다.

- **물건 기본정보**: 사건번호, 법원, 물건종류(아파트/오피스텔/토지 등), 소재지, 감정가, 최저매각가, 유찰횟수, 매각기일, 면적
- **매각물건명세서**: 임차인 현황, 점유관계, 특이사항 (권리분석의 핵심 입력)
- **현황조사서**: 점유자 정보, 부동산 현황
- **감정평가서 요약**: 감정평가액 산출 근거, 위치·환경 요인
- **사진**: 물건 대표 이미지 1~2장 (보고서 삽입용)

시세 비교를 위한 보조 데이터로 국토교통부 실거래가 공개시스템 OpenAPI(공식 API, 무료)를 병행 수집한다. 이는 수익률 계산의 기준가가 된다.

### 2.2 크롤러 설계

법원경매정보 사이트는 2024년 개편 이후 동적 렌더링(JS) 기반이므로 Playwright(headless Chromium)를 기본으로 하고, 내부 JSON API 엔드포인트가 확인되면 requests로 직접 호출하는 방식으로 전환한다(속도 10배 이상 개선).

안정 운영을 위한 필수 설계 요소는 다음과 같다.

- **요청 간격 제한**: 요청당 2~5초 랜덤 지연. 서버 부하 유발 시 IP 차단으로 시스템 전체가 중단되므로 절대적으로 준수
- **야간 실행**: 01:00~03:00 사이 실행으로 사이트 부하가 낮은 시간대 활용
- **증분 수집**: 사건번호 기준 신건 + 매각기일 변경건만 수집. 전체 재수집 금지
- **User-Agent 및 세션 관리**: 일반 브라우저와 동일한 헤더, 세션 쿠키 유지
- **재시도 로직**: 실패 시 지수 백오프(exponential backoff)로 3회 재시도, 최종 실패 건은 `crawl_failures` 테이블에 기록 후 익일 재시도
- **구조 변경 감지**: 파싱 결과 필수 필드(감정가, 매각기일) 누락률이 20%를 넘으면 사이트 구조 변경으로 판단하고 관리자에게 알림 메일 발송 (완전 자동화의 최대 리스크 지점)

### 2.3 수집 범위 설정

전국 전체를 매일 수집하면 요청량이 과도하므로, 설정 파일(`config.yaml`)에서 관심 지역(예: 서울/경기/인천)과 물건종류(예: 아파트, 오피스텔, 다세대)를 지정한다. 이 필터는 크롤링 단계에서 적용하여 요청량 자체를 줄인다.

```yaml
# config.yaml 예시
crawl:
  courts: ["서울중앙지방법원", "수원지방법원", "인천지방법원"]
  property_types: ["아파트", "오피스텔", "다세대주택", "상가"]
  request_delay: [2, 5]        # 랜덤 지연 범위(초)
  max_retries: 3
scoring:
  top_n: 10
  min_discount_rate: 0.20      # 감정가 대비 최저가 20% 이상 할인
report:
  send_time: "08:00"
  recipients: ["ceo@company.co.kr"]
```

---

## 3. 데이터베이스 스키마 (MySQL)

핵심 테이블 8개로 구성한다.

```sql
-- 물건 마스터
CREATE TABLE auction_items (
    id BIGINT AUTO_INCREMENT PRIMARY KEY,
    case_no VARCHAR(50) NOT NULL,           -- 사건번호 (2026타경12345)
    court VARCHAR(50) NOT NULL,             -- 관할법원
    item_no INT DEFAULT 1,                  -- 물건번호
    property_type VARCHAR(30),              -- 물건종류
    address VARCHAR(300),                   -- 소재지
    region_code VARCHAR(10),                -- 법정동코드 (실거래가 매칭용)
    appraisal_price BIGINT,                 -- 감정가
    min_bid_price BIGINT,                   -- 최저매각가
    fail_count INT DEFAULT 0,               -- 유찰횟수
    sale_date DATE,                         -- 매각기일
    area_land DECIMAL(10,2),                -- 토지면적(m2)
    area_building DECIMAL(10,2),            -- 건물면적(m2)
    status VARCHAR(20) DEFAULT 'active',    -- active/sold/cancelled
    first_crawled_at DATETIME,
    last_updated_at DATETIME,
    UNIQUE KEY uk_case (case_no, court, item_no),
    INDEX idx_sale_date (sale_date),
    INDEX idx_region (region_code)
);

-- 원문 문서 (권리분석 LLM 입력)
CREATE TABLE item_documents (
    id BIGINT AUTO_INCREMENT PRIMARY KEY,
    item_id BIGINT NOT NULL,
    doc_type VARCHAR(30),                   -- 매각물건명세서/현황조사서/감정평가요약
    raw_text MEDIUMTEXT,                    -- 원문 텍스트
    crawled_at DATETIME,
    FOREIGN KEY (item_id) REFERENCES auction_items(id)
);

-- 실거래가 시세 (국토부 OpenAPI)
CREATE TABLE market_prices (
    id BIGINT AUTO_INCREMENT PRIMARY KEY,
    region_code VARCHAR(10),
    property_type VARCHAR(30),
    complex_name VARCHAR(100),              -- 단지명
    area DECIMAL(10,2),
    trade_price BIGINT,
    trade_date DATE,
    INDEX idx_region_type (region_code, property_type, trade_date)
);

-- 스코어링 결과
CREATE TABLE item_scores (
    id BIGINT AUTO_INCREMENT PRIMARY KEY,
    item_id BIGINT NOT NULL,
    score_date DATE,                        -- 분석 기준일
    predicted_bid_price BIGINT,             -- 예상 낙찰가 (ML)
    market_price_est BIGINT,                -- 추정 시세
    discount_rate DECIMAL(5,4),             -- 시세 대비 할인율
    competition_score DECIMAL(5,2),         -- 종합 경쟁력 점수 (0~100)
    rank_today INT,                         -- 당일 순위
    FOREIGN KEY (item_id) REFERENCES auction_items(id),
    UNIQUE KEY uk_item_date (item_id, score_date)
);

-- 권리분석 결과 (LLM 출력, JSON)
CREATE TABLE risk_analysis (
    id BIGINT AUTO_INCREMENT PRIMARY KEY,
    item_id BIGINT NOT NULL,
    analyzed_at DATETIME,
    risk_level VARCHAR(10),                 -- LOW/MID/HIGH
    risk_json JSON,                         -- 상세 리스크 구조화 데이터
    summary_text TEXT,                      -- LLM 생성 요약문
    llm_model VARCHAR(50),
    FOREIGN KEY (item_id) REFERENCES auction_items(id)
);

-- 수익률 계산 결과
CREATE TABLE roi_analysis (
    id BIGINT AUTO_INCREMENT PRIMARY KEY,
    item_id BIGINT NOT NULL,
    calc_date DATE,
    scenario VARCHAR(20),                   -- conservative/base/aggressive
    est_total_cost BIGINT,                  -- 총 투입비용
    est_exit_price BIGINT,                  -- 예상 매도가
    est_profit BIGINT,
    roi_pct DECIMAL(6,2),                   -- 수익률(%)
    detail_json JSON,                       -- 비용 항목별 상세
    FOREIGN KEY (item_id) REFERENCES auction_items(id)
);

-- 보고서 발송 이력
CREATE TABLE report_logs (
    id BIGINT AUTO_INCREMENT PRIMARY KEY,
    report_date DATE,
    item_ids JSON,                          -- 포함된 상위 10건 ID
    pdf_path VARCHAR(300),
    sent_at DATETIME,
    send_status VARCHAR(20),                -- success/failed/fallback
    error_msg TEXT
);

-- 파이프라인 실행 로그 (모니터링)
CREATE TABLE pipeline_logs (
    id BIGINT AUTO_INCREMENT PRIMARY KEY,
    run_date DATE,
    stage VARCHAR(30),                      -- crawl/score/risk/roi/report/send
    status VARCHAR(20),                     -- success/partial/failed
    items_processed INT,
    started_at DATETIME,
    finished_at DATETIME,
    error_msg TEXT
);
```

---

## 4. 2단계 — 필터링 및 스코어링

### 4.1 1차 규칙 기반 필터

전체 수집 물건에서 다음 조건으로 후보군을 압축한다 (통상 수백 건 → 30~50건).

감정가 대비 최저매각가 할인율 20% 이상(유찰 1회 이상), 매각기일이 향후 7~30일 이내(입찰 준비 시간 확보), 관심 지역·물건종류 일치, 그리고 명백한 배제 조건(지분매각, 대지권 미등기 등 config에서 지정한 키워드가 물건비고에 포함된 경우 제외)을 적용한다.

### 4.2 예상 낙찰가 예측 (XGBoost)

과거 낙찰 데이터를 학습하여 예상 낙찰가를 예측한다. BNCos 품질예측 프로젝트와 동일한 구조(XGBoost 회귀 + SHAP 해석)를 재활용한다.

- **학습 데이터**: 크롤링으로 축적되는 과거 매각결과 (사건번호별 낙찰가). 초기에는 데이터가 없으므로 첫 2~3개월은 규칙 기반(지역별 평균 낙찰가율 × 감정가)으로 운영하고, 데이터 축적 후 ML로 전환
- **주요 피처**: 감정가, 최저가, 유찰횟수, 물건종류, 지역, 면적, 인근 실거래가 평균, 매각기일 계절성
- **출력**: 예상 낙찰가 및 신뢰구간

### 4.3 종합 경쟁력 점수

각 후보 물건에 대해 0~100점의 종합 점수를 산출한다.

```
경쟁력 점수 = 0.40 × 시세차익 점수   (추정시세 - 예상낙찰가) / 추정시세
           + 0.25 × 환금성 점수     (해당 지역·평형 최근 6개월 거래량)
           + 0.20 × 리스크 점수     (권리분석 결과 역산, HIGH=0점)
           + 0.15 × 임대수익 점수   (추정 월세 수익률)
```

가중치는 config.yaml에서 조정 가능하다. 상위 10건이 최종 보고서 대상이 된다.

---

## 5. 3단계 — LLM 권리분석 (Claude API)

### 5.1 처리 흐름

상위 후보 30~50건에 대해서만 LLM 분석을 수행한다(비용 최적화). 매각물건명세서 + 현황조사서 원문을 Claude API에 입력하고, structured output으로 리스크 요소를 JSON으로 강제 추출한다.

```python
RISK_SCHEMA = {
    "senior_tenant": {           # 선순위 임차인
        "exists": "bool",
        "deposit_amount": "int|null",
        "opposing_power": "bool",     # 대항력
        "priority_repayment": "bool"  # 우선변제권
    },
    "lien": "bool",              # 유치권 신고
    "legal_superficies": "bool", # 법정지상권 성립 여지
    "share_sale": "bool",        # 지분매각
    "land_right_issue": "bool",  # 대지권 미등기/토지별도등기
    "occupancy": "string",       # 점유 현황 요약
    "estimated_eviction_difficulty": "LOW|MID|HIGH",  # 명도 난이도
    "estimated_extra_cost": "int",    # 인수 예상 추가비용 (보증금 인수 등)
    "risk_level": "LOW|MID|HIGH",
    "risk_summary": "string"     # 3문장 이내 한국어 요약
}
```

### 5.2 프롬프트 설계 원칙

시스템 프롬프트에 권리분석 기준(대항력 판단 기준일 = 말소기준권리 설정일, 최우선변제금 기준표 등)을 명시하고, "확실하지 않으면 반드시 HIGH로 분류"하는 보수적 판정 원칙을 강제한다. 완전 자동화 환경에서는 오탐(false negative)이 실제 손실로 직결되므로, 애매한 물건은 리스크를 높게 잡아 순위에서 밀려나게 하는 것이 안전하다.

### 5.3 비용 추정

건당 입력 약 3,000토큰 + 출력 500토큰, 일 50건 기준 gpt-4o-mini 사용 시 월 2~4만 원 수준이다. 보고서 문구 생성까지 포함해도 월 5만 원 이내로 예상된다.

---

## 6. 4단계 — 수익률 계산 (ROI Engine)

LLM이 아닌 규칙 기반 계산 엔진으로 구현한다(숫자 계산에 LLM을 쓰면 오류 위험). 3가지 시나리오(보수/기준/공격)로 산출한다.

**총 투입비용** = 예상낙찰가 + 취득세(주택 1~12%, 상가 4.6% 자동 적용) + 등기비용 + 명도비용(난이도별: LOW 200만/MID 500만/HIGH 1,500만) + 인수 보증금(권리분석 결과) + 수리비(면적 × 평당 단가) + 대출이자(보유기간 6~12개월 가정)

**예상 매도가** = 인근 실거래가 중위값 × 시나리오 계수(보수 0.93 / 기준 0.97 / 공격 1.00)

**수익률(%)** = (예상 매도가 − 총 투입비용) / 자기자본 × 100 (레버리지 반영)

취득세율, 대출 LTV, 금리 등 파라미터는 config.yaml에서 관리하여 세법·금리 변동 시 코드 수정 없이 대응한다.

---

## 7. 5단계 — 보고서 생성 및 발송

### 7.1 보고서 구성 (PDF, 10~15페이지)

1페이지 요약(오늘의 TOP 10 테이블: 순위, 소재지, 최저가, 예상낙찰가, 예상수익률, 리스크등급, 매각기일) 후, 물건당 1페이지씩 상세 분석(물건 사진, 기본정보, 시세 비교 차트, 권리분석 요약, 시나리오별 수익률 표, LLM 생성 투자 포인트 3줄)을 배치한다. 마지막에 데이터 기준일과 면책 문구를 넣는다.

HTML 템플릿(Jinja2) → WeasyPrint로 PDF 변환. 차트는 matplotlib으로 PNG 생성 후 삽입한다.

### 7.2 발송

Python smtplib + SMTP(회사 메일 서버 또는 Gmail SMTP)로 08:00 정각 발송한다. 메일 본문에는 TOP 3 하이라이트를 HTML로 넣고 전체 보고서는 PDF 첨부한다.

### 7.3 폴백 정책 ("매일 아침 메일 보장")

- 크롤링 실패 → 전일 데이터 기준으로 보고서 생성, 제목에 [데이터 지연] 표기
- LLM API 장애 → 권리분석 없이 스코어링 결과만으로 축약 보고서 발송
- 상위 10건 미달 → 존재하는 건수만으로 발송
- 보고서 생성 자체 실패 → 실패 알림 메일이라도 반드시 발송 (침묵 실패 금지)

---

## 8. 스케줄링 및 운영

### 8.1 cron 구성

```cron
# /etc/cron.d/auction-pipeline
0  1 * * *  auction  /opt/auction/venv/bin/python /opt/auction/run.py crawl
0  3 * * *  auction  /opt/auction/venv/bin/python /opt/auction/run.py score
0  4 * * *  auction  /opt/auction/venv/bin/python /opt/auction/run.py risk
0  6 * * *  auction  /opt/auction/venv/bin/python /opt/auction/run.py roi
30 7 * * *  auction  /opt/auction/venv/bin/python /opt/auction/run.py report
0  8 * * *  auction  /opt/auction/venv/bin/python /opt/auction/run.py send
15 8 * * *  auction  /opt/auction/venv/bin/python /opt/auction/run.py healthcheck
```

healthcheck는 당일 send 성공 로그가 없으면 관리자에게 SMS/알림톡 또는 별도 메일로 경보를 보낸다.

### 8.2 디렉토리 구조

```
/opt/auction/
├── run.py                  # 단계별 진입점 (CLI)
├── config.yaml             # 전체 설정
├── crawler/
│   ├── court_auction.py    # 법원경매정보 크롤러 (Playwright)
│   ├── molit_api.py        # 국토부 실거래가 API
│   └── parser.py           # 문서 파싱
├── analysis/
│   ├── scorer.py           # 필터링 + XGBoost 예측
│   ├── risk_llm.py         # ChatGPT API 권리분석
│   └── roi.py              # 수익률 계산
├── report/
│   ├── builder.py          # Jinja2 → PDF
│   ├── templates/report.html
│   └── mailer.py           # SMTP 발송
├── db/
│   ├── schema.sql
│   └── models.py
├── logs/
└── output/                 # 생성된 PDF 보관 (일자별)
```

---

## 9. 완전 자동화의 예상 문제점 (운영하며 확인할 것)

사람 개입 없이 돌리면서 직접 확인하게 될 주요 리스크를 미리 정리한다.

1. **사이트 구조 변경**: 법원경매정보 사이트 개편 시 크롤러 전면 수정 필요. 구조 변경 감지 알림(2.2절)이 유일한 방어선이며, 통상 연 1~2회 발생
2. **IP 차단**: 요청 과다 시 차단. 지연 설정 준수 및 차단 감지 시 자동 중단 로직 필수
3. **권리분석 오판**: LLM이 특수 권리관계(가장임차인, 유치권 진위 등)를 놓칠 수 있음. 보수적 판정 원칙으로 완화하지만, 실제 입찰 전에는 반드시 별도 검증이 필요하다는 한계가 보고서 면책 문구에 명시됨
4. **시세 추정 오차**: 실거래가 데이터가 희소한 지역(토지, 특수물건)은 수익률 신뢰도 급락. 보고서에 시세 신뢰도 등급(A/B/C) 표기
5. **낙찰가 예측 초기 정확도**: 학습 데이터 축적 전 2~3개월은 규칙 기반이라 오차 큼
6. **크롤링의 약관 리스크**: 사이트 이용약관상 자동수집 제한 조항이 존재할 수 있으며, 상업적 이용 시 문제 소지가 있음. 안정 운영 후 지지옥션/스피드옥션 유료 API 전환을 중기 과제로 권장 (월 10~30만 원, 크롤러 유지보수 비용과 상쇄)

---

## 10. 구축 로드맵

**1주차**: DB 스키마 + 크롤러 (물건 기본정보) + 국토부 API 연동
**2주차**: 문서(매각물건명세서) 수집 + 규칙 기반 필터/스코어링 + ROI 엔진
**3주차**: ChatGPT API 권리분석 + 보고서 템플릿 + PDF 생성
**4주차**: 이메일 발송 + cron 배치 + 폴백/모니터링 + 시험 운영 시작
**2~3개월차**: 낙찰 결과 데이터 축적 → XGBoost 예측 모델 학습 전환

이후 확장 옵션으로 웹 대시보드(PHP, 과거 보고서 열람/물건 검색), 카카오 알림톡 연동, 관심물건 매각기일 D-day 알림 등을 붙일 수 있다.
