-
탈락 전략 해부 — Global Cut·trades 부족·신호 미발화 세 가지 실패 유형
14개 전략 × 32개 심볼 실험에서 최종 Allowlist에 들어간 전략은 golden_triangle, pmax_explorer, elastic_student_t 세 가지뿐이다. 나머지 11개가 왜 탈락했는지를 세 가지 실패 유형으로 나눠서 살펴본다. 버그 하나, 설계 문제 하나, 파라미터 문제 하나다. 유형 1 — 신호 자체가 발화하지 않는 버그 support_resistance_channels 32개 심볼 전부에서 trades=0, Sharpe=0, OOS return=0이었다. DEFAULT_PARAMS = {"channel_window": 96, "break_buffer": 0.0005} res... Read More
-
전략 선택 아티팩트 빌드 — 실험 랭킹에서 Allowlist까지 4단계 필터
run_experiments.py가 14개 전략 × 32개 심볼에 대해 백테스트를 돌리고 ranking_all.csv를 만든다. 이 448행짜리 CSV를 그대로 라이브 봇에 넣을 수는 없다. 어떤 전략이 어느 심볼에서 실제로 쓰일지 결정하는 두 가지 스크립트가 있다. build_strategy_selection.py — 핵심 필터링 로직 refresh_nonvix_selection.py — SHA256 버전 관리 + meta/report 생성 입력 데이터 구조 ranking_all.csv는 전략·심볼 조합별 백테스트 결과를 담는다. 컬럼: oos_return, sharpe, mdd, profit_... Read More
-
라이브 봇 메인 루프 — 레짐에서 주문까지 60초 사이클 전체 흐름
지금까지 레짐 분류기, 전략 라우터, 리스크 오버레이, 백테스트 엔진, 실행 레이어를 각각 다뤘다. run_live_bot.py는 이 컴포넌트들을 60초마다 하나의 사이클로 묶는 메인 루프다. 각 사이클에서 무슨 일이 일어나는지 순서대로 정리한다. 사이클 개요 [매 60초] 1. 데이터 로드 (Alpaca / Yahoo / FRED) 2. 데이터 freshness 체크 (stale / outside_rth / missing) 3. 레짐 피처 빌드 → 레짐 예측 4. 체인 결정 (defensive / chop / trend / neutral) 5. Allowlist 필터 (허용 전략만 통과) 6. 활성... Read More
-
4개 전략 × 84 심볼 OOS 검증 — roc_dual_momentum만 살아남았다
새 전략을 추가할 때 가장 위험한 실수는 Train 성능만 보는 것이다. 좋은 Train Sharpe가 OOS에서 반전되면 라이브에서 그대로 손실이 된다. search_low_corr_strategies.py는 4개 전략을 84개 심볼에 걸쳐 Train/Test로 분리해 검증하고, 양쪽 모두 LiveGate를 통과한 조합만 선별한다. 실험 설계 기간 분리 구간 기간 역할 Train 2024-01-01 ~ 2025-06-30 파라미터 최적화 Test 2025-07-... Read More
-
백테스트 엔진 내부 — 바 단위 루프, Stop/TP 처리, 비용 모델
Walk-Forward나 Optuna 최적화 포스트에서 run_backtest()를 블랙박스처럼 썼는데, 그 안에서 실제로 무슨 일이 일어나는지 정리한다. 핵심은 세 가지다. 신호 지연 — 현재 바의 신호가 다음 바에 체결되는 구조 Stop / Take-Profit — 바 내부 high/low로 터치 여부 판단 비용 모델 — 포지션 변화량(turnover)에 수수료·슬리피지 부과 설정 — BacktestConfig @dataclass(frozen=True) class BacktestConfig: fee_bps_side: float = 2.0 # 편도 수수료 (bps) s... Read More
-
라이브 전환 게이트 — Metrics, LiveGate, Shadow Mode 검증 파이프라인
백테스트 성능이 좋다고 바로 라이브에 올리면 안 된다. 백테스트 과적합, 체결 슬리피지, 브로커 오류율, 레이턴시 같은 실운영 변수를 다 고려해야 한다. 직접 구현한 라이브 전환 파이프라인은 세 단계로 나뉜다. metrics.py — 백테스트 지표 계산 및 selection score gates.py — 실시간 LiveGate 통과 여부 판정 Shadow Guard Loop — 실제 라이브 환경에서 5영업일 자동 검증 후 Cutover 지표 계산 — metrics.py MDD (Max Drawdown) def max_drawdown(equity: pd.Series) -> float: ... Read More
-
라이브 트레이딩 모니터링 대시보드 — 순수 Python HTTP 서버와 TTL 캐시
라이브 트레이딩 봇이 돌아가는 동안 현재 상태를 한눈에 볼 수 있어야 한다. 레짐이 어느 체인으로 분류됐는지, 어느 전략이 활성화됐는지, 마지막 주문이 성공했는지. Flask나 FastAPI를 쓰면 의존성이 늘어난다. 순수 Python 표준 라이브러리의 http.server로 구현했다. 서버 구조 from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer class DashboardHandler(BaseHTTPRequestHandler): def do_GET(self): parsed = urlparse(self.path) ... Read More
-
VIX Prob Hybrid — 항상 켜진 VIX 리스크온 확률 전략
포트폴리오 전략들이 체인 라우터에 의해 켜고 꺼지는 동안 vix_prob_hybrid는 항상 켜져 있다. 라우터 설정에서 frozen 전략으로 지정되어 오버라이드 목록에 포함돼도 필터링되지 않는다. _FROZEN_OVERRIDE_STRATEGIES = {"vix_prob_hybrid"} def _sanitize_override_strategies(values): out = [] for v in values: s = str(v).strip() if not s or s in _FROZEN_OVERRIDE_STRATEGIES: continue # ... Read More
-
볼린저 밴드 + RSI 평균회귀 전략 — 상태머신 루프와 ATR 포지션 사이징
평균회귀 전략은 추세 전략과 반대 방향 신호를 낸다. PMAX나 ROC Dual Momentum이 상승 돌파에 롱을 잡을 때, 볼린저 밴드 + RSI 전략은 BB 하단 이탈 구간에서 롱을 잡는다. 두 전략을 포트폴리오에 함께 넣으면 상관관계가 낮아져서 전체 변동성이 줄어든다. 진입 조건: 롱: close < BB 하단 AND RSI < 35 (과매도) 숏: close > BB 상단 AND RSI > 65 (과매수) 청산 조건: 롱 청산: close >= BB 중심선 (EMA로 복귀) 숏 청산: close <= BB 중심선 목표가를 고정 수익률이 아니라 BB... Read More
-
PMAX 전략 구현 — EMA + ATR 래칫 트레일링 스톱
PMAX(Progressive Moving Average with ATR Trailing Stop)는 EMA 기반 이동평균선과 ATR 밴드를 결합한 추세 추종 전략이다. 핵심은 래칫(ratchet) 구조다. 상승 추세에서는 손절선이 위로만 올라가고 내려오지 않는다. 하락 추세에서는 반대로 아래로만 내려간다. 이 단방향 이동이 추세 추종과 손실 제한을 동시에 달성한다. 알고리즘 PMAX 라인은 두 밴드로 구성된다. def _pmax(close, ma, atr_v, mult): upper = ma + mult * atr_v lower = ma - mult * atr_v trend = np.... Read More
-
한국 시장 레짐 분류기 — Transformer 단일 헤드 정정 확률 예측
이전에 만든 US Transformer는 멀티태스크다. 레짐 분류(3-class)와 고변동성 예측(binary)을 하나의 인코더로 처리하고 심볼·섹터 임베딩을 달았다. 한국 시장 버전은 구조를 단순하게 가져갔다. 이유는 두 가지다. 한국 레짐 분류의 목적이 다르다. “지금 추세 상승/하락/횡보”가 아니라 “근미래에 시장이 조정을 받을 가능성”이다. 심볼별 특성 차이가 미국보다 좁다. KOSPI 구성 종목은 S&P500 대비 섹터 분산이 작고 외국인 수급이라는 공통 변수에 강하게 묶인다. 결과적으로 단일 이진 출력(correction_prob)을 내는 단순한 구조가 됐다. 모델 구조 clas... Read More
-
퀀트 전략 라우터 — 레짐 체인 결정, 차트 스냅샷, Softmax 가중치 배분
Transformer 레짐 분류기가 p_trend_up / p_trend_down / p_chop / p_high_vol 네 확률을 뱉는다. 이 확률로 “지금 어떤 전략을 켜고, 얼마나 태울까”를 결정하는 게 라우터 레이어다. 세 모듈이 파이프라인을 이룬다. chain.py — 레짐 확률로 체인(defensive/chop/trend/neutral)을 결정 chart_selector.py — 차트 지표로 전략-차트 적합도를 계산 interface.py — 전략별 점수를 Softmax로 가중치로 변환 체인 결정 — decide_chain 레짐 확률이 들어오면 priority 순서로 체인을 고른다. ... Read More
-
퀀트 데이터 파이프라인 — Alpaca/Yahoo/FRED 폴백 체인과 ADR 프록시 피처
라이브 트레이딩 시스템에서 데이터 파이프라인이 단일 소스에 의존하면 해당 소스가 내려갈 때 전체가 멈춘다. Alpaca가 주 소스고, Yahoo Finance가 폴백, FRED API가 VIX 공식 소스 역할을 한다. 각 소스에서 가져온 데이터로 레짐 분류에 쓸 피처를 만드는 과정을 정리한다. 멀티소스 폴백 체인 Alpaca 데이터 클라이언트 Alpaca Data API v2는 페이지 토큰 기반 페이지네이션을 쓴다. next_page_token이 없을 때까지 반복 요청한다. def fetch_bars(self, req: BarRequest) -> pd.DataFrame: url = "https:... Read More
-
Walk-Forward 폴드 equity 스티칭과 Alpaca 주문 실행 레이어
Walk-Forward 백테스트를 만들면서 두 가지가 까다로웠다. 폴드별로 나온 equity curve를 하나로 이어 붙이는 방법 백테스트 포지션 비율을 실제 주문 수량으로 변환하고, Alpaca API 오류를 분류해서 재시도하는 방법 Walk-Forward 구조 폴드 하나는 train 구간과 test 구간으로 구성된다. | ← train (252일) → | ← test (63일) → | | ← train (252일) → | ← test (63일) → | ... step_days마다 윈도... Read More
-
라이브 트레이딩 리스크 오버레이 — Kill Switch, 오더 실패율, 익스포저 캡
전략 레벨 stop loss와 별개로, 계좌 전체를 보호하는 리스크 오버레이가 필요하다. 전략이 여러 개 돌아갈 때 개별 전략 stop만으로는 전체 손실을 제어할 수 없다. 또 브로커 API 오류나 네트워크 장애가 연속으로 나면 잘못된 주문이 누적될 수 있다. RiskManager는 세 가지를 본다. 일별/주별 손실이 한도를 넘으면 kill switch 주문 실패율이 임계치를 넘으면 kill switch 포지션이 심볼/섹터/전체 한도를 초과하면 비율 조정 Kill Switch 조건 @dataclass class RiskLimits: daily_loss_limit: float = -0.0... Read More
-
Transformer로 시장 레짐 분류하기 — 심볼 임베딩 + 이중 헤드 멀티태스크
규칙 기반 레짐 감지(이동평균 교차, VIX 임계값)는 파라미터를 수동으로 튜닝해야 하고 시장 환경이 바뀌면 다시 맞춰야 한다. Transformer 인코더로 레짐을 학습하게 하면 피처 조합의 비선형 패턴을 직접 학습한다. 목표는 두 가지다. 현재가 추세 상승 / 추세 하락 / 횡보 중 어디인가 (3-class) 향후 변동성이 높을 것인가 (binary) 두 태스크를 하나의 인코더로 처리하는 멀티태스크 구조를 만들었다. 피처 설계 FEATURE_COLS = [ "ret_1", # 1봉 수익률 "ret_4", # 4봉 수익률 "ret_8", ... Read More
-
퀀트 전략 파라미터 최적화 — Optuna + SQLite 캐시 + Monte Carlo 강건성 검증
전략별 최적 파라미터를 찾으면서 세 가지 문제가 생겼다. 파라미터 공간이 넓으면 그리드 서치가 너무 느리다 같은 파라미터 조합을 조건이 바뀔 때마다 재계산한다 백테스트 Sharpe가 높아도 실제 운용에서 파산할 수 있다 각각 Optuna 베이지안 최적화, SQLite 해시 캐시, Monte Carlo 거래 셔플로 해결했다. 왜 그리드 서치가 안 되나 이동평균 기간 하나를 10~200 사이 정수로, 임계값을 0.1~2.0 사이 실수 20단계로 조합하면 이미 1,900가지다. 전략 파라미터가 5개면 100만 가지가 넘는다. 여기에 Walk-Forward 폴드 3개를 각 조합마다 돌리면 수백만 번의... Read More
-
카카오 게임봇 매크로 — pynput F-키 시스템과 MaskablePPO Action Masking
카카오톡 게임봇 “검키우기”는 검을 강화하거나 판매하는 미니게임이다. 강화에 실패하면 레벨이 유지되거나 파괴되고, 잔고가 없으면 강화를 못 한다. 이 제약을 모델에 반영하면서 매크로로 실행하는 것까지 구현했다. 이전 포스트에서 Gymnasium 환경 설계와 PPO 학습 흐름을 다뤘다. 이 글은 그 다음 두 가지에 집중한다. 표준 PPO 대신 MaskablePPO를 쓴 이유와 Action Masking 구현 pynput 기반 F-키 매크로 시스템과 카카오톡 클립보드 실시간 파싱 Action Masking: MaskablePPO 표준 PPO는 어떤 액션이든 선택할 수 있다고 가정한다. 하지만 실제 게... Read More
-
Selenium + multiprocessing 예약 봇 — Shadow DOM JS 접근과 프로세스 간 통신
이전에 Playwright + asyncio 조합으로 예약 봇을 만들었는데, 같은 로직을 Selenium + multiprocessing으로 다시 구현했다. Playwright가 없는 환경이거나 Selenium으로만 유지해야 하는 상황에서 쓸 수 있다. 구현하면서 Playwright와 달라지는 지점이 세 곳 있었다: Shadow DOM 접근법, 병렬 처리 방식, 봇 감지 우회. Shadow DOM: execute_script로 shadowRoot 직접 참조 Playwright는 pierce/ selector로 Shadow DOM을 관통한다. Selenium에는 그런 게 없다. find_element는 Shad... Read More
-
Reddit + 국내 커뮤니티 멀티소스 스크래퍼 + GPT-4o로 사이드 프로젝트 아이디어 발굴하기
사이드 프로젝트 아이디어를 찾으려면 커뮤니티를 직접 뒤지는 게 제일 확실하다. “이런 앱 있으면 좋겠다”, “왜 아직도 없냐” 같은 글들이 실제 수요를 가장 날 것 그대로 보여준다. 근데 에펨코리아, 디시인사이드, 클리앙, Reddit을 일일이 검색하다 보면 금방 질린다. 그래서 멀티소스 스크래퍼를 만들고 GPT-4o로 수요 분류와 MVP 후보 추출까지 자동화했다. 구조 idea-scraper/ ├── main.py ├── config.py ├── scrapers/ │ ├── browser.py # Selenium 공통 드라이버 │ ├── reddit.py # requests + JSON ... Read More
-
Z-Score 평균회귀 전략 설계와 OOS 검증
기존에 운용하던 bb_rsi_reversion 전략이 계속 손실을 냈다. 백테스트에서는 괜찮았는데 라이브에서 실패하는 패턴이었다. 원인을 분석하고 z-score 기반으로 전략을 재설계했다. bb_rsi_reversion 실패 원인 세 가지가 문제였다. 1. 타임프레임이 너무 짧았다 (15m) 15분봉은 노이즈가 많다. 볼린저밴드 하단에 닿았다고 반등이 오는 게 아닌 경우가 많았다. 2. 레짐 필터가 없었다 추세장에서도 평균회귀를 시도했다. 강한 하락 추세에서 “저가에 진입”하면 계속 물린다. 평균회귀는 횡보·방어 구간에서만 작동해야 한다. 3. BB 중선 청산 볼린저밴드 중선(20일 이동평균)에서 청산하다... Read More
-
OpenAI web_search_preview로 크롤링 안 되는 사이트 긁기
가격 비교 플랫폼을 만들면서 야후옥션 일본, Amazon JP, 네이버 중고를 크롤링해야 했다. 문제는 이 사이트들이 전통적인 스크래퍼로 긁기가 까다롭다는 점이다. JS 렌더링이 필요하거나, 봇 감지가 빡빡하거나, DOM 구조가 자주 바뀐다. Selenium이나 Playwright를 붙이는 대신 OpenAI Responses API의 web_search_preview 툴을 써봤다. Responses API와 web_search_preview OpenAI Responses API는 Chat Completions와 다르게 툴을 모델이 직접 실행할 수 있다. web_search_preview 툴을 주면 LLM이 웹... Read More
-
FastAPI + SQLite 동시 읽기/쓰기 타임아웃, WAL 모드로 해결
FastAPI 서버에서 검색 요청이 들어올 때 결과를 SQLite에 저장하는 구조였다. 로컬에서는 잘 돌았는데 Railway에 올리고 나서 간헐적으로 요청이 타임아웃으로 죽기 시작했다. 상황 검색 파이프라인이 끝나면 결과를 DB에 flush하는 구조다. def _persist_normalized_offers(self, db: Session, run_id: str, offers: List[Offer]): rows = [] for idx, offer in enumerate(offers[:200], start=1): row = NormalizedOfferRow(run_id=run_id,... Read More
-
라이브 트레이딩 봇 레짐 분류 강화와 포지션별 손절 추가
자동매매 봇을 실제로 돌리다 보면 백테스트에서 잘 보이지 않던 문제들이 라이브에서 드러난다. 최근에 두 가지를 고쳤다. 하나는 레짐 체인 분류가 너무 쉽게 “trend” 체인으로 들어가는 문제였고, 다른 하나는 체인 수준의 손실 한도는 있는데 개별 포지션 단위 손절이 없다는 문제였다. 레짐 체인 구조 봇은 Transformer 모델이 예측한 레짐 확률 (p_up, p_dn, p_chop, p_hv)를 보고 4가지 체인 중 하나를 선택한다. 체인마다 활성 전략과 exposure_multiplier가 다르다. defensive → exposure 0.40 (방어적 전략만, 노출 낮춤) chop →... Read More
-
price-agent Railway + Vercel 배포와 effective_grade 구현
price-agent 플랫폼을 로컬에서 클라우드로 올리면서 두 가지 작업을 같이 했다. 백엔드(FastAPI)는 Railway, 프론트엔드(Next.js)는 Vercel로 분리 배포하고, LLM이 중고 상품의 상태를 등급으로 분류하는 effective_grade 기능을 추가했다. 배포 구조 Vercel (Next.js) ──── NEXT_PUBLIC_BACKEND_URL ────▶ Railway (FastAPI) │ ... Read More
-
멀티에이전트 LLM 가격 검색 파이프라인 설계하기
글로벌 최저가 탐색 플랫폼을 만들면서 가장 먼저 부딪힌 문제가 있었다. 단순히 여러 마켓플레이스를 크롤링하면 되는 게 아니었다. 공식몰·중고·해외직구가 섞인 결과에서 “진짜 최저가”를 뽑으려면 각 소스의 성격에 맞게 쿼리를 다르게 짜야 했고, 부품용·약정포함·빈 박스 같은 미끼 가격도 걸러야 했다. 그리고 이 모든 게 45초 안에 끝나야 했다. 결국 역할이 분리된 에이전트를 두고, Orchestrator가 타임아웃 내에 순서대로 실행하는 구조로 설계했다. 전체 구조 사용자 쿼리 ↓ QueryExpander (LLM/휴리스틱 다국어 확장) ↓ SearchOrchestrator ├── [f... Read More
-
Prefect + LangChain으로 영화 시나리오 AI 분석 파이프라인 만들기
영화·드라마 시나리오 원문을 넣으면 캐릭터, 세계관, 스토리, 액션, 비주얼 방향을 LLM이 분석하고, 그 결과로 Kling AI가 프리비즈 영상을 자동으로 만들어주는 파이프라인이다. 파일 파싱부터 영상 생성·병합까지 엔드투엔드로 돌아가도록 Prefect 메달리온 아키텍처로 설계했다. 전체 구조 입력: .pdf / .txt / .html / .docx Bronze → 파싱 + MD5 캐싱 Silver → 5 LLM 에이전트 분석 Gold → 통합 JSON + 리포트 Previs → intent별 샷 리스트 저장 VideoGen → Kling AI 호출 + FFmpeg 병합 출력: outputs/... Read More
-
S&P500 444종목 전략 탐색 파이프라인
라이브 트레이딩 allowlist를 ETF 14쌍에서 S&P500 전체로 확장하면서, 종목별로 전략과 파라미터 조합을 자동으로 선별하는 파이프라인을 만들었다. 핵심은 Train 기간에서 최적화하고 Test 기간에서 독립적으로 검증하는 OOS 분리 구조다. 왜 파이프라인이 필요했나 전략을 전체 기간 데이터로 최적화하면 과최적화가 된다. 백테스트 Sharpe가 2.0이어도 실제 라이브에서 음수가 나오는 경우가 많다. Train/Test 분리가 필수다. 444종목에 4개 전략을 각각 수십~수백 개 파라미터 조합으로 돌리면 수만 번의 백테스트가 필요하다. 수작업으로 할 수 없어서 search_low_corr_... Read More
-
스캔 PDF 처리 - pdfplumber + tesseract OCR fallback 구현
시나리오 분석 파이프라인을 만들면서 PDF 지원이 필요했다. 영화 시나리오가 PDF로 오는 경우가 많다. 문제는 PDF가 두 종류라는 것. 텍스트 레이어가 있는 일반 PDF, 그리고 종이를 스캔한 이미지 PDF. 전자는 텍스트 추출이 간단하지만 후자는 OCR을 써야 한다. pdfplumber 기본 추출 import pdfplumber def _parse_pdf(path: Path) -> str: texts: list[str] = [] with pdfplumber.open(path) as pdf: for page in pdf.pages: page_text... Read More
-
ROC 이중 모멘텀 전략 설계
golden_triangle 전략이 EMA 크로스오버 기반이라면, roc_dual_momentum은 가격 변화율(ROC)을 직접 비교한다. 이동평균의 후행성 없이 모멘텀의 방향과 가속도를 직접 잡는다. ROC 이중 모멘텀이란 ROC(Rate of Change)는 N봉 전 가격 대비 현재 가격의 변화율이다. ROC(n) = (현재가 - n봉 전가) / n봉 전가 이중 모멘텀은 단기 ROC와 장기 ROC를 동시에 본다. fast_roc(10봉): 최근 모멘텀 slow_roc(40봉): 중기 모멘텀 단기 ROC가 장기 ROC보다 크다는 건 모멘텀이 가속 중이라는 뜻이다. 반대면 감속. fast_... Read More
-
Clova OCR API로 영상 크레딧 텍스트 자동 추출
영화나 드라마 엔딩 크레딧에는 모든 스태프가 나온다. 이걸 자동으로 읽어서 DB에 넣을 수 있으면 KOBIS에 없는 스태프 데이터를 보완할 수 있다. 방식: 영상에서 프레임 추출 → OCR로 텍스트 인식 → LLM으로 역할/이름 파싱. OCR 선택 과정 처음엔 Tesseract를 썼다. 무료고 Python 바인딩이 있다. 한국어 텍스트 인식률이 낮았다. 특히 영상 크레딧처럼 배경이 어둡고 폰트가 작은 경우. 인식률이 30~40% 수준. Google Vision API도 테스트했는데 한국어 세로쓰기나 특수 폰트에서 틀리는 경우가 있었다. Clova OCR(Naver)이 한국어 인식률이 가장 높았다. 영상 ... Read More
-
동적 웹 크롤링 Prefect 플로우 + Langfuse 게이트 연동
KOBIS, TMDB, 나무위키에 이어 드라마 스태프 정보가 구조화된 형태로 잘 정리된 미디어 정보 사이트 크롤링 플로우를 추가했다. 드라마/영화 서비스에 필요한 경력 데이터를 보완하기 위해서다. 이번에는 Langfuse 게이트를 붙여서 LLM이 추출한 데이터 품질을 파이프라인 내에서 검증하도록 했다. 플로우 구조 미디어 정보 페이지 크롤링 (patchright) ↓ Bronze: 원본 HTML 파싱 → 구조화 ↓ Silver: LLM 경력 추출 + Langfuse 게이트 ↓ Gold: DB 적재 기존 KOBIS/TMDB 플로우와 동일한 메달리온 아키텍처를 따른다. patchrigh... Read More
-
PPO 강화학습으로 게임 봇 만들기 - Stable Baselines3 실전 사용기
강화학습(RL)이 게임 자동화에 어떻게 쓰이는지 직접 실험해보고 싶었다. 카카오 게임봇에서 실행되는 카드 강화 미니게임을 타겟으로 잡았다. 규칙 기반 매크로는 이미 만들어뒀고, RL 에이전트가 더 잘할 수 있는지 비교하고 싶었다. 구조 설계 게임 화면 파싱 → Gym 환경 → PPO 에이전트 학습 → 매크로 실행 화면에서 게임 상태를 읽어서 Gym 환경에 넣고, PPO 에이전트가 액션을 선택하면 매크로로 실행하는 구조다. 데이터 수집 카카오톡 채팅 로그를 export해서 게임 상태를 파싱했다. 채팅 메시지에 강화 결과(성공/실패, 현재 레벨 등)가 텍스트로 나온다. import pandas as pd ... Read More
-
LLM으로 영화 스태프 데이터 Silver ETL 만들기
영화/드라마 서비스를 만들다 보면 스태프 이력 데이터가 핵심이 된다. 감독, 배우, 촬영감독 등이 어떤 작품에 참여했는지. KOBIS 공식 API에서 스태프 데이터를 내려받으면 이름과 직무가 오는데, 문제는 품질이다. 같은 사람인데 이름 표기가 다르거나(“박준영” vs “박준영 B”), 직무명이 표준화되지 않거나(“촬영” vs “촬영감독” vs “Dir. of Photography”). 이걸 정제하는 Silver ETL을 LLM으로 만들었다. 문제 정의 // KOBIS Raw 스태프 데이터 (Bronze) { "movieCd": "20251234", "directors": [{"peopleNm": "... Read More
-
카카오톡 챗봇으로 Notion 게임 리스트 관리하기
Notion에 게임 리스트를 관리하는데, 매번 앱을 열어서 수동으로 추가하는 게 번거로웠다. 카카오톡 채널 봇으로 명령어 하나로 Notion DB에 추가되도록 만들었다. /추가 백투더던 PC 생존 2D 시뮬레이션 이렇게 입력하면 제목, 플랫폼, 태그가 파싱돼서 Notion 데이터베이스에 새 행이 추가된다. 구조 Flask로 카카오톡 스킬 서버를 구현하고, Render에 배포했다. src/ app.py # Flask 진입점 kakao_chatbot.py # 스킬 서버 + Notion API 연동 assets/ game_emoge.svg # 카드 썸네일용 아이콘 카카오톡 ... Read More
-
Prefect ETL 배치 처리와 preload 최적화
career_db_etl_flow는 운영 DB의 커리어 로우데이터(staging.career_raw)와 KOBIS-TMDB 병합 ETL 결과(merge.career_generation_etl)를 매칭해서 최종 병합 테이블(merge.career_db_etl)을 만드는 플로우다. 초기 구현에서는 ETL 데이터 전체를 메모리에 올린 다음 DB 레코드와 매칭했다. 데이터가 수십만 건으로 늘어나면서 OOM이 발생했다. 문제: 전체 로드 방식의 한계 # 기존 방식 — 전체 ETL 데이터를 한 번에 로드 etl_df = await load_all_etl_records() # 수십만 건 → OOM match_resul... Read More
-
나무위키 미디어 데이터 크롤링 + KOBIS-TMDB 매칭 자동화
KOBIS와 TMDB만으로는 드라마 스태프 데이터가 부족했다. 특히 OTT 오리지널이나 최신 드라마는 KOBIS에 누락이 많다. 나무위키가 의외로 드라마/영화 스태프 정보가 잘 정리되어 있다. 비공식 데이터라 신뢰성 이슈가 있지만 KOBIS 공식 데이터와 교차 검증하면 쓸 수 있다. ETL 클래스 설계 처음엔 각 소스별로 함수를 만들다가, 코드가 너무 비슷한데 각각 다른 파일에 흩어지는 문제가 생겼다. 공통 인터페이스를 정의하는 베이스 클래스로 리팩터링. from abc import ABC, abstractmethod from dataclasses import dataclass from typing imp... Read More
-
KOBIS-TMDB 인물 매칭 ETL 파이프라인
드라마·영화 크루 프로필 데이터를 구축하면서 KOBIS(한국영화진흥원)와 TMDB(The Movie Database) 인물 데이터를 병합해야 했다. 두 소스의 인물 ID가 달라서 동명이인 구분, 활동 작품 교집합, 직무 정규화를 거쳐 같은 인물인지 판단하는 매칭 로직이 필요했다. 문제 정의 KOBIS에는 한국 인물 데이터가 풍부하고, TMDB에는 이미지·영문 이름·글로벌 활동 이력이 있다. 두 소스를 연결하면 더 완성도 높은 프로필을 만들 수 있다. 단순히 이름으로만 매칭하면 동명이인 오매칭이 생긴다. “이정재”는 배우도 있고 감독도 있다. 같은 이름이어도 직무, 성별, 활동 작품이 다르면 다른 인물이다. ... Read More
-
Prefect로 KOBIS/TMDB 영화 데이터 ETL 파이프라인 구축
영화/드라마 데이터를 자동으로 수집하고 적재하는 파이프라인이 필요했다. KOBIS(영화진흥위원회)와 TMDB(The Movie Database)를 주 소스로 선택했다. Prefect를 오케스트레이터로 쓰기로 했다. Airflow도 검토했는데 Prefect는 로컬 실행이 간단하고 태스크 단위 재시도 설정이 직관적이었다. 기본 구조 메달리온 아키텍처로 3단계. Raw (API 응답 그대로) → Staging (정제) → Production DB from prefect import flow, task from prefect.tasks import task_input_hash from datetime impor... Read More
-
Playwright로 예약 자동화 봇 만들기 - 병렬 처리와 Shadow DOM 대응
예약 자동화 봇을 Playwright로 만들었다. 구체적인 내용은 생략하고, 만들면서 부딪혔던 기술적 문제들만 정리한다. Shadow DOM 문제 최신 React 기반 예약 위젯은 Shadow DOM을 쓰는 경우가 많다. 일반 page.locator로는 Shadow DOM 안쪽 요소에 접근이 안 된다. Playwright는 pierce CSS selector로 Shadow DOM을 뚫을 수 있다. # 일반 locator — Shadow DOM 안쪽 접근 불가 page.locator("button.booking-confirm") # 작동 안 함 # pierce로 Shadow DOM 관통 page.locat... Read More
-
이터널리턴 카카오톡 + 디스코드 챗봇 만들기
이터널리턴(Eternal Return) 게임 커뮤니티용 챗봇을 만들었다. 카카오톡 채널 봇과 디스코드 봇 두 가지를 동시에 지원한다. 주요 기능: 닉네임으로 유저 전적 / 통계 / 랭킹 조회 공식 홈페이지 패치노트 자동 스크래핑 → 디스코드 채널로 전송 카트 명령어, 기타 유틸리티 구조 카카오톡 봇은 Flask 웹훅 서버로 처리하고, 디스코드 봇은 discord.py로 별도 프로세스로 실행한다. . ├── app.py # Flask 웹훅 서버 (카카오톡) ├── discord_bot.py # discord.py 봇 ├── eternal_api.py # 이터널리턴 공식 ... Read More
-
영상 크레딧 OCR 파이프라인 — 히트맵 크롭 + Clova OCR + Bi-Encoder 추천
영상에서 스태프 크레딧을 자동으로 추출하는 파이프라인을 구축했다. 크레딧 구간의 프레임에서 텍스트 영역을 감지하고, OCR로 텍스트를 추출한 뒤, Bi-Encoder로 직군 매칭 추천까지 이어지는 end-to-end 구조다. 전체 파이프라인 영상 프레임 입력 ↓ FilterPipeline (EAST 텍스트 감지 → 히트맵 생성) ↓ HeatmapCropper (히트맵 기반 크레딧 영역 크롭) ↓ OCRProcessor (Clova OCR / Tesseract) ↓ 후처리 (텍스트 정제, 컬럼 클러스터링) ↓ Bi-Encoder (직군 분류 추천) HeatmapCropper... Read More
-
PostgreSQL DISTINCT ON과 ORDER BY 함께 쓸 때 빠지는 함정
경력 목록 API에서 특정 조건으로 조회 시 500 에러가 발생했다. 에러 메시지는 이랬다. ProgrammingError: SELECT DISTINCT ON expressions must match initial ORDER BY expressions DISTINCT ON을 쓰면서 ORDER BY가 맞지 않아서 생긴 에러. 처음 보면 뭔 소린지 몰랐다. 왜 DISTINCT ON을 썼나 사용자의 경력에서 같은 작품에 여러 직무로 참여한 경우, 작품별로 하나씩만 보여줘야 했다. Career.objects.order_by('media_id', '-created_at').distinct('media_id') ... Read More
-
Android 15 16KB 페이지 크기 지원 - React Native 앱 대응기
Google이 Android 15부터 일부 기기에서 16KB 페이지 크기를 지원하기 시작했고, 2025년 이후로는 Play Store 신규 앱이 16KB 정렬을 지원해야 한다는 요구사항을 공지했다. React Native 앱에서 이 대응 작업을 했다. 뭐가 문제인가 기존 Android는 4KB 페이지 크기를 기준으로 동작했다. 16KB 페이지 크기 기기에서는 네이티브 라이브러리(.so 파일)가 16KB 경계에 정렬되어 있어야 한다. 그렇지 않으면 앱이 실행되지 않는다. 직접 작성한 C/C++ 코드가 없어도, 의존하는 네이티브 라이브러리 중 하나라도 정렬이 안 돼 있으면 문제가 된다. 확인 방법 # APK... Read More
-
React Native BottomSheet 크로스플랫폼 이슈 정리
앱 여러 화면에서 BottomSheet를 썼는데, iOS에서는 자연스러운 게 Android에서 어색하거나 그 반대인 경우가 계속 생겼다. 전체 리팩터링을 하면서 정리했다. 라이브러리는 @gorhom/bottom-sheet를 썼다. snap points 문제 기존 코드에서 snap points를 퍼센트로 고정해놨다. // 기존 - 단순 고정값 const snapPoints = ['25%', '50%', '90%']; 문제는 콘텐츠 높이에 따라 25%가 너무 낮거나 너무 높은 케이스가 생겼다. 댓글이 1개인데 50% 높이가 되거나, 댓글이 50개인데 90%가 부족하거나. 동적으로 콘텐츠 높이에 맞추되, 최... Read More
-
NICE 본인인증 React Native WebView 연동 삽질기
앱에 본인인증을 붙였다. NICE에서 제공하는 패스(PASS) 인증 방식. 공식 문서가 있긴 한데, React Native WebView에서 쓸 때의 레퍼런스가 거의 없어서 처음부터 삽질이었다. 인증 흐름 앱 → Django 서버: 인증 토큰 요청 Django → NICE API: 암호화 토큰 발급 Django → 앱: 인증 URL + 암호화된 파라미터 반환 앱: WebView로 NICE 인증 페이지 오픈 사용자: PASS 앱 인증 완료 NICE → Django 콜백: 인증 결과 (암호화됨) Django: 복호화 후 앱에 결과 전달 Django 암호화 처리 NICE는 AES-1... Read More
-
Django에서 NICE 본인인증 구현하기
NICE 본인인증(CheckPlus)을 Django 백엔드에 붙이면서 문서만 봐서는 파악하기 어려운 부분들이 있었다. 토큰 캐싱, 멀티워커 환경의 콜백 처리, AES 키 파생 방식이 특히 그랬다. NICE 본인인증 흐름 NICE CheckPlus 표준창 서비스는 3단계로 진행된다. 1. OAuth 토큰 발급 → 2. 암호화 토큰 발급 → 3. enc_data 생성 + HTML Form 제출 ↓ NICE 본인인증 화면 (팝업... Read More
-
Firestore로 실시간 채팅 구현 - React Native에서 쓸 때 주의할 것들
앱에 DM 기능을 붙였다. 기술 선택은 Firestore. Django 서버에 채팅 테이블을 만드는 것보다 실시간 동기화가 기본으로 제공되기 때문. Firestore 데이터 구조 chats/ {chatRoomId}/ messages/ {messageId}/ text: string sender_id: string created_at: Timestamp is_read: boolean participants: [userId1, userId2] last_message: string last_message_at: Time... Read More
-
React Native 메모리 누수 잡기 - FlatList, React Query, 타이머
앱 사용 중 스크롤이 점점 버벅거린다는 피드백이 있었다. 오래 쓸수록 심해졌다. 메모리 누수 세 군데에서 찾았다. 1. BannerSection 타이머 누적 배너 자동 슬라이드 타이머가 화면 전환 시 제거되지 않았다. 탭을 왔다갔다 할수록 타이머가 쌓였다. // 문제 코드 useEffect(() => { const timer = setInterval(slideNext, 3000); // 반환값 없음 - cleanup 없음 }, []); React Navigation에서 탭 화면은 탭 전환해도 unmount되지 않는다. useEffect의 cleanup이 호출 안 된다. // 수정 - use... Read More
-
배너에 DB 실시간 통계 붙이기 - 클라이언트에서 계산하던 걸 API로 옮긴 이야기
홈 화면 배너에 “등록 배우 X명”, “등록 작품 Y편” 같은 수치를 보여주는 기능이 있었다. 초기에는 React Native에서 API로 유저 수, 작품 수를 따로 가져와서 계산했다. API가 늘어나면서 홈 화면 진입 시 요청이 5~6개가 됐다. 통계 집계 API 한 번의 요청으로 배너에 필요한 수치를 모두 반환하는 API를 만들었다. class DBStatsAPIView(APIView): def get(self, request): from django.db.models import Count stats = { 'user_count': User.o... Read More
-
DRF 피드 API N+1 쿼리 전부 잡기
배너 섹션과 피드를 합친 홈 화면 API 응답이 느렸다. 측정해보니 쿼리가 40개 넘게 나가고 있었다. 문제 파악 # TimedViewSetMixin으로 측정 [PERF] GET /api/v2/feeds/ | 580ms total | 47 queries | 420ms SQL 47개 쿼리. 피드 20개 목록 요청에. 각 쿼리 로그를 보면 패턴이 보였다. SELECT * FROM feeds_feed WHERE id = X (반복 20회) SELECT * FROM users_user WHERE id = X (반복 20회) SELECT * FROM careers_career WHERE user_id = X ... Read More
-
TimesFM + FinBERT로 AI 자동매매 시스템 만들기
자동매매 시스템을 한번 만들어보고 싶었다. 규칙 기반 전략만으로는 시장 변화에 대응하기 어렵고, AI 모델을 붙이면 어느 정도 보완이 될 것 같았다. 실제 주문 연동보다는 파이프라인 설계와 AI 모델 실험에 집중했다. Backtrader로 백테스팅을 돌려서 전략을 검증하는 구조까지 만드는 게 목표였다. 전체 구조 가격 데이터 수집 (yfinance/CCXT) ↓ TimesFM / LSTM 가격 예측 ↓ FinBERT 뉴스 감성 분석 ↓ 신호 결합 → 매매 판단 ↓ Backtrader 백테스팅 가격 예측 — TimesFM Google이 공개한 TimesFM은 시계열 데이터에 특... Read More
-
Django REST API v2 전환과 쿼리 성능 최적화
API가 v1에서 v2로 전환되는 시점에 성능 최적화를 같이 진행했다. 기존 Media 모델에 메타 정보가 너무 많이 쌓여 있었다. 시즌, 에피소드, 제작사 정보까지 한 테이블에. v2에서는 MediaMetaInfo를 별도 모델로 분리했다. 모델 구조 변경 # v1 - 하나의 모델에 다 때려넣기 class Media(models.Model): title = models.CharField(max_length=200) year = models.IntegerField(null=True) genre = models.CharField(max_length=100, null=True) dire... Read More
-
Django ViewSet에 쿼리 실행 시간 측정 붙이기
API 응답이 느리다는 제보를 받았다. 얼마나 느린지 수치가 없으면 어디서 시작해야 할지 모른다. QuerySet 실행 시간을 로그로 남기는 미들웨어를 붙이는 방법도 있지만, 특정 ViewSet 메서드 단위로 시간을 찍고 싶었다. 미들웨어는 요청 전체 시간이라 쿼리가 많은 뷰에서 어떤 쿼리가 병목인지 파악하기 어렵다. 접근 방법 connection.queries를 활용했다. Django DEBUG=True 환경에서는 실행된 모든 SQL과 시간이 기록된다. from django.db import connection, reset_queries import time class TimedViewSetMixin: ... Read More
-
EAST 텍스트 감지 모델로 영상 엔딩 크레딧 구간 자동 감지하기
영화·드라마 엔딩 크레딧에서 스태프 정보를 뽑아야 했다. 크레딧이 언제 시작하는지를 먼저 찾아야 하는데, 사람이 직접 타임코드를 찍는 방식은 콘텐츠가 늘어날수록 현실적이지 않았다. OTT 플랫폼(넷플릭스, 웨이브, 티빙, 디즈니+ 등)마다 플레이어가 다르고, 크레딧 구간을 자동으로 감지하는 방법이 필요했다. 핵심 아이디어 엔딩 크레딧은 텍스트가 가득한 구간이다. 일반 영상 씬과 비교하면 프레임 안에 텍스트 박스 개수가 훨씬 많다. EAST(Efficient and Accurate Scene Text Detector) 모델로 프레임당 텍스트 영역 수를 세면 크레딧 구간을 찾을 수 있다고 판단했다. EAST 모... Read More
-
C(Pro C)로 VAN 대용량 배치 처리와 TCP 소켓 프로세스 개발하기
KIS정보통신은 VAN(Value Added Network) 사업자다. 카드 결제 승인 데이터가 가맹점 → VAN → 카드사 → 정산 흐름으로 이동하는데, 이 중간 처리를 담당한다. 여기서 하루 1,000만 건이 넘는 거래내역 배치 프로세스와, 실시간 승인서버와 정보계(Oracle) 간 원장 동기화 TCP 소켓 프로세스를 맡았다. 기술 스택이 C(Pro C)인 이유 금융권 레거시 시스템은 아직 C 기반이 많다. 특히 AIX(IBM Unix), Linux 서버에서 Oracle DB와 직접 통신하는 Pro C(Oracle Precompiler for C)를 쓴다. Pro C는 C 코드 안에 SQL을 직접 작성하... Read More
-
Git-Hub 사용법
GitHub 사용법 GitHub는 소스 코드 관리를 위한 웹 기반의 호스팅 서비스로, Git을 사용하는 프로젝트를 지원합니다. 이 포스트에서는 GitHub의 기본적인 사용법을 단계별로 설명하겠습니다. 목차 GitHub 계정 만들기 저장소 만들기 로컬 저장소와 연결하기 커밋하고 푸시하기 풀 리퀘스트 보내기 기타 유용한 명령어 GitHub 계정 만들기 GitHub 웹사이트로 이동합니다. Sign up 버튼을 클릭합니다. 필요한 정보를 입력하고 계정을 만듭니다. 저장소 만들기 로그인 후, 오른쪽 상단의 + 버튼을 클릭하고 New repository를 선택합니다. ... Read More
-
Docker로 Apache Airflow 로컬 환경 구성하기
Apache Airflow를 로컬에서 돌리기 위한 Docker Compose 구성을 정리한다. 공식 문서에서 제공하는 docker-compose.yaml 기반이고, CeleryExecutor로 Worker를 여러 개 띄울 수 있는 구성이다. 사전 준비 Docker 설치 Docker Compose v1.29.1 이상 구성 요소 CeleryExecutor 구성에서 돌아가는 서비스들: 서비스 역할 postgres Airflow 메타데이터 DB redis Celery 브로커 (... Read More
-
Markdown & Kramdown 문법
Markdown 문법 폰트 크기, 굵기, 기울기 H1 H2 H3 H4 H5 ## H1 ### H2 #### H3 ##### H4 ###### H5 기울여서 굵게 강조하고 기울여서 혼용하여 기울여서 사용할 수 있습니다 *기울여서* **굵게** ***강조하고 기울여서*** **혼용하여 _기울여서_ 사용할 수 있습니다** 단락 나누기 - – - -- --- BlockQuote Second line Third line Fourth line Fifth line ... Read More
-
Docker와 github.io 활용하여 기술 블로그 만들기
GitHub Pages와 Jekyll로 기술 블로그를 구축했다. 로컬 개발은 Docker로 Ruby/Jekyll 환경 없이 돌리고, main 브랜치에 push하면 GitHub Actions가 자동 빌드해서 배포하는 구조다. 왜 GitHub Pages + Jekyll인가 Velog, Tistory 등 기존 플랫폼 대신 GitHub Pages를 선택한 이유는 하나다. 포스트 전체가 Git 히스토리로 관리되고, 자유롭게 커스터마이징할 수 있다. Jekyll은 Markdown 파일을 정적 HTML로 변환해준다. 서버 없이 GitHub Pages에 올리면 무료로 호스팅된다. 테마 선택 — jekyll-theme-ya... Read More
-
Java Spring에서 외부업체 REST API Web Service 코어 설계하기
서울애널리티카에서 TG삼보컴퓨터와 외부 파트너사(Amway, Epson, Ilyang Logis 등) 간 데이터 연동을 담당했다. 기존 방식은 파트너사가 TG삼보의 DB에 직접 접근하는 방식이었다. 보안상 문제가 있고 인터페이스 관리가 어려워서 REST API로 전환하는 프로젝트를 맡았다. 기존 방식의 문제 파트너사 담당자가 DB 접근 계정을 알고 있음 어떤 쿼리를 실행하는지 모니터링 불가 파트너사별 접근 권한 제어 불가능 DB 스키마가 바뀌면 파트너사 코드도 전부 바꿔야 함 API 코어 설계 모든 파트너사 요청이 공통 인터셉터를 거치도록 했다. @Component public clas... Read More
-
PHP 5.4 레거시 프로젝트 Docker로 이전한 경험
클라이언트가 PHP 5.4로 만들어진 사이트를 유지보수해달라고 했다. 2023년에 PHP 5.4. 로컬에 PHP 5.4를 설치하려면 Homebrew에서 지원도 안 하고, 수동으로 소스 컴파일해야 한다. macOS 최신 버전에서는 의존성 충돌도 있다. Docker로 해결했다. 환경 구성 docker-compose.yml에 nginx + PHP-FPM + MySQL을 묶었다. version: '3.8' services: nginx: image: nginx:alpine ports: - "8080:80" volumes: - ./src:/var/www/html ... Read More
-
FastAPI AI 서버 보일러플레이트 구성하기
AI 모델 서빙 서버를 반복해서 만들다 보면 매번 같은 설정을 처음부터 하게 된다. Poetry 패키지 관리, Docker 구성, pre-commit 코드 품질, 버전 관리까지 한 번에 잡아둔 FastAPI 보일러플레이트를 만들었다. 전체 구조 src/ main.py # FastAPI 앱, 미들웨어, 라우트 interface.py # 요청/응답 Pydantic 모델 util.py # CamelCase 응답 유틸 Dockerfile.pip # pip 기반 Docker 이미지 Dockerfile.poetry # poetry 기반 Docker 이미지 docker-comp... Read More
-
Spring Boot 다중 역할 CMS 설계 — Admin/Inspector/Worker 인증 분리
서울애널리티카에서 촬영 스태프 프로젝트 관리 CMS를 맡았다. 관리자(Admin), 감리(Inspector), 작업자(Worker) 세 가지 역할이 있고, 역할마다 접근 가능한 메뉴와 기능이 달랐다. Spring Boot + Spring Security로 역할별 인증을 분리하고, JPA(Repository) + MyBatis(Mapper) 혼용 구조로 설계했다. 다중 역할 인증 구조 세 역할이 같은 로그인 폼을 쓰지만, 인증 처리는 각각 별도 Provider를 쓴다. HTTP 요청 ↓ CustomAuthenticationFilter (공통 필터) ↓ AuthenticationManager ... Read More
-
Node.js + Python으로 멀티플랫폼 소셜 미디어 크롤러 만든 경험
서울애널리티카에서 소셜 미디어 데이터 수집 파이프라인을 처음부터 구축하는 일을 맡았다. 요구사항은 단순했다. 인스타그램, 네이버 뉴스/카페, 트위터, 뽐뿌, 보배드림에서 키워드 기반으로 게시물을 수집하고 대시보드에 보여줄 것. 문제는 각 플랫폼마다 수집 방식이 완전히 달랐다. 구조 결정 Node.js를 메인 서버로 쓰고, 처리가 복잡한 Instagram 부분은 Python으로 분리했다. src/ models/ sns/ # Instagram, Twitter community/ # 뽐뿌, 보배드림 portal/ # 네이버 뉴스, 카페 routes/ ... Read More
-
Flutter PCR 의료기기 진단 앱 개발 - BLE UART 통신과 Ct 값 알고리즘 이식
서울애널리티카에서 위즈바이오의 PCR 의료기기 진단 앱을 맡았다. 대상 기기는 Cleo One — COVID-19를 포함한 다중 채널 PCR 진단기다. 앱은 BLE로 기기와 통신해서 PCR 형광 데이터를 실시간으로 수집하고, Ct(Cycle Threshold) 값을 산출해서 양성/음성을 판정한다. 기존 C#으로 작성된 Ct 값 알고리즘을 Dart로 이식하는 게 핵심 과제였다. App Store / Play Store 출시까지 담당했고, 이후 단일 기기(Single)에서 다중 기기 동시 테스트(Multi) 버전으로 확장했다. BLE 통신 구조 — Nordic UART Cleo One은 Nordic Semicon... Read More
-
PHP 웹 서버 온프레미스 → GCP Docker 이전
서울애널리티카에서 인하대학교 PHP 웹 서비스의 서버 이전을 맡았다. 기존 온프레미스 서버에서 회사 GCP 서버로 옮기고, Docker 기반으로 재구성하는 작업이었다. 기존 환경 온프레미스 Linux 서버에서 Apache + PHP 5.x 직접 구동 파일이 서버에 직접 배포된 방식 (FTP 접근) DB는 동일 서버에 MySQL 이전 목표는 GCP VM에 Docker 기반으로 올려서 이전 가능성과 관리 편의성을 높이는 것이었다. Docker 구성 PHP 5.x 레거시라 기존 버전을 유지해야 했다. Docker 덕분에 호스트 환경과 분리할 수 있었다. # PHP 5.6 + Apache FROM... Read More
-
PHP 레거시 CMS 절차지향 → MVC2 패턴으로 리팩토링
에스아이알소프트의 CMS 코어 코드는 2010년대 초에 만들어진 레거시였다. 단일 PHP 파일에 DB 쿼리, 비즈니스 로직, HTML이 전부 섞여 있는 구조였다. // 기존 구조 예시 <?php $conn = mysql_connect(...); // mysql_* 함수 사용 중 $result = mysql_query("SELECT * FROM posts WHERE id=" . $_GET['id']); $row = mysql_fetch_array($result); // 바로 HTML 출력... ?> <html> <body> <h1><?= $row['tit... Read More
-
Git API로 PHP 프로젝트 FTP/SFTP 자동 배포 구현하기
에스아이알소프트 CMS는 여러 고객사 서버에 올라가 있었다. 업데이트가 생기면 개발자가 직접 FTP로 파일을 하나씩 올려야 했다. 실수로 파일을 빠뜨리거나 이전 버전을 덮어씌우는 사고가 간간이 있었다. CI/CD 도구 없이 Git API를 활용해서 자동 배포를 구현하기로 했다. 기본 아이디어 GitHub API로 최신 릴리스 태그를 읽어서, 현재 설치된 버전보다 높으면 자동으로 업데이트 파일을 내려받아 배포하는 방식이다. 고객사 서버에서 스케줄러 실행 → GitHub API로 최신 릴리스 버전 확인 → 현재 버전과 비교 → 버전이 높으면 변경된 파일 목록 조회 → FTP/SFTP... Read More
-
PHP CMS에 inicis 간편인증 모듈 연동하기
에스아이알소프트에서 운영하던 PHP CMS에 inicis 간편인증 모듈을 붙이는 작업을 맡았다. 간편인증은 공인인증서 없이 SMS나 생체인식으로 본인확인을 하는 방식이다. inicis 연동 구조 inicis는 클라이언트 → inicis 서버 → 콜백 방식으로 동작한다. 사용자 브라우저 → inicis 인증 팝업 열기 → inicis 서버에서 인증 처리 → 인증 결과를 콜백 URL로 POST → 우리 서버에서 결과 검증 PHP로 inicis에서 제공하는 SDK를 연동해야 했다. SDK가 PHP 5.x 기준이라 현재 환경에서 deprecated 함수를 일부 교체해야 했다. 가장 ... Read More
-
TypeScript + GraphQL로 B2B 경비 정산 서비스 백엔드 설계하기
B2B 경비 정산 서비스의 백엔드와 모바일 앱을 풀스택으로 개발했다. 기업의 임직원 경비 신청 → 승인 → 정산 흐름을 처리하는 서비스다. 백엔드는 Node.js + TypeScript, API 레이어는 GraphQL(graphql-yoga), 데이터는 TypeORM + MySQL로 구성했다. 모바일 앱은 React Native + Expo + Apollo Client. 왜 GraphQL인가 REST API로 설계했다면 경비 신청 상세, 프로젝트 정보, 승인자 목록을 각각 다른 엔드포인트로 호출해야 했다. GraphQL로 클라이언트가 필요한 데이터를 한 쿼리로 가져오게 설계했다. // 경비 신청 스키마 예시... Read More
-
CentOS 5 → CentOS 7 서버 이전 삽질기
한경ITS에서 마지막으로 맡은 작업이 서버 이전이었다. 운영 중인 서버가 CentOS 5였다. CentOS 5는 2017년에 이미 EOL이었는데 계속 쓰고 있었다. 보안 업데이트가 전혀 안 되는 상태였다. 신규 CentOS 7 서버를 구축하고 기존 PHP/Apache 서비스를 전부 이전하는 작업이었다. CentOS 5와 7의 차이 처음에 별거 아니라고 생각했는데 생각보다 차이가 컸다. init vs systemd CentOS 5는 SysVinit 기반이라 서비스 관리가 service 명령어였다. CentOS 7은 systemd라서 systemctl을 써야 한다. # CentOS 5 service httpd... Read More
-
PHP + jQuery로 사내 인트라넷 리뉴얼하기
첫 직장인 한경ITS에서 맡은 첫 번째 프로젝트가 사내 인트라넷 리뉴얼이었다. 기존 그룹웨어 시스템이 오래돼서 UI도 낡고 기능도 부족한 상태였다. PHP, JavaScript(jQuery), MySQL 스택이었다. 백엔드 API부터 프론트엔드까지 풀스택으로 혼자 담당했다. 기존 시스템 문제 인터페이스가 2010년대 초반 수준으로 노후화 기능이 게시판, 결재 정도만 있고 부서별 필요 기능이 누락 코드 구조가 파일 단위로 로직이 흩어진 절차적 방식 리뉴얼 방향 UI는 전면 재설계. jQuery로 동적 인터랙션을 추가했다. // 기존: 파일 하나에 DB 쿼리와 HTML이 뒤섞인 구조 // 리뉴... Read More
