--- append-only: true created: 2026-04-27 ---
Learnings — TimelyGPT MCP Hub
This file is append-only. New entries go at the bottom under a dated header.
Format
## YYYY-MM-DD — <topic>
- **Pattern**: <reusable pattern with file/command pointer>
- **Anti-pattern**: <what we tried that failed and why>
- **Why it matters**: <hidden constraint, gotcha, perf cliff, etc.>
- **Apply when**: <trigger conditions>
Entries should be:
- Non-obvious — not visible from reading the code or git history
- Reusable — applicable to future tasks
- Specific — name the file, command, or error signature
Skip "what we did" descriptions (use commit messages for that).
---
<!-- Append new entries below -->
2026-04-27 — Bootstrap
- Pattern: solopilot autopilot scaffolds the PM-driven loop (coordinator → implementor → verifier) with PRD as immutable source of truth and append-only LEARNINGS.
- Why it matters: separates planning from execution and keeps drift visible via
/verifyand/drift. - Apply when: every new feature — start with
/plan "<feature>", do not skip to/work.
2026-04-27 — data.go.kr 활용신청 캐시 반영 지연
- Pattern: 같은
PUBLIC_API_DATA_GO_KR_KEY로 신규 데이터셋 활용신청 후 즉시 호출하면 5~30분간 403 Forbidden. 자동승인이라도 동일. - Anti-pattern: 즉시 코드 작업 진입 (rent 추측 필드로 DTO 작성) — PRD No 뇌피셜 위반.
- Why it matters: data.go.kr는 데이터셋별 키-매핑 캐시가 별도 존재. 자동승인 ≠ 즉시 활성화.
- Apply when: 신규 data.go.kr 데이터셋 추가 시. 신청 후 라이브 호출 sanity check 통과해야 T1(필드 캡처) 진입.
2026-04-27 — MOLIT 매매 vs 전월세 필드 차이
- Pattern:
RTMSDataSvcAptTrade는RTMSDataSvcAptRent와 lawdCd/dealYmd/UA 패턴 동일하나 필드 셋이 다름. 신규:landLeaseholdGbn,estateAgentSggNm,cdealType/cdealDay,rgstDate,dealAmount(만원, 콤마). 부재:aptSeq,roadNm,contractType/Term. - Anti-pattern: rent 모듈 통째 복사 후 DTO만 손보기. → 부재 필드 잔존 + 신규 필드 누락.
- Why it matters: MOLIT XML 형제 API라도 필드 set은 다른 dataset. 거래해제(
cdealType=O) row 보존 +cancelDeal:true표면화는 LLM 무효 거래 인지에 필수. - Apply when: MOLIT RTMS 계열 신규 추가 시 (오피스텔·단독·연립·분양권). T1 실 XML 캡처 필수.
2026-04-27 — 날짜 정규화 함정
- Pattern: MOLIT trade의
dealMonth="3"(zero-pad 안됨),cdealDay/rgstDate="YY.MM.DD"(예:26.04.23). 정규화 헬퍼:pad()+parseShortDate()(정규식/^(\d{2})\.(\d{2})\.(\d{2})$/→20YY-MM-DD). - Anti-pattern: 날짜 필드를
String()만으로 join →2026-3-21또는26-04-23클라이언트 파싱 실패. - Why it matters: LLM 도구 사용 시 ISO
YYYY-MM-DD만 안전. 한국 행정 API는 zero-pad/short-year 일관성 X. - Apply when: 한국 행정 API의 날짜 필드 처리 시 — 항상 padStart + 명시적 정규화.
2026-04-27 — tools/list composite filter (PRD acceptance)
- Pattern:
ToolRegistryService.list()에서t.composite필터링 필수.register({composite:true})된 stub은 LLM에 노출되지 않아야 한다 (PRD acceptance 명시). - Anti-pattern: register 시점에는 컴포지트 표시했지만 list에서 필터 안 걸어 stub이 LLM에 expose → 호출 실패.
- Why it matters: 미구현 composite를 LLM이 호출 시도 → 실패 응답으로 LLM 신뢰도 하락.
- Apply when:
ToolRegistryService또는 동등 registry 수정 시 항상 점검. 신규 composite 추가 전 atomic 부품 완비 후 활성화.
2026-04-28 — OTel + Pino 정석 로깅 (풀스택 trace_id 전파)
Stack: @opentelemetry/sdk-node + nestjs-otel (@Traceable()/@Span()) + nestjs-pino (mixin으로 trace_id 자동 주입) — 백엔드. @vercel/otel + pino (transport 없이 JSON only) — Next.js 프론트. ConsoleSpanExporter + JSON Pino 로그만으로 grep 기반 분석 가능 (Jaeger/Tempo UI 불필요).
핵심 패턴:
tracing.ts는 main.ts 첫 줄에서 import — Nest 부트보다 먼저 SDK.start() 안 하면 instrumentation이 hook 못 검.- Pino
mixin()에서trace.getActiveSpan().spanContext()추출 → 모든 로그 라인에trace_id/span_id자동 주입. 백엔드 프론트 동일 로직. genReqId로 W3Ctraceparent헤더의 trace_id를req.id에 채택 → 응답 헤더x-request-id로 echo. 외부에서 trace_id 강제 주입 가능.@Traceable()클래스 데코레이터로 한 줄 적용 — 모든 메서드 자동 span. 19개 서비스에 일괄 적용은 awk 한 줄로 처리.tracer.startActiveSpan()로 핵심 진입점 명시 —mcp.tool.call ${name}같은 의미있는 span name + tool.name attribute로 grep/필터 용이.
함정:
pino-pretty워커 트랜스포트는 Next.js dev 번들러 (vendor-chunks/lib/worker.jsresolve 실패)와 충돌 → Next 쪽은 평문 JSON only.head -N으로 백그라운드 프로세스 stdout 자르면 SIGPIPE로 프로세스가 죽음 →> /tmp/log.log &로 디스크 redirection만.nestjs-otelv8metrics: { apiMetrics: { enable: false } }옵션 타입 변경됨 →OpenTelemetryModule.forRoot()인자 없이.
Why: 사용자 요청은 "Spring @Trace 어노테이션처럼 붙이기만 하면 자동" — 정확히 OTel + nestjs-otel 조합이 충족. 풀스택 한 trace_id로 묶이는 게 핵심 검증 대상이었음.
How to apply: 새 도구 서비스 추가 시 @Injectable() 아래 @Traceable() 한 줄 추가만 하면 끝. 핵심 진입점만 startActiveSpan 명시. trace_id로 grep하면 풀스택 흐름이 한 줄로 묶여 나옴.