Local + Ring(알림) = 우리 동네 문화생활을 한눈에!
로컬링은 대한민국 전국의 문화행사, 스포츠 강좌, 평생학습 프로그램 정보를 자동으로 수집하고, AI가 맞춤 추천해주는 문화생활 플랫폼입니다.
🚀 직접 실행해보고 싶다면 → 처음부터 실행하기 가이드 (docs/GETTING_STARTED.md)
┌─────────────────────────────────────────────────────────────────────────────────────┐
│ LOCALING │
│ AI 기반 문화행사 검색 플랫폼 │
└─────────────────────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────────────────┐
│ FRONTEND (static/) │
│ ┌─────────────┐ ┌──────────────┐ ┌───────────────────────────────────────────┐ │
│ │ index.html │ │ style.css │ │ app.js │ │
│ │ - 채팅 UI │ │ - 반응형 │ │ - 위치감지 (GPS/IP) │ │
│ │ - 모달 창 │ │ - 모바일퍼스트│ │ - 자연어 검색 │ │
│ │ - 로딩 오버레이│ │ - 다크모드 │ │ - 페이지네이션 │ │
│ └─────────────┘ └──────────────┘ └───────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────────────────┘
│
▼ HTTP/JSON
┌─────────────────────────────────────────────────────────────────────────────────────┐
│ BACKEND (app/) FastAPI + Uvicorn/Gunicorn │
│ │
│ ┌───────────────────────────────────────────────────────────────────────────────┐ │
│ │ API Routes (api/routes/) │ │
│ │ ┌──────────────┐ ┌───────────────────┐ ┌─────────────┐ ┌──────────────┐ │ │
│ │ │ POST /api/ │ │ GET /api/detect- │ │ GET /api/ │ │ POST /kakao/ │ │ │
│ │ │ chat │ │ location │ │ locate │ │ skills/events│ │ │
│ │ └──────┬───────┘ └───────────────────┘ └─────────────┘ └──────────────┘ │ │
│ └─────────┼─────────────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌───────────────────────────────────────────────────────────────────────────────┐ │
│ │ Services (services/) │ │
│ │ │ │
│ │ ┌─────────────────┐ ┌───────────────────┐ ┌─────────────────────────┐ │ │
│ │ │ llm.py │ │ semantic_search.py │ │ admin_region.py │ │ │
│ │ │ │ │ │ │ │ │ │
│ │ │ • Gemini 2.5 │ │ • Hybrid Search │ │ • 행정구역 정규화 │ │ │
│ │ │ • Rule-based │ │ (벡터+키워드) │ │ • 별칭 매핑 │ │ │
│ │ │ fallback │ │ • pgvector 유사도 │ │ • 캐시 기반 조회 │ │ │
│ │ └─────────────────┘ └───────────────────┘ └─────────────────────────┘ │ │
│ │ │ │
│ │ ┌─────────────────┐ ┌───────────────────┐ ┌─────────────────────────┐ │ │
│ │ │ embedding.py │ │ events.py │ │ region_parser.py │ │ │
│ │ │ │ │ │ │ │ │ │
│ │ │ • E5-small-ko │ │ • 이벤트 스코어링 │ │ • 전국 지역 추출 │ │ │
│ │ │ • 384차원 벡터 │ │ • 키워드 확장 │ │ • 동명이인 처리 │ │ │
│ │ │ • 배치 생성 │ │ • 추천 알고리즘 │ │ │ │ │
│ │ └─────────────────┘ └───────────────────┘ └─────────────────────────┘ │ │
│ └───────────────────────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌───────────────────────────────────────────────────────────────────────────────┐ │
│ │ Models & ORM (models/) SQLAlchemy 2.0 │ │
│ │ ┌─────────────────────────────────────────────────────────────────────────┐ │ │
│ │ │ Event Model │ │ │
│ │ │ - id, source, title, sido, sigungu, dong, place, address │ │ │
│ │ │ - start_date, end_date, codename, use_fee, homepage... │ │ │
│ │ │ - embedding (Vector 384) ← pgvector │ │ │
│ │ └─────────────────────────────────────────────────────────────────────────┘ │ │
│ └───────────────────────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────────────────┘
│
▼ psycopg2
┌─────────────────────────────────────────────────────────────────────────────────────┐
│ DATABASE PostgreSQL (Neon) + pgvector │
│ │
│ ┌───────────────────────────────────┐ ┌─────────────────────────────────────┐ │
│ │ events 테이블 │ │ admin_regions 테이블 │ │
│ │ - 25,000+ 행사 │ │ - 시도/시군구/읍면동 │ │
│ │ - HNSW 벡터 인덱스 │ │ - 별칭 매핑 │ │
│ │ - 복합 인덱스 │ │ │ │
│ └───────────────────────────────────┘ └─────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────────────────┐
│ CRAWLERS (crawlers/) 278개 지역별 크롤러 │
│ │
│ ┌─────────────────────────────────────────────────────────────────────────────┐ │
│ │ Base Classes │ │
│ │ ┌─────────────────────────┐ ┌──────────────────────────────────────┐ │ │
│ │ │ BaseCrawler │ │ BaseSeleniumCrawler │ │ │
│ │ │ - HTTP 기반 │ │ - JavaScript 렌더링 필요 사이트 │ │ │
│ │ │ - BeautifulSoup │ │ - Headless Chrome │ │ │
│ │ └─────────────────────────┘ └──────────────────────────────────────┘ │ │
│ └─────────────────────────────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ seoul/ (43) │ │gyeonggi(106)│ │ incheon(17) │ │ busan/ (5) │ │national(26) │ │
│ │ 25개 자치구 │ │ 31개 시/군 │ │ 10개 구/군 │ │ 16개 구/군 │ │KOPIS,Yes24 │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ daegu/ (5) │ │daejeon/(6) │ │ gwangju/(5) │ │ ulsan/ (5) │ │ gangwon(15) │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘ │
└─────────────────────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────────────────┐
│ EXTERNAL SERVICES │
│ │
│ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ ┌────────────────┐ │
│ │ Google Gemini │ │ Kakao Maps │ │ Nominatim │ │ 지자체/플랫폼 │ │
│ │ 2.5-flash LLM │ │ Local API │ │ Reverse Geocode │ │ KOPIS/관광공사 │ │
│ └─────────────────┘ └─────────────────┘ └─────────────────┘ └────────────────┘ │
└─────────────────────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────────────────┐
│ INFRASTRUCTURE Google Cloud Run │
│ │
│ ┌─────────────────────────────────────────────────────────────────────────────┐ │
│ │ 배포 파이프라인 │ │
│ │ │ │
│ │ cloudbuild.yaml → Docker Build → Container Registry → Cloud Run │ │
│ │ │ │
│ │ Cloud Scheduler (매주 월요일 2AM KST) → 크롤러 실행 │ │
│ │ Cloud Scheduler (5분마다, 24시간) → /warmup 워밍업 │ │
│ └─────────────────────────────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────────────────────────┐ │
│ │ Secret Manager │ │ asia-northeast3 │ │ max-instances: 1, min-instances: 0 │ │
│ │ API Keys 보관 │ │ (서울 리전) │ │ 2 CPU, 4Gi Memory, Concurrency 80 │ │
│ └─────────────────┘ └─────────────────┘ └─────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────────────────┘
사용자 app.js chat.py region_parser.py DB (PostgreSQL)
│ │ │ │ │
│ "시청 맛집" │ │ │ │
│────────────────────▶│ │ │ │
│ │ POST /api/chat │ │ │
│ │───────────────────▶│ │ │
│ │ │ │ │
│ │ │ ① Rule-based 파싱 │ │
│ │ │──────────────────────▶│ │
│ │ │ │ │
│ │ │ │ RegionCache 조회 │
│ │ │ │ (alias → sido/sigungu)│
│ │ │ │──────────────────────▶│
│ │ │ │ 시청 → 서울특별시 중구│
│ │ │ │◀──────────────────────│
│ │ │ │ │
│ │ │ ParsedQuery 반환 │ │
│ │ │ region=중구 │ │
│ │ │ sido=서울특별시 │ │
│ │ │ keywords=[맛집] │ │
│ │ │◀──────────────────────│ │
│ │ │ │ │
│ │ │ ② normalized_region 설정 │
│ │ │ sigungu=중구, sido=서울특별시 │
│ │ │ (parsed 정보 직접 사용, 재조회 없음) │
│ │ │ │ │
│ │ │ ③ 계층적 지역 매칭 │ │
│ │ │ sigungu=중구 AND sido=서울특별시 │
│ │ │──────────────────────────────────────────────▶│
│ │ │ 서울 중구 이벤트만 반환 (동명이구 방지) │
│ │ │◀──────────────────────────────────────────────│
│ │ │ │ │
│ │ │ ④ 하이브리드 검색 │ │
│ │ │ 벡터 유사도(0.6) + 키워드 LIKE(0.4) │
│ │ │──────────────────────────────────────────────▶│
│ │ │◀──────────────────────────────────────────────│
│ │ │ │ │
│ │ │ ⑤ 스코어링 + 정렬 │ │
│ │ │ 키워드 매칭·무료·날짜 근접·지역 가중치 │
│ │ │ │ │
│ │ JSON 응답 │ │ │
│ │ (AI 요약 + 9개) │ │ │
│ │◀──────────────────│ │ │
│ 결과 카드 렌더링 │ │ │ │
│◀────────────────────│ │ │ │
│ │ │ │ │
사용자 입력
│
▼
┌─────────────────────────┐ 성공 ┌─────────────────────┐
│ Rule-based 파서 │────────────▶│ ParsedQuery 반환 │
│ (region_parser.py) │ │ region, sido, │
│ • 지역 alias 매칭 │ │ keywords, 날짜 │
│ • 조사/접미사 처리 │ └─────────────────────┘
│ • 날짜 파싱 │
└─────────┬───────────────┘
│ 지역 추출 실패
▼
┌─────────────────────────┐ 성공 ┌─────────────────────┐
│ Gemini LLM Fallback │────────────▶│ ParsedQuery 반환 │
│ (llm.py) │ └─────────────────────┘
│ • JSON 구조화 추출 │
└─────────┬───────────────┘
│ API 오류/실패
▼
┌─────────────────────────┐
│ 키워드 전용 검색 │
│ (지역 필터 없이 전체) │
└─────────────────────────┘
① 동(dong) 매칭 시도 ──── 있으면 ──▶ 결과 반환
│
없으면
▼
② 시군구(sigungu) 매칭 ── 있으면 ──▶ 결과 반환 (sido 함께 필터링 → 동명이구 방지)
│
없으면
▼
③ 시도(sido) 매칭 ─────── 있으면 ──▶ 결과 반환
│
없으면
▼
④ 전체 이벤트 Fallback ────────────▶ 결과 반환 + "해당 지역 서비스 준비 중" 안내
| 레이어 | 기술 |
|---|---|
| 프론트엔드 | Vanilla JS, CSS3, Pretendard 폰트 |
| 백엔드 | FastAPI 0.104, Uvicorn, Gunicorn |
| 데이터베이스 | PostgreSQL (Neon) + pgvector |
| ORM | SQLAlchemy 2.0 |
| AI/NLP | Gemini 2.5-flash, E5-small-ko (384차원), kiwipiepy (Korean NLP) |
| 크롤링 | BeautifulSoup4, Selenium, httpx |
| 인프라 | Docker, Google Cloud Run, Cloud Build |
| 스케줄링 | Cloud Scheduler (크롤링: 매주 월요일 02:00 KST, 워밍업: 5분마다 24시간) |
| 지역 | 크롤러 수 | 주요 데이터 소스 |
|---|---|---|
| 서울 | 43개 | 25개 구 문화센터, 체육시설, 평생학습관, 도서관 |
| 경기 | 106개 | 31개 시군 문화센터, 도서관, 청소년시설, 박물관 |
| 인천 | 17개 | 10개 구 문화시설, 도서관, 체육시설 |
| 부산 | 5개 | 문화센터, 도서관, 체육시설, 청소년시설 |
| 대구 | 5개 | 문화센터, 도서관, 체육시설 |
| 대전 | 6개 | 문화센터, 도서관, 구별 센터 |
| 광주 | 5개 | 문화센터, 도서관, 체육시설 |
| 울산 | 5개 | 문화센터, 도서관, 체육시설 |
| 강원 | 15개 | 속초, 강릉, 춘천, 원주 등 문화/축제 |
| 충북 | 4개 | 청주 문화/도서관/평생학습 |
| 충남 | 4개 | 천안 문화/도서관/평생학습 |
| 전북 | 9개 | 전주, 군산, 익산 문화/축제 |
| 전남 | 15개 | 목포, 순천, 여수 문화/체육/청소년 |
| 경북 | 4개 | 경주(gyeongju_culture.py), 구미(gumi_culture.py), 안동(andong_culture.py), 포항(pohang_culture.py) |
| 경남 | 4개 | 창원, 김해, 거제, 진주 문화 |
| 제주 | 5개 | 문화센터, 도서관, 체육시설 |
| 전국/플랫폼 | 26개 | KOPIS, 인터파크, 예스24, Meetup, 한국관광공사, 국립박물관 등 |
- Google Gemini 2.5-flash: 자연어 질문 이해 및 파싱
- 한국어 최적화 임베딩:
dragonkue/multilingual-e5-small-ko모델 (384차원) - 하이브리드 검색: 시맨틱(벡터 유사도) + 키워드(LIKE) 검색 결합
- 행정구역 정규화: admin_regions 테이블 + 카카오 로컬 API fallback
- DB 캐시 기반 빠른 매칭 (시도/시군구/동 계층)
- alias 컬럼으로 별칭 지원 (여의도→영등포구, 홍대→마포구)
- 매칭 실패 시 카카오 로컬 API로 자동 정규화
- 스마트 Fallback: API 오류 시 규칙 기반 파서로 자동 전환
- 위치 기반: GPS/IP로 현재 위치 자동 감지
- 키워드 추출: 검증된 불용어/조사 사전 기반
- 채팅 UI: 대화형 검색 인터페이스
- 추천 이벤트: AI 기반 맞춤 추천
- 반응형 디자인: 모바일/데스크톱 지원
- DB 연결 풀 튜닝: pool_size=20, max_overflow=40 (최대 60 동시 연결)
- 멀티 지역 단일 쿼리: 복수 지역 검색 시 N개 쿼리 → 1개 OR 쿼리로 통합
- 행정구역 캐시: RegionCache 싱글턴에 시군구→시도 매핑 딕셔너리 캐시, UNION ALL 단일 쿼리
- 임베딩 모델 백그라운드 Preload + Lock: 서버 시작 시 백그라운드 스레드로 로드,
threading.Lock이중 잠금으로 중복 로딩 방지, float16 직접 로드로 메모리 피크 절감 /warmup엔드포인트: 임베딩 모델만 사전 로드 (LLM·DB 없음), warmup 스케줄러가 5분마다 호출해 첫 검색 지연 방지- Cloud Scheduler 워밍업: 5분 간격
/warmup호출로 인스턴스·모델 warm 유지 (24시간) - Rule-based 파싱 우선: LLM 호출 전 규칙 기반 파서 먼저 적용하여 API 비용 최소화
Localing/
├── app/ # FastAPI 애플리케이션
│ ├── main.py # 앱 진입점
│ ├── api/routes/ # API 라우트
│ │ ├── chat.py # 채팅 API (자연어 검색)
│ │ ├── location.py # 위치 감지 API (IP/GPS/타임존)
│ │ └── kakao.py # 카카오 챗봇 스킬 서버
│ ├── services/ # 비즈니스 로직
│ │ ├── llm.py # Gemini AI 연동
│ │ ├── semantic_search.py # 시맨틱 검색 (하이브리드 검색)
│ │ ├── embedding.py # 텍스트 임베딩 (E5-small-ko)
│ │ ├── events.py # 이벤트 점수/추천
│ │ ├── admin_region.py # 행정구역 정규화 (DB 캐시)
│ │ ├── region_parser.py # 지역 파싱
│ │ ├── kakao.py # 카카오 챗봇 통합
│ │ └── kakao_local.py # 카카오 로컬 API (fallback)
│ ├── models/ # SQLAlchemy 모델
│ ├── schemas/ # Pydantic 스키마
│ └── constants/ # 상수 정의
│
├── crawlers/ # 크롤러 모듈 (278개)
│ ├── base.py # 기본 크롤러 클래스
│ ├── base_selenium.py # Selenium 크롤러 클래스
│ ├── utils.py # 유틸리티 함수
│ │
│ ├── seoul/ # 서울 (43개)
│ ├── gyeonggi/ # 경기 (106개)
│ ├── incheon/ # 인천 (17개)
│ ├── busan/ # 부산 (5개)
│ ├── daegu/ # 대구 (5개)
│ ├── daejeon/ # 대전 (6개)
│ ├── gwangju/ # 광주 (5개)
│ ├── ulsan/ # 울산 (5개)
│ ├── gangwon/ # 강원 (15개)
│ ├── chungbuk/ # 충북 (4개)
│ ├── chungnam/ # 충남 (4개)
│ ├── jeonbuk/ # 전북 (9개)
│ ├── jeonnam/ # 전남 (15개)
│ ├── gyeongbuk/ # 경북 (4개)
│ ├── gyeongnam/ # 경남 (4개)
│ ├── jeju/ # 제주 (5개)
│ └── national/ # 전국/플랫폼 (26개)
│
├── core/ # 핵심 모듈
│ ├── database.py # DB 연결 (커넥션 풀 최적화)
│ ├── config.py # 설정 관리
│ └── prompts.py # AI 프롬프트
│
├── static/ # 프론트엔드
│ ├── index.html # 메인 페이지
│ ├── about.html # 서비스 소개
│ ├── faq.html # FAQ
│ ├── guide.html # 사용 가이드
│ ├── privacy.html # 개인정보처리방침
│ ├── terms.html # 이용약관
│ ├── app.js # JavaScript
│ ├── style.css # 스타일시트
│ ├── og-image.png # OG 이미지
│ ├── robots.txt # 크롤러 설정
│ ├── sitemap.xml # 사이트맵
│ ├── ads.txt # AdSense 인증
│ ├── blog/ # 블로그 (58개 SEO 글 + index)
│ └── events/ # SEO 이벤트 랜딩 페이지 (63개)
│
├── tests/ # 테스트
│ ├── ai_message_and_llm.py # AI 메시지 및 LLM 파싱 테스트 (400+ 케이스)
│ └── comprehensive_parsing.py # 종합 파싱 테스트 (1,583+ 케이스)
│
├── scripts/ # 실행 스크립트
│ ├── run_all_crawlers.py # 전체 크롤러 실행
│ ├── generate_embeddings.py # 임베딩 일괄 생성
│ ├── download_model.py # 임베딩 모델 다운로드 (GCS 캐시)
│ ├── update_embedding_model.py # 임베딩 모델 업데이트
│ ├── fix_region_data.py # 지역 데이터 정규화
│ ├── normalize_fees.py # 이용료 데이터 정규화
│ ├── remove_duplicate_events.py # 중복 이벤트 제거
│ ├── analyze_crawlers.py # 크롤러 분석
│ ├── convert_english_to_korean.py # 데이터 언어 변환
│ ├── remove_fallback.py # Fallback 데이터 정리
│ ├── check_memory_usage.py # 메모리 사용량 측정
│ ├── fix_invalid_urls.py # 잘못된 URL 데이터 수정
│ ├── run_tests.sh # 테스트 일괄 실행
│ └── setup_secrets.sh # GCP Secret Manager 설정
│
├── db/ # 데이터베이스
│ ├── schema.sql # 테이블 스키마 (events, admin_regions)
│ ├── seed_admin_regions.sql # 행정구역 시드 데이터 (7,460행, 별칭 포함)
│ ├── neon.md # Neon PostgreSQL 연결 가이드
│ └── migrations/ # DB 마이그레이션
│ ├── 001_add_embedding.sql # pgvector 임베딩 컬럼 추가
│ ├── 002_drop_gu_name_column.sql
│ ├── 003_remove_event_key.sql # 복합 유니크 제약조건
│ ├── 004_add_landmark_aliases.sql # 랜드마크 별칭 추가
│ ├── 005_add_nationwide_aliases.sql # 전국 지역 별칭 추가
│ ├── 006_add_hongdae_alias.sql # 홍대 별칭 추가
│ └── 007_fix_aliases_ambiguous.sql # 동명이구 별칭 수정
│
├── models/ # 임베딩 모델 캐시
│
├── docs/ # 문서
│ ├── GETTING_STARTED.md # 처음부터 실행하기 가이드
│ ├── OPERATIONS.md # 운영 명령어 가이드
│ ├── CRAWLERS_README.md # 크롤러 상세 문서
│ └── claude_testcase_guide.txt # 테스트 케이스 작성 가이드
│
├── cloudbuild.yaml # Google Cloud Build 설정
├── Dockerfile # Docker 이미지
└── requirements.txt # Python 의존성
| 권역 | 구 | 크롤러 |
|---|---|---|
| 강남권 | 강남구 | gangnam.py, gangnam_culture.py |
| 서초구 | seocho.py, seocho_sports.py, seocho_sports_center.py, seocho_citizen_center.py | |
| 송파구 | songpa_culture.py, songpa_learning.py, songpa_sports.py | |
| 강서권 | 강서구 | gangseo_culture.py |
| 양천구 | yangcheon_culture.py | |
| 영등포구 | yeongdeungpo_culture.py, yeongdeungpo_learning.py | |
| 강북권 | 노원구 | nowon_culture.py |
| 도봉구 | dobong_culture.py | |
| 강북구 | gangbuk_culture.py | |
| 동북권 | 동대문구 | dongdaemun_culture.py |
| 성동구 | seongdong_culture.py | |
| 광진구 | gwangjin_culture.py | |
| 중랑구 | jungnang_culture.py | |
| 성북구 | seongbuk_culture.py | |
| 서북권 | 마포구 | mapo_culture.py |
| 서대문구 | seodaemun_culture.py | |
| 은평구 | eunpyeong_culture.py | |
| 도심권 | 종로구 | jongno_culture.py |
| 중구 | junggu_culture.py | |
| 용산구 | yongsan_culture.py | |
| 서남권 | 관악구 | gwanak.py, gwanak_culture.py |
| 동작구 | dongjak.py | |
| 금천구 | geumcheon_culture.py | |
| 구로구 | guro_culture.py | |
| 동남권 | 강동구 | gangdong_culture.py |
| 공통 | 서울시 | seoul_opendata_events.py, seoul_libraries.py, seoul_library.py, seoul_museums.py, seoul_youth.py, seoul_youth_centers.py, seoul_festival.py, seoul_sfac.py, seoul_sema.py, seoul_sports_multi.py |
수원, 성남, 고양, 용인, 부천, 안산, 안양, 남양주, 화성, 평택, 의정부, 시흥, 파주, 김포, 광명, 광주, 군포, 하남, 오산, 이천, 안성, 의왕, 양평, 여주, 과천, 가평, 양주, 연천, 포천, 동두천, 구리 등 31개 시군
| 크롤러 | 설명 |
|---|---|
| kopis.py | KOPIS 공연예술통합전산망 API |
| interpark_ticket.py | 인터파크 티켓 |
| yes24_ticket.py | 예스24 티켓 |
| showala.py | 쇼왈라 공연 정보 (AJAX) |
| meetup.py | Meetup.com 이벤트 |
| onoffmix.py | 온오프믹스 행사 |
| eventbrite.py | Eventbrite 이벤트 |
| thinkcontest.py | 공모전/대회 정보 |
| visitkorea_festival.py | 한국관광공사 축제 API |
| visitkorea_experience.py | 한국관광공사 체험 API |
| coex.py, kintex.py, bexco.py, exco.py | 전시컨벤션센터 |
| sac.py | 서울 예술의전당 |
| museum_korea.py | 국립중앙박물관 |
| mmca.py | 국립현대미술관 |
| seoul_history_museum.py | 서울역사박물관 |
| national_library_api.py | 국립도서관 API |
| national_museum_api.py | 국립박물관 API |
| national_youth_api.py | 청소년활동정보서비스 API |
| culture_portal.py | 문화포털 |
| culture_facility.py | 문화시설 정보 |
| kcisa_culture.py | 한국문화정보원 |
| culture_unified.py | 서울시 + 전국 문화행사 통합 수집 |
| seoul.py | 서울시 열린데이터광장 |
🧭 처음 실행하시나요? 준비물부터 트러블슈팅까지 전 과정을 담은 처음부터 실행하기 가이드 (docs/GETTING_STARTED.md) 를 따라 하세요. 아래는 요약본입니다.
검색에 사용하는 임베딩 모델(dragonkue/multilingual-e5-small-ko, 약 450MB)은 저장소에 포함되어 있지 않습니다 (models/는 gitignore 대상).
- 로컬 개발: 별도 준비 불필요. 서버 첫 실행 시 Hugging Face에서 자동 다운로드됩니다 (인터넷 연결 필요).
- Docker 빌드:
Dockerfile이models/디렉토리를 이미지에 복사하므로, 빌드 전에 모델을 미리 받아둬야 합니다:
# models/ 디렉토리에 모델 다운로드 (Docker 빌드 전 1회)
SENTENCE_TRANSFORMERS_HOME=./models python -c \
"from sentence_transformers import SentenceTransformer; SentenceTransformer('dragonkue/multilingual-e5-small-ko')"- Cloud Build 배포:
scripts/download_model.py가 GCS 버킷 캐시에서 모델을 가져옵니다 (GCS_BUCKET환경변수로 버킷 지정). GCS에 없으면 Hugging Face에서 받아 캐시합니다.
# 1. 의존성 설치
pip install -r requirements.txt
# 2. 환경 변수 설정
cp .env.example .env
# .env 파일 편집하여 API 키 설정
# 3. DB 초기화 (pgvector 지원 PostgreSQL 필요, 예: Neon)
psql $DATABASE_URL -f db/schema.sql
psql $DATABASE_URL -f db/seed_admin_regions.sql # 행정구역 시드 (지역 파싱 필수)
# 4. 크롤러 실행 (행사 데이터 수집)
python scripts/run_all_crawlers.py
# 5. 임베딩 생성
python scripts/generate_embeddings.py
# 6. 서버 실행 (첫 실행 시 임베딩 모델 자동 다운로드)
uvicorn app.main:app --reload
db/seed_admin_regions.sql은 반드시 실행해야 합니다. 전국 행정구역(시도/시군구/동)과 별칭(홍대→마포구 등) 7,460행이 들어 있으며, 이 데이터가 없으면 지역 파싱·크롤러의 지역 정규화가 동작하지 않습니다. 별칭 마이그레이션(004~007)은 이미 반영되어 있습니다.
# 사전에 위 "임베딩 모델 준비"의 models/ 다운로드 필요
docker-compose up -dgcloud builds submit --config cloudbuild.yaml.env 파일에 다음 변수들을 설정하세요:
# 데이터베이스
DATABASE_URL=postgresql://user:password@host/dbname
# AI
GEMINI_API_KEY=your-gemini-api-key
# 지자체/플랫폼 API
SEOUL_API_KEY=your-seoul-opendata-api-key
NATIONAL_API_KEY=your-data-go-kr-api-key
KOPIS_API_KEY=your-kopis-api-key
# 위치 서비스
KAKAO_REST_API_KEY=your-kakao-key # 지도 표시 + 지역 정규화 fallback| 엔드포인트 | 메서드 | 설명 |
|---|---|---|
/ |
GET | 메인 페이지 |
/about |
GET | 서비스 소개 |
/faq |
GET | FAQ |
/guide |
GET | 사용 가이드 |
/blog |
GET | 블로그 목록 |
/blog/{slug} |
GET | 블로그 포스트 |
/events/{slug} |
GET | SEO 이벤트 랜딩 페이지 (도시+카테고리 조합) |
/privacy |
GET | 개인정보처리방침 |
/terms |
GET | 이용약관 |
/api/chat |
POST | AI 채팅 (자연어 검색) |
/api/detect-location |
GET | IP 기반 위치 감지 |
/api/detect-location-timezone |
GET | 타임존 기반 위치 감지 (fallback) |
/api/locate |
GET | 좌표→지역명 변환 |
/kakao/skills/events |
POST | 카카오 챗봇 스킬 서버 |
/warmup |
GET | 임베딩 모델 워밍업 (스케줄러 5분 간격 호출) |
/health |
GET | 헬스체크 |
- Backend: FastAPI, SQLAlchemy, PostgreSQL (Neon) + pgvector
- Frontend: Vanilla JS, CSS
- AI/NLP: Google Gemini 2.5-flash, Sentence Transformers (
dragonkue/multilingual-e5-small-ko), kiwipiepy (Korean NLP) - Crawling: BeautifulSoup4, Selenium, Requests, httpx
- Infra: Google Cloud Run, Cloud Build, Cloud Scheduler
- Container: Docker
# AI 메시지 및 LLM 파싱 테스트 (400+ 케이스)
python tests/ai_message_and_llm.py
# 종합 파싱 테스트 (1,583+ 케이스)
python tests/comprehensive_parsing.py현재 2,000+ 테스트 케이스 (AI 메시지/LLM 400+ + 종합 파싱 1,583+)
- 전국 시도/시군구/랜드마크/지하철역 커버리지 (단일 + 2개 지역 복합)
- 단일 지역+조사 637, 복합 지역 296, 키워드 150, 노이즈 워드 134, 날짜 파싱 94, 복합 조합 133, 엣지 케이스 139
- 테스트 케이스의 약 70%가 조사 포함 자연어 형태 (
~에서,~에 있는,알려줘,추천해줘등)
MIT License
Made with care for better local culture exploration