지구를 모니터링하는 법
숙지빈 폴더에서 superpowers 워크플로로 설계하고 Phase 단위로 빌드합니다. 2시간 트랙이라 키 발급이 없고(USGS는 공공 피드), 마감은 로컬이 아니라 CloudFront 실배포입니다. 데이터는 실제 지구의 기록이며, 수집 주기마다 바뀝니다.
| 레이어 | 구성 | 핵심 원칙 |
|---|---|---|
| 수집 | USGS 2.5_day.geojson을 60초 폴링 | 키 없음, 피드 하나가 전체 상류. 비정상 필드는 다듬고 계속 |
| 저장 | 이벤트 id 멱등 딕셔너리(최대 500건) | 같은 지진은 한 번만, 신규 진입을 사이클마다 기록 |
| 화면 | 등장방형 세계지도 SVG + 필터 + 테이블 | 좌표 변환 두 줄이 지도의 전부, 라이브러리 없음 |
| AI와 개장 | REST 직접 브리핑, CDK로 CF → ALB → Fargate | Bearer는 Secrets로 기동 주입, 이미지에 비밀 없음 |
1. 워치 (메인 화면)
| 기능 | 설명 |
|---|---|
| 세계지도 | 진앙을 원으로: 반지름은 규모, 색은 깊이(얕음 주황 → 깊음 보라), 최근 1시간은 펄스 |
| 통계 바 | 24h 건수, 최대 규모, 활동 최다 지역, 마지막 수집 시각 |
| 이벤트 테이블 | 시각(KST), 규모, 지역, 깊이 - 최신순, 지도와 같은 데이터 |
| 필터 | 규모 슬라이더(2.5~6.0)와 시간 창(6h/24h), 듀얼 테마 |
2. 해설과 개장
| 기능 | 설명 |
|---|---|
| 브리핑 | "지난 24시간, 지구는" - 전체 흐름, 주목 이벤트 2~3개, 활동 급증 지역, 한국어 |
| 브리핑 캐시 | 같은 10분 버킷 동안 재사용, LLM 호출은 아껴 쓴다 |
| 실배포 | CloudFront → prefix list SG + X-Origin-Verify → ALB → Fargate |
| 피날레 | CloudFront URL을 Slack에, 누구나 열리는 내 지진 워치 |
이 캡스톤을 마치면 다음 네 가지를 설명할 수 있고, 다시 만들 수 있습니다.
| 목표 | 검증되는 순간 |
|---|---|
| 이벤트 id 기반 멱등 수집을 설계하는 법 | M2에서 같은 지진이 폴링마다 와도 스토어가 한 건만 유지할 때 |
| 지리 좌표를 화면 좌표로 바꾸는 최소 수학 | M3에서 변환 두 줄로 진앙이 제자리에 찍힐 때 |
| 수치 데이터를 서사로 푸는 브리핑 프롬프트 | M4에서 통계 요약이 읽고 싶은 해설로 돌아올 때 |
| 컨테이너 앱을 CF → ALB → Fargate로 개장하는 법 | 내 CloudFront URL이 다른 사람 브라우저에서 열릴 때 |
| 챕터 | 이 캡스톤에서 재등장하는 곳 |
|---|---|
| Ch1 설치부터 Headless까지 | M0~전체: 세션 운용, 지시 → 실행 → 검증 루프 |
| Ch2 Subagents | M2~M3: 수집기 리뷰를 code-reviewer에게, 지도와 테이블 병행 |
| Ch3 Admin Setup | M4: Bearer 자격과 Secrets 주입이 도는 통제선의 배경 |
| Ch4 Settings, 훅과 스킬 | OPT: 하네스 성숙화, 훅과 스킬의 프로젝트 자산화 |
| Ch5 CLI Reference | 확장 방향: 수집 상태 점검을 헤드리스 파이프라인으로 |
| Ch6 Agent SDK | 확장 방향: 큰 지진 감지 시 브리핑을 코드에서 부르는 알림 봇 |
구현 청사진
참고M1 설계가 이 구조로 수렴하도록 방향을 잡아 주는 구현 지도입니다. 계획이 이상하게 흐르면 여기로 돌아와 대조하세요.
[수집 루프] 60초마다
Scheduler ──> Collector: GET 2.5_day.geojson ──> 정규화(id, mag, place, time, lon/lat/depth)
│
Event Store <── 멱등 upsert(같은 id는 한 번만) ──+── 신규 진입 id 기록
[조회 경로] 브라우저가 당길 때
Browser ── fetch ──> FastAPI: /api/quakes?hours=&min_mag= ──> 필터 + 통계 + 목록
^ (USGS를 직접 치지 않는다)
+── 지도 원(반지름=규모, 색=깊이) + 테이블 ────────────────────────+
[출고 경로] 개장
docker build ──> ECR push ──> CDK: CloudFront ──> (prefix list SG + X-Origin-Verify) ALB ──> Fargate
Secrets Manager ──(기동 주입)──> AWS_BEARER_TOKEN_BEDROCK
| 항목 | 내용 |
|---|---|
| 피드 URL | earthquake.usgs.gov/earthquakes/feed/v1.0/summary/2.5_day.geojson (규모 2.5+, 최근 24h) |
| 이벤트 키 | feature.id (예: us7000abcd) - 멱등의 기준, 같은 지진은 갱신만 |
| 핵심 필드 | properties.mag / place / time(UTC 밀리초), geometry.coordinates = [경도, 위도, 깊이km] |
| 좌표 변환 | x = (경도+180)/360 × 지도폭, y = (90-위도)/180 × 지도높이 (등장방형) |
| 폴링 예산 | 정적 피드라 사실상 한도 없음, 그래도 60초면 충분하고 캐시 친화적 |
| 선택 | 이유 |
|---|---|
| 이벤트 id 멱등 스토어 | 피드는 24h 창이라 매 폴링 대부분이 재등장, 키 기반 upsert가 시계열을 오염에서 지킨다 |
| 등장방형 투영 | 두 줄짜리 수학으로 충분히 정확한 세계지도, 라이브러리는 과잉 |
| Bedrock REST 직접 | SDK 없이 Authorization 헤더 + JSON, "LLM 호출은 결국 HTTP" |
| CF → ALB → Fargate 개장 형식 | prefix list SG와 X-Origin-Verify 이중 방어를 갖춘, 공개 URL까지 가는 최단 경로 |
사전 준비
5분이 트랙은 외부 키 발급이 없습니다. Bedrock Bearer와 AWS 자격만 확인하면 출발입니다.
mkdir -p ~/capstone/capstone-5 && cd ~/capstone/capstone-5
set -a; source ~/capstone/.env; set +a # AWS_BEARER_TOKEN_BEDROCK 로드
python3 -c "import os; print('Bearer:', 'OK' if os.environ.get('AWS_BEARER_TOKEN_BEDROCK') else 'MISSING')"
aws sts get-caller-identity --query Account --output text # 배포용 자격 확인
설계, 브레인스토밍
15분수집/저장/지도/브리핑/배포까지 요구 9조항으로 넘깁니다.
이 미션의 바탕 학습: Ch1의 세션 운용과 Capstone Setup의 superpowers 워크플로.
claude
/superpowers:brainstorming 다음 요구사항으로 "Quake Watch", 전 세계 실시간 지진 모니터를 설계하자.
1) 구조: backend/(Python 3.11+ FastAPI, app/collector 수집, app/store 스냅샷, app/api 라우트,
app/llm 브리핑, config.py) + frontend/index.html(빌드 없는 정적 SPA 한 장). FastAPI가 정적 파일을
함께 서빙해 :8000 하나로 완결. 컨테이너는 Dockerfile 한 장
2) 수집기: httpx로 USGS 피드 GET
https://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/2.5_day.geojson 을 60초 주기 폴링.
각 feature에서 id, properties.mag / place / time(UTC ms), geometry.coordinates[경도, 위도, 깊이km]만
정규화. 비정상 필드는 0 또는 "unknown"으로 다듬고 계속(부분 실패 격리)
3) 스토어: 이벤트 id를 멱등 키로 하는 인메모리 딕셔너리(최대 500건, 오래된 것부터 제거).
같은 id는 갱신만 하고 중복 삽입하지 않는다. 신규 진입 id 목록을 사이클마다 기록
4) API 계약: GET /healthz, GET /api/quakes?hours=24&min_mag=2.5 (시간 창과 규모 필터,
최신순 목록 + 요약 통계: 건수, 최대 규모, 활동 최다 지역), POST /api/brief (아래 6)
5) 화면(텍스트 스펙): 라이트/다크 듀얼 테마(토글 + localStorage). 상단 통계 바(24h 건수, 최대 규모,
최다 지역, 마지막 수집 시각). 중앙에 등장방형(equirectangular) 세계지도, 외부 라이브러리 없이
인라인 SVG: 대륙 윤곽은 간이 폴리라인이면 충분, 진앙은 원으로 - 반지름은 규모에 비례(mag*2.2px),
색은 깊이(얕음 주황 → 깊음 보라), 최근 1시간 이벤트는 펄스 애니메이션. 좌표 변환은
x=(경도+180)/360*폭, y=(90-위도)/180*높이. 하단에 최근 이벤트 테이블(시각 KST 변환, 규모, 지역, 깊이)과
규모 필터 슬라이더(2.5~6.0)
6) 브리핑(LLM): Bedrock Converse를 SDK 없이 httpx 직접 REST로.
POST https://bedrock-runtime.ap-northeast-2.amazonaws.com/model/global.anthropic.claude-sonnet-4-6/converse
Authorization: Bearer(환경변수 AWS_BEARER_TOKEN_BEDROCK), maxTokens 700.
입력은 최근 24h 요약 통계와 상위 이벤트 목록, 출력은 한국어 "지난 24시간, 지구는" 브리핑:
전체 흐름 한 문단, 주목 이벤트 2~3개 해설, 활동 급증 지역. 같은 10분 버킷 동안 결과 캐시
7) 키의 거처: AWS_BEARER_TOKEN_BEDROCK은 환경변수로만. USGS는 키가 없다, 화면은 우리 API만 본다
8) 배포: CDK로 CloudFront → (prefix list SG + X-Origin-Verify) ALB → ECS Fargate 를 구성해 배포한다. Secrets Manager가 Bearer를 기동 시 주입, 이미지에 비밀 없음
9) 구현 순서: Phase 1(수집기 + 스토어 + /api/quakes, curl 검증) → Phase 2(지도 SVG + 테이블 + 필터)
→ Phase 3(브리핑 + 캐시) → Phase 4(cdk deploy와 CloudFront 개장). 시간 제약 2시간:
테스트 생략, curl과 브라우저로 검증. 결정이 필요하면 이 범위 안에서 최소한으로만 물어봐.
auto mode on을 선언해 두면 superpowers가
brainstorming → write-plan → execute-plan의 단계 전환과 승인 대기를 자동으로 이어 갑니다.
시간이 빠듯할 때 유용합니다. 되돌리려면 auto mode off입니다.
/superpowers:write-plan으로 Phase 4개 계획을 승인하고, 위 구현 청사진과 대조하세요.
Phase 1, 수집기와 스토어
30분피드가 60초마다 돌고, 이벤트가 id 멱등으로 쌓이고, /api/quakes가 필터와 통계를 얹어 주면 끝입니다.
이 미션의 바탕 학습: Ch1의 지시 → 실행 → 검증 루프. 여유가 있다면 Ch2의 code-reviewer에게 수집기 리뷰를 맡겨 보세요.
cdk deploy는 첫 회 12~15분이 걸립니다. Phase 1을 시작하자마자
별도 터미널에서 인프라 배포를 먼저 걸어 두세요(앱 이미지는 나중에 갱신). 빌드와 배포 대기가
겹치면 M4는 이미지 푸시와 서비스 갱신만 남아 10분 안에 개장합니다.
cd ~/capstone/capstone-5 && npx aws-cdk@2 deploy --require-approval never # Claude가 infra를 만든 직후 실행
/superpowers:execute-plan Phase 1만 구현하고 멈춰줘. backend venv + requirements(fastapi, uvicorn, httpx, apscheduler), USGS 수집기(60초, 정규화, 부분 실패 격리), 이벤트 id 멱등 스토어(최대 500), /api/quakes(hours, min_mag 필터 + 통계)와 /healthz. Dockerfile과 infra(CDK: CloudFront + prefix list SG + X-Origin-Verify + ALB + Fargate + Secrets)까지 골격 생성. uvicorn 실행과 curl 검증 방법 포함.
cd backend && .venv/bin/uvicorn app.main:app --port 8000 &
sleep 5
curl -s "localhost:8000/api/quakes?hours=24&min_mag=2.5" | python3 -c "import sys,json; d=json.load(sys.stdin); print('건수:', d['stats']['count'], '| 최대:', d['stats']['max_mag'])"
Phase 2, 세계지도
40분변환 두 줄로 진앙이 제자리에 찍히고, 규모 슬라이더와 테이블까지 살아나면 워치가 완성됩니다.
이 미션의 바탕 학습: Ch2의 병렬 감각. 지도를 그리는 동안 테이블과 필터를 서브에이전트로 병행할 수 있습니다.
/superpowers:execute-plan Phase 2를 구현해줘. frontend/index.html 정적 SPA 한 장: 요구 5의 화면 스펙(듀얼 테마, 통계 바, 등장방형 세계지도 SVG - 간이 대륙 윤곽 + 진앙 원(반지름=규모, 색=깊이) + 최근 1시간 펄스, KST 이벤트 테이블, 규모 슬라이더와 시간 창 토글). FastAPI 정적 서빙 mount까지. 외부 지도/차트 라이브러리 금지.
# 브라우저에서 /proxy/8000/ 열기, 슬라이더를 움직이면 지도와 테이블이 함께 걸러진다
Phase 3 + 4, 브리핑과 개장
30분숫자가 읽고 싶은 해설이 되고, 내 CloudFront URL이 열리면 미션 완료입니다.
이 미션의 바탕 학습: Ch3에서 본 Bedrock 자격과 Secrets 주입의 배경, 그 위에서 도는 REST 계약.
/superpowers:execute-plan Phase 3(브리핑)을 구현해줘. llm 모듈: httpx POST converse(Bearer, maxTokens 700, 타임아웃 504/파싱 502 매핑), POST /api/brief - 입력은 24h 통계와 상위 이벤트, 출력은 요구 6의 한국어 브리핑, 10분 버킷 캐시. 화면 브리핑 카드와 버튼까지.
/superpowers:execute-plan Phase 4(개장)를 진행해줘. 선배포한 인프라에 앱 이미지를 빌드/푸시하고 서비스 갱신, CloudFront 도메인 출력. X-Origin-Verify와 Secrets 주입이 살아 있는지 확인 절차 포함.
DOMAIN=$(aws cloudformation describe-stacks --stack-name QuakeWatch --query "Stacks[0].Outputs[?OutputKey=='CloudFrontDomain'].OutputValue" --output text)
curl -s "https://$DOMAIN/healthz" && echo " <- 열렸습니다, 이 URL을 Slack에"
워치 퀄리티 강화 라운드, 감각을 올려라
+α 자유워치가 열렸다면 이제 워치를 내 것으로 만드는 시간입니다. 강화 카드 중 끌리는 것을 골라 하나씩 주문하세요. 요령은 같습니다: 한 번에 하나, 화면으로 확인하고, 마음에 들면 커밋과 재배포.
지역 프리셋을 추가하자. 헤더에 전체 / 환태평양 / 일본 주변 / 한반도 주변 버튼:
각 프리셋은 경위도 바운딩 박스로 필터하고, 지도는 그 영역을 확대(viewBox 전환)한다.
프리셋 선택은 통계 바와 테이블에도 함께 반영, localStorage에 기억.
지각판 경계를 지도에 겹치자. 판 경계의 간이 폴리라인 데이터를 plates.js에 상수로 내장하고
(정밀할 필요 없음, 주요 경계 윤곽이면 충분) 얇은 점선 레이어로 그린다.
토글 버튼으로 켜고 끄기. 진앙이 경계를 따라 늘어서는 그림이 이 카드의 보상이다.
새로고침 사이의 변화를 보여주자. /api/quakes 응답에 신규 진입 id 목록(new_ids)을 포함하고,
화면은 직전 응답과 비교해 새 이벤트에 NEW 배지와 3초 하이라이트를 준다.
헤더에 "이번 사이클 신규 N건" 카운터. 지구가 방금 움직였다는 감각을 화면에.
깊이 단면을 추가하자. 지도 아래에 가로축 = 경도, 세로축 = 깊이(0~700km 로그 스케일)인
산점 SVG를 하나 더: 같은 데이터를 다른 축으로 보면 섭입대의 경사가 드러난다.
지도의 필터와 완전히 연동, 점 호버 시 규모와 지역 툴팁.
여기부터는 정해진 카드가 없습니다. 당신이 관측소장입니다. 지도를 보며 궁금했던 것 하나를 골라, 위 카드들처럼 직접 요구사항을 써서 주문하세요. 아이디어가 필요하면: 시간대별 히스토그램, 규모-빈도 분포(구텐베르크-리히터), 최근 7일 창 전환(2.5_week.geojson), 진앙 클릭 → USGS 상세 링크, 소리 알림, DynamoDB로 스토어 승격(TTL 24시간이면 "창" 개념이 테이블 차원에서 재현되고, 재배포와 다중 태스크에도 상태가 유지됩니다).
내 워치에 [내가 원하는 기능]을 추가하자. 규칙:
1) 데이터: [필요한 필드와 출처, 피드가 달라지면 URL 명시]
2) 화면: [어디에 어떻게 보이는지]
3) 계약: 화면은 우리 API만 본다, 멱등 스토어 원칙은 깨지 않는다
4) 배포: 완성되면 재배포까지
구현 전에 이 규칙을 네 계획으로 다시 말해주고, 승인하면 진행해.
하네스 엔지니어링
+15분완주한 프로젝트를 표준 하네스로 성숙시킵니다 (project-init으로 구조를 잡고 harness-eval로 6개 차원 등급을 받습니다).
이 미션의 바탕 학습: Ch4의 훅과 스킬을 팀 자산으로 승격하는 감각, 그리고 Reference 3의 플러그인 생태계.
/project-init:init-project .
/harness-eval:quick
등급표에서 낮은 차원 하나를 골라 /add-adr 또는 /add-runbook으로 보강하고 재평가해 보세요.
미션 종료
피드는 창으로, 저장은 멱등하게, 좌표는 함수로, 숫자는 서사로. 그리고 개장은 CloudFront로.
| 가져가는 것 | 내용 |
|---|---|
| 창 피드의 멱등 upsert | 재등장과 값 갱신이 기본인 상류에서 id 규율이 데이터를 지킨다 |
| 투영의 최소 수학 | 지도 = (lon, lat) → (x, y) 함수, 두 줄이면 세계가 SVG에 실린다 |
| 절제된 브리핑 | 재료, 형식, 금지를 계약한 프롬프트가 품격 있는 해설을 만든다 |