Open Net Worth
Open Net Worth is an open-source personal AI wealth assistant you run yourself: one ledger for your whole financial life, worked by an AI agent you control.
翻译准备中
尚未检测到更新。GitHub 发布版本后会显示在这里。
个人 GitHub 账号下公开仓库的所有者可以登录并验证所有权,自行管理介绍,无需管理员批准。
登录并生成验证文件,在 30 分钟内将其提交到此仓库的默认分支。其他分支或分叉仓库中的文件无效。然后在下方检查文件,即可无需管理员批准直接管理项目。请保留文件以维持管理权限。
如需登录,将前往登录页面。登录后请回到此处,再次点击所选的验证按钮。
在项目文件夹中运行 /nomorevibe verify,即可验证所有权并自行管理介绍。
一分钟完成验证正在加载完整 README。 以下为已保存的摘录。
실제로 매일 바뀌는 값(엔화→원 환율, 100엔당 원화)을 매일 자동으로 기록하고, 어제와 비교하며, 데이터가 오지 않을 때도 마지막 정상값을 지키며 정직하게 설명하는 무로그인 공개 정보판입니다. 카드 1(매일 궁금한 값 하나), 카드 2(비밀 없는 호출), 카드 3(다섯 가지 실패), 카드 4(하루 한 줄), 카드 5(실제 이틀과 어제 대비)까지 반영되어 있습니다. 카드 3부터 데이터 모델이 ALEPH 공개 fixture 계약(aleph-t04-real-information-board-public-contract-v2)과 같은 모양으로 바뀌었습니다 — 자세한 내용은 아래 "카드 3" 절 참고. 카드 5는 실제 배포된 사이트가 2026-09-17~09-21 동안 매일 정상 수집한 진짜 값 5건으로 검증되었습니다. data/history.json은 더 이상 빈 상태가 아니며, scripts/verify_day_pair.py가 가장 최근 인접한 두 실제 기록(09-20 → 09-21)의 출처·값·단위 일치와 독립 재계산 결과를 PASS로 확인했습니다. 자세한 내용은 아래 "카드 5" 절 참고. 카드 1 통과 기준 — 화면 어디서 확인하나查看完整 README ↗
실제로 매일 바뀌는 값(엔화→원 환율, 100엔당 원화)을 매일 자동으로 기록하고, 어제와 비교하며, 데이터가 오지 않을 때도 마지막 정상값을 지키며 정직하게 설명하는 무로그인 공개 정보판입니다.
카드 1(매일 궁금한 값 하나), 카드 2(비밀 없는 호출), 카드 3(다섯 가지 실패), 카드 4(하루
한 줄), 카드 5(실제 이틀과 어제 대비)까지 반영되어 있습니다. 카드 3부터 데이터 모델이
ALEPH 공개 fixture 계약(aleph-t04-real-information-board-public-contract-v2)과 같은
모양으로 바뀌었습니다 — 자세한 내용은 아래 "카드 3" 절 참고.
카드 5는 실제 배포된 사이트가 2026-09-17~09-21 동안 매일 정상 수집한 진짜 값 5건으로 검증되었습니다.
data/history.json은 더 이상 빈 상태가 아니며,scripts/verify_day_pair.py가 가장 최근 인접한 두 실제 기록(09-20 → 09-21)의 출처·값·단위 일치와 독립 재계산 결과를 PASS로 확인했습니다. 자세한 내용은 아래 "카드 5" 절 참고.
| 기준 | 내용 | 확인 위치 |
|---|---|---|
| T04-C01 | 결과물·소스 URL이 로그인/인증/CAPTCHA 없이 시크릿 창에서 열림 | Vercel 정적 배포 + 공개 GitHub 저장소 (배포 후 직접 시크릿 창에서 확인 필요) |
| T04-C03 | 비개인 공개 원천의 실제 동적 값을 조회 | open.er-api.com (ExchangeRate-API, 무료·무로그인) |
| T04-C04 | 값 표시 | 상단 카드 "100엔 = 871.77원" |
| T04-C05 | 단위 표시 | 상단 카드 "단위: KRW / 100 JPY" |
| T04-C06 | 출처 표시 | 상단 카드 "출처: ExchangeRate-API" (원문 링크 포함) |
| T04-C07 | 출처 시각 표시 | 상단 카드 "출처 시각(KST)" — API의 time_last_update_utc를 KST로 환산 |
| T04-C08 | 조회 시각 표시 | 상단 카드 "조회 시각(KST)" — 수집 스크립트 실행 시각 |
| T04-C09 | 기준 시간대 표시 | 페이지 상단 배너 "기준 시간대: Asia/Seoul (KST, UTC+9)" |
| T04-C10 | 정상 1건의 원자료·저장값·화면값 일치 | "정상값 증빙" 섹션 — API 원문 / history.json 저장값 / 화면 표시 문장을 나란히 대조 |
| T04-C25 | 개인정보 0건 | 페이지·수집 과정에 로그인·쿠키·개인 식별 정보 없음 |
| T04-C26 | 5개 실패 재생은 합성 시험값만 사용 | "실패 시나리오 합성 재생 기록" 섹션 — 실제 네트워크를 타지 않고 코드로 조립한 값만 사용 |
설계 자체가 비밀키를 쓰지 않습니다. open.er-api.com은 API 키가 필요 없는 무료
공개 엔드포인트이고, 브라우저(app.js)는 이 외부 API를 절대 직접 호출하지 않습니다 —
같은 오리진에 있는 정적 파일 data/history.json, data/failure_replay.json만
fetch()로 읽습니다. 외부 API 호출은 오직 GitHub Actions 서버에서 scripts/collect_rate.py가
수행하는데, 이 스크립트도 urllib만 쓰고 어떤 키/토큰도 요구하지 않습니다. 즉 브라우저
개발자 도구의 Network 탭에는 open.er-api.com으로 가는 요청 자체가 아예 없습니다
(있다면 그게 오히려 설계에서 벗어난 것입니다).
주장으로 끝내지 않고 scripts/check_secrets.py로 실제 검색한 결과 (커밋 123bb63 기준 —
커밋을 더 쌓았다면 제출 전 python3 scripts/check_secrets.py로 최신 결과를 다시 남기세요):
=== 카드 2 — 비밀값 검색 결과 (T04-C11) ===
[1] 작업 트리(배포 파일 전체) 스캔: 텍스트 파일 10개 검사
→ 비밀값 패턴 0건
[2] git 커밋 기록 스캔: 커밋 1개 전체(git log -p) 검사
→ 비밀값 패턴 0건
[3] .env류 파일 존재 여부: 없음
=== 결과: 비밀값 의심 패턴 총 0건 ===
이 스크립트는 API 키/토큰/비밀번호/AWS 키/JWT/개인키 블록 등 "비밀값처럼 생긴 문자열"을
정규식으로 찾고, YOUR_API_KEY 같은 플레이스홀더는 오탐으로 치지 않습니다. 실제로 비밀값을
심어서 정상적으로 잡아내는지도 확인했습니다(가짜 API_KEY = "sk_live_..." 삽입 → 탐지 성공).
.github/workflows/secret-scan.yml이 매 push마다 이 스크립트를 자동으로 돌려서, 앞으로
누군가 실수로 키를 커밋해도 그 커밋에서 바로 워크플로가 실패하도록 했습니다(사후 발견이
아니라 사전 차단). GitHub에 올린 뒤에는 아래도 직접 한 번씩 확인해서 "남길 것"에 채워 넣으세요.
python3 scripts/check_secrets.py 실행 결과 (커밋을 더 쌓았다면 그 시점 기준으로 다시 실행)open.er-api.com으로 가는 요청이 하나도 없고 자기 도메인의 data/*.json만 보이는지 확인git log --all -p | grep -iE "key|secret|token|password" 를
로컬에서 돌려서 스크립트와 별개로 눈으로도 한 번 훑기ALEPH가 배포한 aleph-t04-real-information-board-public-contract-v2
(t04-real-information-board-public-v1.zip, asset-manifest.json)를 받아서 17개 파일
전부 SHA-256을 직접 계산해 manifest와 대조했습니다. 전부 일치했고(바이트 수·해시 모두),
압축 안에 들어있는 asset-manifest.json 사본도 배포된 원본과 완전히 동일했습니다.
OK README.md OK fixtures/rate-429.json
OK adapter-reset.example.js OK fixtures/recover-d2.json
OK criterion-registry.json OK fixtures/schema-break.json
OK fixture-manifest.json OK fixtures/timeout.json
OK fixture.schema.json OK normalized-reading.schema.json
OK fixtures/auth-401.json OK public-contract.json
OK fixtures/normal-d1-a.json OK reading-status.schema.json
OK fixtures/normal-d1-b.json
OK fixtures/normal-d2.json → ALL MATCH (17/17)
OK fixtures/offline.json
adapter/reading-store.js가 이 카드의 핵심입니다. ALEPH가 준 참조 구현
adapter-reset.example.js를 이 저장소로 옮겨온 것으로, 정규화 검증 → 저장 → 상태 전이
로직이 동일합니다. 핵심 규칙 두 가지:
applySuccessfulReading): signal_id + record_date가 같으면 새 행을 만들지 않고
그 행을 갱신합니다(멱등). 날짜가 다르면 새 행이 생깁니다. 전일 대비 변화값은 저장된 두 값을
다시 계산해서 만듭니다.applyError): daily_readings(일별 기록)를 절대 건드리지 않습니다. 상태만
{freshness: "stale", error_code: "..."}로 바뀔 뿐이라서, 마지막 정상값이 실패로 지워지는
경로 자체가 없습니다(T04-C17). "오래된 값" 표시는 상태가 stale일 때 화면이 마지막 행에
붙이는 배지입니다(T04-C18).오류 코드는 정확히 5종으로 고정됩니다: timeout / auth(401·403) / rate_limit(429) /
offline / schema_error. 이 5종은 scripts/collect_rate.py(실제 라이브 수집)와
adapter/reading-store.js(재생용) 양쪽에서 똑같이 쓰는 분류 체계입니다.
언어를 통일하지 않은 이유를 밝힙니다. ALEPH의 참조 구현은 CommonJS(Node)이고, 이 저장소의
실제 라이브 수집기는 카드 1부터 Python(GitHub Actions에서 무의존성으로 돌리기 위함, 사용자의
주 언어이기도 함)입니다. README가 권장하는 "live adapter와 replay adapter가 같은 함수를
호출"하는 정도까지 완전히 통일하려면 라이브 수집기를 Node로 다시 써야 하는데, 카드1·2에서
이미 검증해둔 Python 파이프라인을 갈아엎는 위험을 감수할 만큼 지금 시점에 꼭 필요하지는
않다고 판단했습니다. 대신 **두 구현이 정확히 같은 상태 모델(정규화 9개 필드, daily_readings
status 구조)과 같은 5종 오류 코드 분류 규칙을 쓰도록 맞추고, 각각 독립적으로 검증**했습니다
— JS 쪽은 공식 fixture 9개 전부 재생해서, Python 쪽은 5종 시뮬레이션과 실제 API 성공 응답
샘플 파싱으로. 코드를 공유하진 않지만 동작은 같다는 걸 양쪽에서 직접 확인한 상태입니다. 이
트레이드오프가 마음에 안 들면 다음 카드로 넘어가기 전에 라이브 수집기를 Node로 옮기는 것도
가능하니 말씀해주세요.scripts/replay_fixtures.js로 공식 재생 순서(정상 시퀀스, 실패 5종 각각의 baseline+실패,
복구 시퀀스)를 그대로 실행한 결과입니다 — 외부 네트워크 없이 fixture만으로 100% 결정론적:
--- 정상 저장 시퀀스 (T04-C20, C21) ---
[PASS] T04-NORMAL-D1-A — 새 일별 행을 만든다.
[PASS] T04-NORMAL-D1-B — 새 행 없이 같은 행을 갱신한다.
[PASS] T04-NORMAL-D2— 새 행과 전일 대비 +15를 만든다.
--- 실패 재생 — T04-TIMEOUT (느린 응답, C12) --- [PASS] stale/timeout, 마지막 정상값 105 유지
--- 실패 재생 — T04-AUTH-401 (401 거절, C13) --- [PASS] stale/auth, 마지막 정상값 105 유지
--- 실패 재생 — T04-RATE-429 (호출 제한, C14) --- [PASS] stale/rate_limit, 마지막 정상값 105 유지
--- 실패 재생 — T04-OFFLINE (오프라인, C15) --- [PASS] stale/offline, 마지막 정상값 105 유지
--- 실패 재생 — T04-SCHEMA-BREAK (형식 변경, C16) --- [PASS] stale/schema_error, 마지막 정상값 105 유지
--- 오류 뒤 복구 시퀀스 (T04-C19) ---
[PASS] T04-TIMEOUT — stale/timeout, 행 1개, 정상값 105
[PASS] T04-RECOVER-D2 — fresh/none으로 회복, 행 2개(다음 날짜 신규 1건), 정상값 120, 전일 대비 +15
=== 전체 결과: 모두 PASS ===
(전체 원문은 로컬에서 node scripts/replay_fixtures.js --fixtures-dir <공식 패키지의 fixtures 폴더>로 그대로 재현할 수 있습니다. 공식 fixture 파일 자체는 ALEPH가 배포한
과제 자료라 이 저장소에는 포함하지 않았습니다.)
상태가 stale이면 상단 카드에 마지막 정상값이 "오래됨(stale)" 배지와 함께 보존되어 있고,
그 아래 "↻ 다시 시도" 버튼이 뜹니다. 이 버튼은 저장소의 GitHub Actions 워크플로 실행
페이지로 연결됩니다 — 정적 사이트라 브라우저에서 직접 재시도 API를 만들 수는 없지만(그러려면
GitHub 토큰을 브라우저에 넣어야 해서 카드 2 원칙에 어긋납니다), 재시도 행동 자체는 항상
눈에 보이고 실제로 그 워크플로를 실행하면 진짜로 재수집이 일어납니다.
| 기준 | 확인 위치 |
|---|---|
| T04-C12 (타임아웃) | replay_fixtures.js T04-TIMEOUT PASS, 화면은 실패 5종 재생 카드의 "타임아웃" |
| T04-C13 (401/403) | replay_fixtures.js T04-AUTH-401 PASS, 화면 "인증 거절(401/403)" |
| T04-C14 (호출 제한) | replay_fixtures.js T04-RATE-429 PASS, 화면 "호출 제한(429)" |
| T04-C15 (오프라인) | replay_fixtures.js T04-OFFLINE PASS, 화면 "오프라인" |
| T04-C16 (형식 변경) | replay_fixtures.js T04-SCHEMA-BREAK PASS, 화면 "응답 형식 변경" |
| T04-C17 (정상값 안 지워짐) | applyError가 daily_readings 미변경 — 5종 전부 PASS에서 row_count 유지로 확인 |
| T04-C18 (오래된 값 표시) | 상단 카드 "오래됨(stale)" 배지 |
| T04-C19 (재시도 행동 + 복구) | 상단 카드 "↻ 다시 시도" 버튼 + replay_fixtures.js 복구 시퀀스 PASS |
① 일별 고유키와 갱신 규칙
scripts/collect_rate.py의 record_id_for(reading)가 그 키입니다:
def record_id_for(reading):
return f"{reading['signal_id']}-{reading['record_date']}"
record_date는 fetched_at(조회 시각)을 기준 시간대(Asia/Seoul)로 환산한 날짜입니다
(kst_date_of()). apply_successful_reading()은 저장 직전에 같은 signal_id +
record_date를 가진 행이 이미 있는지 찾고,
record_id 유지, first_fetched_at은
그대로 두고 last_fetched_at만 갱신) → 행 수 그대로 (T04-C20)이건 새로 만든 로직이 아니라, 카드 3에서 이미 짜뒀던 함수입니다 — 공식 fixture 시퀀스
자체가 "D1-A(같은 날 1번째) → D1-B(같은 날 2번째, 값만 다름) → D2(다음 날)"로 구성되어
있어서, replay_fixtures.js가 이미 이 규칙을 전제로 9개 fixture를 채점하고 있었습니다.
카드 4는 그 규칙을 라이브 수집기(Python) 쪽에서 직접, 합성 시계로 다시 한번 검증한
카드입니다.
② 합성 시계 시험 — scripts/test_same_day_dedup.py
실제 네트워크나 data/history.json을 건드리지 않고, collect_rate.py가 실제로 쓰는
parse_to_reading / apply_successful_reading 함수를 그대로 불러와, 아래 4개 가상
조회 시각(합성 시계)을 순서대로 먹입니다:
| 순서 | 조회 시각(KST) | 날짜(KST) | 원문 rates.KRW |
|---|---|---|---|
| A | 2026-09-16 00:10 | 2026-09-16 | 8.60 |
| B (재실행) | 2026-09-16 09:00 | 2026-09-16 | 8.65 |
| C (재실행) | 2026-09-16 23:55 | 2026-09-16 | 8.70 |
| D | 2026-09-17 00:15 | 2026-09-17 | 8.75 |
실행 결과(같은 날 재실행 전후 행 수, "남길 것" 항목):
[A (같은 날 1번째)] fetched_at=2026-09-16T00:10:00+09:00 record_date=2026-09-16 행수 0 -> 1 (저장값 860.0)
[B (같은 날 2번째, 재실행)] fetched_at=2026-09-16T09:00:00+09:00 record_date=2026-09-16 행수 1 -> 1 (저장값 865.0)
[C (같은 날 3번째, 재실행)] fetched_at=2026-09-16T23:55:00+09:00 record_date=2026-09-16 행수 1 -> 1 (저장값 870.0)
[D (다음 날 1번째)] fetched_at=2026-09-17T00:15:00+09:00 record_date=2026-09-17 행수 1 -> 2 (저장값 875.0)
=== 결과: PASS ===
같은 날짜(2026-09-16) 3번 성공 → 행 수: 1 -> 1 -> 1 (변화 없음, T04-C20)
다음 날짜(2026-09-17) 성공 → 행 수: 1 -> 2 (+1, T04-C21)
증거 저장: data/same_day_dedup_evidence.json
같은 날 3번 재실행하는 동안 행 수는 계속 1로 유지되면서도 저장값은 매번 최신화됨(860 →
865 → 870, 그냥 무시된 게 아니라 실제로 덮어써졌다는 증거)을 확인했고, 다음 날짜 1번
성공에서만 행이 2건으로 늘었습니다. record_id도 A/B/C는 동일, D는 다르다는 것까지
스크립트 내부 assert로 확인합니다 — 하나라도 어긋나면 exit code 1로 실패합니다.
.github/workflows/dedup-test.yml이 push마다 이 스크립트를 실행해서, 앞으로 코드가
바뀌어도 이 규칙이 계속 지켜지는지 자동으로 감시합니다.
③ 통과 기준 매핑
| 기준 | 확인 위치 |
|---|---|
| T04-C20 (같은 날 여러 번 성공 → 1건) | test_same_day_dedup.py 단계 A/B/C, dedup-test.yml PASS |
| T04-C21 (다음 날 성공 → 새 1건) | test_same_day_dedup.py 단계 D, dedup-test.yml PASS |
카드 14는 합성 시계·합성 fixture로 로직을 시험했지만, 카드 5는 다릅니다. 실제
공개 원천(open.er-api.com)을 서로 다른 두 실제 날짜에 진짜로 조회한 값이 있어야만
완성되는 카드입니다. 배포 후 실제 5일(2026-09-1709-21)이 지나면서 data/history.json에
실제 정상 수신 기록이 쌓였고, 아래는 그 진짜 값으로 확인한 결과입니다. 값은 전혀
조작하지 않았습니다 — 개발 환경은 외부 네트워크에 접속할 수 없어 실제 값을 만들어낼
수 없었으므로(과제 지침의 "막히는 지점"이 말하는 상황 그대로), 실제 배포된 GitHub
Actions가 매일 수집한 값을 그대로 받아 적용했습니다.
① 실제 데이터
$ python3 scripts/verify_day_pair.py
[PASS] 이전 날 2026-09-20: 882.14 KRW/100JPY (출처 시각 2026-09-19T09:02:31+09:00)
[PASS] 최근 날 2026-09-21: 880.71 KRW/100JPY (출처 시각 2026-09-20T09:02:31+09:00)
재계산한 어제 대비 변화: -1.43KRW
저장된 last_delta(화면 '전일 대비' 표시값)와 일치: True
(전체 실제 기록 5건 중 가장 최근 인접한 두 건을 사용)
증거 저장: data/day_pair_evidence.json
daily_readings에는 실제로 서로 다른 5개 실제 날짜(09-17~09-21)가 쌓여 있어
T04-C22("서로 다른 실제 날짜 기록이 보존되어 있다")를 이미 넉넉히 만족합니다.
verify_day_pair.py는 그중에서도 가장 최근 인접한 두 건(어제·오늘)을 골라
대조합니다 — 이 두 건이 바로 collect_rate.py가 last_delta를 계산할 때 쓰는 짝이자,
사이트 상단 카드의 "전일 대비"가 실제로 표시하는 짝이기 때문입니다. 계속 자동 수집이
쌓여도(6일째, 7일째…) 이 스크립트는 항상 최신 인접 쌍으로 검증하므로 화면값과 어긋나지
않습니다.
② 독립 재계산 — scripts/verify_day_pair.py
합성값을 전혀 쓰지 않습니다. data/history.json에 실제로 쌓인 기록만 읽어서:
reading.source_url / reading.source_time / normalized_value / unit이
저장된 행(=화면에 표시되는 값)과 정확히 일치하는지 확인하고 (T04-C23) — 실제로
두 값 모두 https://open.er-api.com/v6/latest/JPY, 각각 2026-09-19T09:02:31+09:00 /
2026-09-20T09:02:31+09:00, 882.14 / 880.71 KRW/100JPY로 저장값·화면값이 일치함을
확인했습니다880.71 − 882.14 = -1.43을 다시 계산해서, collect_rate.py가
저장해둔 last_delta(-1.43)과 일치하는지 확인합니다 (T04-C24) — 일치실제 기록이 2건 미만일 때는 값을 지어내는 대신 "PENDING"으로 정직하게 보고하고 끝나도록 되어 있습니다(exit code 2) — 배포 직후에는 이 상태였고, 실제 이틀이 지나면서 위처럼 PASS로 바뀌었습니다.
③ 화면(app.js)에서의 독립 재계산
renderDayPairSection()이 data/history.json의 가장 최근 인접한 두 실제 기록을 골라
출처 URL·시각·값·단위를 나란히 보여주고, 최근 날 값 − 이전 날 값을 화면 스스로 다시
계산해 위 오늘 카드의 "전일 대비" 표시값과 일치하는지 초록 체크 표시로 확인해줍니다.
실제 배포 데이터로 로컬 서버(Playwright)에서 렌더링을 확인한 결과, 오늘 카드에는
"▲ -1.43원 (전일 대비)"(부호 없는 음수 표시), "실제 이틀 대조" 섹션에는 882.14 → 880.71
두 기록과 "✓ 위 오늘 카드의 "전일 대비" 표시값과 일치합니다"가 정확히 표시되었습니다.
④ 통과 기준 매핑
| 기준 | 확인 위치 | 실제 결과 |
|---|---|---|
| T04-C22 (서로 다른 실제 날짜 기록 보존) | data/history.json의 daily_readings | 5건 (09-17~09-21), 모두 서로 다른 실제 날짜 |
| T04-C23 (출처URL·시각·값·단위 저장값=화면값 일치) | 사이트 "실제 이틀 대조" 섹션, verify_day_pair.py | 일치 확인 (PASS) |
| T04-C24 (어제 대비 재계산이 화면값과 일치) | 사이트 "실제 이틀 대조" 섹션의 초록 체크, verify_day_pair.py의 recomputed_matches_stored_last_delta | true (재계산 -1.43 = 저장된 last_delta -1.43) |
| T04-C27 (확인 방법 4단 구분) | 이 README의 "확인 방법" 절 | 4단 구분 완료 |
| T04-C28 (제출문 3단 구분) | 이 README의 "AI와 나의 판단" 절 | 3단 구분 완료 |
index.html, style.css, app.js 세 파일이 전부입니다. React/Vue, 빌드 도구 없음.app.js는 페이지가 열리면 fetch()로 data/history.json, data/failure_replay.json을
읽어와서 그 내용으로 화면을 채웁니다. 백엔드 API 서버는 없고, 정적 JSON 파일만 읽습니다.time_last_update_utc, KRW 값)를 초록색으로 강조합니다.
즉 "화면값"이 "저장값"에서, "저장값"이 "원자료"에서 코드로 파생되는 과정이 그대로 보입니다.style.css는 CSS 변수로 색을 정의하고 prefers-color-scheme: dark로 다크 모드를 지원합니다.scripts/collect_rate.py 하나. urllib(HTTP), json, datetime, email.utils(RFC1123
시각 파싱), argparse만 사용합니다. 외부 라이브러리 설치 없음.{"result":"error", ...}인 경우까지 실패로 잡아냅니다
(실제 API가 종종 이렇게 "겉보기엔 정상, 속은 실패"로 응답하기 때문입니다).--simulate 실패 재생이 완전히 같은 파싱·저장 함수를 지나가므로,
"재생 화면은 그럴듯한데 실제 실패 시엔 다르게 동작한다"는 일이 생기지 않습니다.daily_readings + status{freshness, error_code})과 오류 코드 5종
(timeout/auth/rate_limit/offline/schema_error)은 아래 ④의 JS 어댑터와 동일한
규칙을 씁니다 — 언어는 다르지만 상태 모델은 하나입니다..github/workflows/collect-rate.yml, secret-scan.yml)data/*.json에 커밋합니다.
이 대화 세션이나 브라우저와 무관하게 GitHub 인프라에서 독립적으로 돌아갑니다.workflow_dispatch(수동 실행)에 failure_type 선택지가 있어 Actions 탭에서 5가지 실패를
언제든 재생할 수 있습니다.secret-scan.yml은 push마다 check_secrets.py를 돌려 비밀값 유입을 사전 차단합니다.adapter/reading-store.js, scripts/replay_fixtures.js)adapter/reading-store.js가 ALEPH 참조 구현을 옮겨온 상태기계이고, replay_fixtures.js가
그 상태기계에 9개 fixture를 공식 순서대로 먹이면서 기대값과 대조하는 채점기 겸 테스트입니다.fx-board/
├── index.html # 화면 구조
├── style.css # 스타일 (라이트/다크 모드 지원)
├── app.js # 데이터 fetch + 렌더링 + 증빙 대조 + 재시도 버튼
├── .gitignore # .env/키 파일류 방어적 제외
├── adapter/
│ └── reading-store.js # 재생용 상태기계 — ALEPH 공식 참조 구현 기반 (카드 3)
├── data/
│ ├── history.json # 일별 실제 수집 기록 (성공만 누적, 지금은 빈 상태)
│ ├── failure_replay.json # 5종 실패 합성 재생 기록 (자동 생성)
│ ├── same_day_dedup_evidence.json # 하루 한 줄 합성 시계 시험 증거 (카드 4)
│ └── day_pair_evidence.json # 실제 이틀 대조 증거 (카드 5, 실제 이틀 후 생성됨)
├── scripts/
│ ├── collect_rate.py # 수집기 (실제 실행 / --simulate 5종 실패 재생, 카드 3 상태모델)
│ ├── check_secrets.py # 비밀값 검색기 (작업 트리 + git 기록, 카드 2)
│ ├── replay_fixtures.js # 공식 fixture 9개 재생 채점기 (카드 3, Node 내장 기능만 사용)
│ ├── test_same_day_dedup.py # 같은 날 재실행/다음 날 신규행 합성 시계 시험 (카드 4)
│ └── verify_day_pair.py # 실제 이틀 기록 대조 + 독립 재계산 (카드 5, 합성값 미사용)
└── .github/workflows/
├── collect-rate.yml # 매일 자동 수집 + 수동 5종 실패 재생 워크플로
├── secret-scan.yml # push마다 비밀값 스캔 (카드 2)
├── dedup-test.yml # push마다 하루 한 줄 규칙 시험 (카드 4)
└── verify-day-pair.yml # 수동 실행 — 실제 이틀 데이터가 쌓인 뒤 대조 (카드 5)
이 폴더는 이미 로컬 git 저장소입니다(커밋 1개 포함, 비밀값 검색 결과도 그 커밋 기준으로 0건 확인됨). GitHub 웹에서 새 빈 저장소를 만들고(README/gitignore 자동 생성 옵션은 꺼두세요 — 이미 있어서 충돌합니다) 아래처럼 연결하면 기존 커밋 기록이 그대로 올라갑니다.
cd fx-board
git remote add origin https://github.com/<본인계정>/<저장소이름>.git
git branch -M main
git push -u origin main
jpy-krw-board).app.js 맨 위의 SOURCE_URL 상수를 실제 저장소 주소로 바꿔줍니다.failure_type: none으로 첫 실제 값을 즉시 받아옵니다. 이때
"정상값 증빙" 섹션도 처음으로 채워집니다 (T04-C10 확인 가능 시점).failure_type을 바꿔가며
(timeout/auth/rate_limit/offline/schema_error) 5회 더 수동 실행하면, 사이트의 "실패
시나리오 합성 재생 기록" 섹션이 채워지고 T04-C12~C19를 실제 배포 환경에서도 눈으로
확인할 수 있습니다.data/day_pair_evidence.json으로 증거가 커밋됩니다. (이 저장소는 실제로
2026-09-17Node.js(
adapter/,scripts/replay_fixtures.js)는 배포에는 전혀 필요 없습니다. Vercel은 정적 파일만 서빙하고, 실제 수집은 GitHub Actions의 Python이 담당합니다. Node는 오직 로컬에서 "내 로직이 ALEPH 공식 fixture와 맞는지" 스스로 채점할 때만 씁니다 —node scripts/replay_fixtures.js --fixtures-dir <fixture 폴더 경로>.
python3 scripts/verify_day_pair.py와
python3 scripts/check_secrets.py 실행 결과python3 scripts/verify_day_pair.py를 실행daily_readings에 서로 다른 실제 날짜 기록이 2건 이상
보존되어 있고(T04-C22, 현재 5건: 09-17~09-21), 각 기록의 출처 URL·시각·값·단위가 화면에
그대로 보이며(T04-C23), verify_day_pair.py가 가장 최근 인접한 두 건으로 [PASS]
종료하면서 재계산한 델타가 저장된 last_delta와 일치한다고 보고합니다(T04-C24, 실제
결과: -1.43원 일치)[PENDING](exit code 2)으로 정직하게 멈추고,
값이 있는데도 출처·값·단위 중 하나라도 불일치하거나 재계산 값이 어긋나면
verify_day_pair.py가 [FAIL]과 구체적인 문제 목록을 출력하며 exit code 1로 실패함adapter/reading-store.js) 작성과 9개 fixture 전량 재생 검증
(replay_fixtures.js, 전부 PASS), "마지막 정상값이 실패로 지워지지 않는다"는 요구를
만족하는 저장 로직 설계, 합성 시계로 같은 날 재실행 3회·다음 날 1회를 시험하는
test_same_day_dedup.py 작성과 검증(PASS), 실제 이틀 데이터를 조작 없이 대조·독립
재계산하는 scripts/verify_day_pair.py와 화면 섹션(renderDayPairSection) 작성 —
실제 데이터가 없는 개발 환경에서는 표본 형태로만 로직을 확인하고 저장소에는 남기지
않음과제 4(T04 카드)와 별도의 과제입니다. 이 저장소에 기능 한 개("일별 기록" 표의 전일 대비
열) 를 두 AI 세션이 차례로 완성했고, 두 번째 세션은 앞 대화 없이 저장소와 인수인계
문서만 받았습니다. 판정 대상은 t05/ 폴더의 최종 실행입니다.
https://<배포된 사이트 주소>/report.html — 메인 페이지 하단 "과제 5 비교 보고서"
링크로도 갈 수 있습니다. 운영(Production) 배포 주소를 제출하세요. Vercel의 브랜치
미리보기 주소는 기본으로 로그인 보호가 걸릴 수 있습니다.
이 저장소입니다. 커밋 ID가 그대로 보존되도록 fx-board.bundle로 올린 브랜치(또는 태그)를
기준으로 합니다.
| 구간 | 시작 | 종료 |
|---|---|---|
| AI A | t05f-a-start = 75bb6e5 | t05f-a-end = 926dd15 |
| AI B | t05f-b-start = 926dd15 | t05f-b-end = f762e3b |
| 파일 | 내용 |
|---|---|
t05/REQUEST.md | 같은 최초 요청, 모듈 계약, 공통 상한(세션당 20분·도구 호출 40회), 절차 |
t05/CHECKS.md | 고정 검사 10개(DC-01~10, ID·입력·기대값) |
t05/run_checks.js | 검사 실행기 — node t05/run_checks.js (실행마다 t05/runs.jsonl에 기록) |
t05/HANDOFF.md | AI A가 남긴 7칸 인수인계(AI B는 고치지 않음) |
t05/SESSION_LOG.md | 순서·인수인계 누락·자체 사용량 기록 |
t05/runs.jsonl | 검사 실행 회차 기록(오류 수 계산 근거) |
t05/usage_measured.json | 세션 실행 기록에서 집계한 시간·요청·호출 수 |
t05/fresh_folder_repro.txt | 새 폴더·깨끗한 환경에서 인수인계 실행 명령 재현 결과 |
t05/AUDIT.md | 카드 1~4 기준별 독립 확인 결과 |
| AI A | AI B | 상한 | |
|---|---|---|---|
| 실제 작업시간 | 65.1초 | 48.6초 | 20분 |
| 요청 / 도구 호출 | 1회 / 7회 | 1회 / 5회 | 40회 |
| 오류 수(1개 이상 FAIL 회차) | 1회 | 0회 | — |
| 검사 통과 수 | 8/10 | 10/10 | — |
모델 이름은 report.html 아래 "가림 해제"에만 적었습니다.
다음 작업에서도 모델 이름이 아니라, 같은 고정 검사를 먼저 돌려 첫 회차 통과 수가 높고 도구 호출 수가 적은 쪽을 먼저 쓴다.
▲ +4.95, ▼ -1.43처럼 기호·부호·소수 둘째
자리 값이 있고 가장 이른 날짜만 —이며, 맨 위(최신) 행 값이 오늘 카드의 "전일 대비"와 같음daily-change.js를 못 불러온 경우)1차(스파크라인: 루트 CHECKS.md, HANDOFF.md)와 2차(CSV 다운로드: T05_CARD1.md,
T05_HANDOFF.md, T05_CARD2.md)는 공식 카드 기준을 받기 전·중간에 진행한 기록입니다. 두
기능은 사이트에 남아 있지만 판정 대상은 아닙니다.
대화 전문은 제출하지 않습니다. 고정 검사, 집계 결과, 인수인계 문서, 본인 판단만 남깁니다.