Công cụ CLI local-first phục vụ nghiên cứu nội dung crypto — scrape RSS từ nhiều nguồn, AI scoring bằng Claude Haiku (0–10), research brief bằng Claude Sonnet, lưu vào SQLite, hiển thị tiered inbox (HOT / OTHER). Người dùng tự viết bài rồi đăng lên X (Twitter). Đây KHÔNG phải bot tự động — mọi post đều do người viết và duyệt.
TypeScript + Node.js, scrape RSS song song từ 16 nguồn (9 enabled), dedup theo URL, score bằng Claude Haiku (batch 10 bài/call), generate research brief bằng Claude Sonnet (per article), lưu vào SQLite (better-sqlite3). Mỗi bài viết được theo dõi qua state machine (new → read → starred → drafted → posted → dismissed). Bài score < 6 tự động dismissed.
Cấu hình credentials qua .env; nguồn RSS chỉnh trong src/config/rss-sources.ts.
Chi tiết kiến trúc, luồng dữ liệu và module: xem PROJECT_OVERVIEW.md.
- RSS scraper — scrape 6 nguồn song song (
Promise.allSettled), dedup theo URL (INSERT OR IGNORE), timeout 10 giây/feed. - AI scoring (Haiku) — batch scoring 10 bài/call, điểm 0–10 (decimal, e.g. 7.2, 8.5) + 12 category cố định (L2, DeFi, AI x Crypto, Developer Tooling, SocialFi, Bitcoin, Regulation, Macro, Research/Protocol, Price/Trading, Exchange/Corporate, Other) + reasoning + suggested angle; cost tracking (token + USD).
- Tiered inbox — HOT (score 7.5+) hiện mặc định với suggested angle, OTHER (6–7.4), Dismissed (< 6) tự ẩn. Recency filter 30 ngày mặc định,
--days=Nđể override. - Research brief (Sonnet) —
npm run brief <id>generate brief cho bất kỳ article: WHY IT MATTERS + related stories + suggested angles (VN primary + EN). Hook-first angle prompts (không tóm tắt lại tin). Cache trong DB — lần 2 không gọi API;npm run brief -- <id> --refreshđể gọi lại Sonnet và ghi đè cache. - Angle markdown (Obsidian) — mỗi lần brief xong tự ghi file
.mdvào~/Dev/vault/projects/content-creator/angles/từ template~/Dev/vault/templates/angle-template.md(placeholder__KEY__— tương thích obsidian/YAML, không dùng{{}}). Terminal hiện💾 Saved: ~/.... Thiếu template → chỉ cảnh báo, brief vẫn hiện. - User context — brief cá nhân hóa dựa trên
about-me.md+tone-guidelines.mdđọc từ~/Dev/projects/Content-Creator/; không có thì vẫn chạy. - OG image — tự lấy
og:imagetừ trang gốc, 5 request song song, timeout 5 giây; không bao giờ crash. - Article state machine —
new→read→starred→drafted→posted→dismissed. - Source upsert —
seedSources()dùngON CONFLICT DO UPDATE— thay đổi URL hoặc enabled/disabled trong config tự sync vào DB. - Graceful degradation — lỗi 1 feed hoặc Haiku/Sonnet API failure không crash toàn bộ.
- Source status —
npm run sources:statusin bảng per-source: enabled/disabled, số bài, ngày bài gần nhất, lần fetch cuối; không cần Anthropic API key. - Verbose fetch —
npm run fetch -- --verboselog per-feed: số item feed trả về, new vs duplicate sau mỗi nguồn. - Scoring diagnostic —
npm run stats:scoring [--days=N]in histogram 8 bucket (chia tại 7.5) + tỉ lệ HOT/OTHER/dismissed + breakdown theo Haiku category; không cần API key. - Per-source diagnostic —
npm run stats:sources [--days=N]in bảng per-source: total scored, avg score, HOT/OTHER/dismissed counts, HOT%; không cần API key. - Near-HOT inspector —
npm run inspect:near-hot [--limit=N] [--days=N]hiện 10 bài score 7–7.4 với full Haiku reasoning và suggested angle; dùng để diagnose threshold (hiện tại: 7.5). - Reply tracking (X analytics) —
npm run reply:analyzeđọc 2 file CSV X Analytics, phân loại reply KOL vs original (reply = text bắt đầu@), tra niche từng KOL (security/tokenomics/l1l2/other), enrich qua TwitterAPI.io (giờ reply + imp/eng tweet gốc), tích lũy vào bảngreply_tracking(idempotent theo Post id), ghi snapshot JSON ra vault cho dashboard Newsroom.--skip-enrichchạy CSV-only (không tốn API);--content=/--overview=override path. - 71 tests — Vitest, in-memory SQLite; bao gồm dedup, seeding, states, mocked Anthropic (cả Haiku và Sonnet), fallback scoring, brief cache, và reply tracking (CSV parser, niche lookup, DB idempotency, snapshot builder, orchestrator).
| Lớp | Công nghệ |
|---|---|
| Runtime | Node.js 20+, TypeScript 5.7, tsx |
| Database | SQLite via better-sqlite3 (sync API) |
| AI — Filter | Anthropic SDK — Claude Haiku (claude-haiku-4-5-20251001): batch relevance scoring |
| AI — Brief | Anthropic SDK — Claude Sonnet (claude-sonnet-5): research brief generation |
| Reply enrichment | TwitterAPI.io via native fetch (TWITTERAPI_IO_KEY) — parent-tweet metrics + reply hour |
| RSS | rss-parser |
| Config | dotenv + .env; AI_MODELS (haiku / sonnet) trong src/config/index.ts — đổi model một chỗ cho toàn app |
| Test | Vitest |
| Publishing (planned) | X (Twitter) API |
- Node.js 20+ (khuyến nghị LTS)
- npm (đi kèm Node)
- Anthropic API key (cần cho scoring + brief)
cd InkPilot
npm install
cp .env.example .env
# Sửa .env: điền ANTHROPIC_API_KEY| Lệnh | Mô tả |
|---|---|
npm run fetch |
Scrape RSS → dedup → insert → OG image → Haiku scoring |
npm run fetch -- --verbose |
Như trên + log per-feed: số item, new vs duplicate |
npm run fetch
# Output:
# Fetch complete:
# Sources checked: 6
# New articles: 14
# Duplicates: 823
# Scored: 14 (HOT: 3, OTHER: 7, Dismissed: 4)
# Cost: $0.0031 (Haiku: 3.2K in, 1.4K out tokens)
npm run fetch -- --verbose
# Thêm per-feed log:
# Decrypt: 25 items → 3 new, 22 duplicates
# Bankless: 18 items → 2 new, 16 duplicates
# ...# Mặc định: hiện HOT trước, sau đó OTHER (last 30 days)
npm run list
# Chỉ HOT (score 7.5+) — có suggested angle
npm run list -- --hot
# Chỉ OTHER (score 6–7.4)
npm run list -- --other
# Tất cả (bao gồm dismissed)
npm run list -- --all
# Recency filter
npm run list -- --hot --days=7 # chỉ 7 ngày gần nhất
npm run list -- --all --days=90 # mở rộng ra 3 tháng
# Kết hợp filter
npm run list -- --hot --today --limit=10
npm run list -- --source=vitalik --limit=5
npm run list -- --state=new --todayOutput mặc định (tiered):
HOT 🔥 (score 7.5+)
──────────────────────────────────────────────────────────────────────────────────────────
[9.1] 42 Base introduces new fee mechanism for... Base Blog 2h ago
→ Compare with Arbitrum's approach and developer adoption impact
[8.5] 38 EigenLayer restaking hits $10B TVL amid.. The Block 5h ago
→ What restaking saturation means for ETH staking yields
OTHER (score 6–7.4)
──────────────────────────────────────────────────────────────────────────────────────────
[7.2] 41 DeFi TVL reaches new high amid... CoinDesk 3h ago
[6.8] 39 Kraken acquires... CoinDesk 6h ago
Showing 2 of 139 hot, 2 of 423 other (last 30 days). Use --days=N or --limit=N to adjust.
# Generate brief cho article #42
npm run brief 42
# Bỏ qua cache + regenerate Sonnet + ghi đè file angle
npm run brief -- 42 --refreshOne-time — vault template (Obsidian):
mkdir -p ~/Dev/vault/templates ~/Dev/vault/projects/content-creator/angles
cp templates/angle-template.md ~/Dev/vault/templates/angle-template.mdOutput:
══════════════════════════════════════════════════════════════
BRIEF #42 — Base introduces new fee mechanism
Source: Base Blog | Score: 9.1 | Published: 2 days ago
URL: https://base.mirror.xyz/...
══════════════════════════════════════════════════════════════
WHY IT MATTERS
Base's new fee mechanism decouples L2 transaction costs from L1 gas
spikes, affecting all apps deployed on Base (~2,500 contracts).
RELATED STORIES (last 7 days)
[8.5] #38 EigenLayer restaking hits $10B TVL — 5 days ago
[7.2] #41 DeFi TVL reaches new high — 3 days ago
SUGGESTED ANGLES
🇻🇳 Tiếng Việt
1. Fee mechanism mới của Base — template cho các L2 khác hay chỉ
workaround cho vấn đề gas của riêng Base?
2. So sánh cách Base handle fee vs Arbitrum Nitro — cùng bài toán
nhưng approach khác nhau hoàn toàn.
🌐 English
1. Base's fee redesign: structural advantage for app-specific L2s
or a band-aid for Ethereum's gas problem?
══════════════════════════════════════════════════════════════
Cost: $0.01 | Sonnet: 1.2K in / 0.4K out tokens
💾 Saved: ~/Dev/vault/projects/content-creator/angles/2026-04-18-42-base-fee-mechanism.md
══════════════════════════════════════════════════════════════
Lần 2 chạy cùng article → dùng cache, không gọi API (file angle vẫn được ghi lại / overwrite cùng tên):
══════════════════════════════════════════════════════════════
(cached — no API call)
💾 Saved: ~/Dev/vault/projects/content-creator/angles/2026-04-18-42-base-fee-mechanism.md
══════════════════════════════════════════════════════════════
# Histogram phân bố score + HOT/OTHER/dismissed ratio (7 ngày mặc định)
npm run stats:scoring
npm run stats:scoring -- --days=30
# Per-source: avg score, HOT count, HOT% (7 ngày mặc định)
npm run stats:sources
npm run stats:sources -- --days=14
# 10 bài score 7–7.9 kèm full Haiku reasoning (để kiểm tra threshold)
npm run inspect:near-hot
npm run inspect:near-hot -- --limit=20 --days=14Phân tích reply KOL từ X Analytics CSV → enrich → snapshot JSON cho dashboard Newsroom.
One-time — credential: thêm TWITTERAPI_IO_KEY vào .env (lấy key tại twitterapi.io).
Input: export 2 file CSV từ X Analytics vào ~/Dev/vault/projects/content-creator/analytics/raw/:
content-latest.csv(per-post: Post id, Date, Post text, Impressions, Engagements, New follows…)overview-latest.csv(per-day topline)
# Đọc CSV → phân loại reply/original → tra niche → enrich (TwitterAPI.io) → reply_tracking → snapshot JSON
npm run reply:analyze
# CSV-only, không gọi API (không tốn tiền)
npm run reply:analyze -- --skip-enrich
# Override đường dẫn CSV
npm run reply:analyze -- --content=/path/to/content.csv --overview=/path/to/overview.csvOutput: in summary terminal (reply count, imp share, dud rate, top KOLs, by-niche) và ghi snapshot vào ~/Dev/vault/projects/content-creator/analytics/reply-tracking/latest.json (schema cố định — Newsroom đọc file này).
- Reply detection: post text bắt đầu bằng
@→ reply; KOL = handle ngay sau@đầu tiên. - Niche: tra từ
src/config/kol-niches.ts(security/tokenomics/l1l2/other; không có trong list →other). Sync tay từ vaultkol-reply-list.md. - Dud: reply có impressions < 50.
- Idempotent: chạy lại cùng CSV không nhân đôi row (key theo Post id); enrichment chỉ chạy 1 lần/row.
- Graceful: thiếu
TWITTERAPI_IO_KEY→ báo lỗi rõ (hoặc dùng--skip-enrich); lỗi enrich 1 reply → skip + warn, không crash; thiếu content CSV → lỗi rõ; thiếu overview CSV → period suy từ content.
npm run sources:status
# Output:
# Sources status:
#
# Source Status Articles Last article Last fetch
# ──────────────────────────────────────────────────────────────────────
# Bankless enabled 243 arts 3h ago 2h ago
# CoinDesk enabled 512 arts 1h ago 1h ago
# Decrypt enabled 389 arts 2h ago 2h ago
# CoinCu News enabled 87 arts 5h ago 4h ago
# Ethereum Foundation Blog enabled 34 arts 3d ago 2h ago
# Vitalik's Blog enabled 12 arts 14d ago 2h ago
# The Block disabled 0 arts — —
# Base Blog disabled 0 arts — —
# ...npm test # Chạy 71 tests
npm run test:watch # Watch modesrc/
├── config/
│ ├── index.ts # Load .env → Config; AI_MODELS, SCORE_THRESHOLDS, REPLY_THRESHOLDS, requireTwitterApiIoKey
│ ├── types.ts # Config interface
│ ├── rss-sources.ts # Source of truth: 16 feeds (9 enabled) × 4 tiers
│ └── kol-niches.ts # KOL handle → niche map (lookupNiche); synced by hand from vault kol-reply-list.md
├── content-filter/
│ ├── index.ts # filterNewArticles orchestrator (batch + state update)
│ ├── haiku-filter.ts # Anthropic SDK — scoreArticles (batch 10, cost tracking)
│ └── types.ts # ArticleToScore, FilterResult, BatchFilterResult
├── research-briefer/
│ ├── index.ts # generateBrief orchestrator (load → related → Sonnet → cache → export angle)
│ ├── sonnet-briefer.ts # Anthropic SDK — generateBriefWithSonnet (cost tracking)
│ ├── angle-exporter.ts # fill __KEY__ template → vault/angles/*.md
│ ├── formatter.ts # printBrief — terminal output formatting
│ └── types.ts # Brief, RelatedArticle, SuggestedAngles
├── database/
│ ├── index.ts # Singleton DB connection (WAL, foreign keys)
│ ├── schema.ts # CREATE TABLE × 8 + indexes
│ ├── migrations.ts # Run schema + auto-migrate old tables
│ ├── types.ts # ArticleState, Source, Article, ArticleStateRow
│ ├── sources.ts # seedSources (upsert), getEnabledSources, getSourceBySlug
│ ├── articles.ts # insertArticle (dedup), getArticlesWithFilter
│ ├── article-states.ts # createArticleState, updateArticleState
│ ├── filter-results.ts # insertFilterResult, isArticleScored, cacheArticleBrief, getCachedBrief
│ ├── posts.ts # insertPost, getPostsByPlatform, countTodayPosts
│ └── reply-tracking.ts # upsertReply (idempotent), updateReplyEnrichment, getReplies* queries
├── feed-fetcher/
│ ├── index.ts # runFetch orchestrator (parallel + OG + scoring)
│ ├── rss-parser.ts # fetchFeed → FeedItem[]
│ └── og-extractor.ts # extractOgImageUrl → string | null
├── reply-tracking/
│ ├── index.ts # runReplyAnalyze orchestrator (parse → upsert → enrich → snapshot)
│ ├── csv-parser.ts # parseCsv (RFC-4180), parseContentCsv/parseOverviewCsv, parseXDate, extractKolHandle, derivePeriod
│ ├── snapshot-builder.ts # buildSnapshot (pure) → Newsroom contract; weekStart, nowIsoPlus7
│ ├── enricher.ts # enrichReply (TwitterAPI.io); mapTweet, computeHourPlus7
│ ├── exporter.ts # exportSnapshot → vault reply-tracking/latest.json
│ └── types.ts # ContentRow, OverviewRow, Period, ReplyEnrichment, ReplySnapshot
├── scripts/
│ ├── fetch.ts # CLI: npm run fetch [--verbose]
│ ├── list.ts # CLI: npm run list (tiered inbox, --days=N)
│ ├── brief.ts # CLI: npm run brief <id> [--refresh]
│ ├── sources-status.ts # CLI: npm run sources:status (per-source health table)
│ ├── stats-scoring.ts # CLI: npm run stats:scoring [--days=N] (histogram + categories)
│ ├── stats-sources.ts # CLI: npm run stats:sources [--days=N] (per-source HOT rate)
│ ├── inspect-near-hot.ts # CLI: npm run inspect:near-hot (score 7–7.4 + full reasoning)
│ └── reply-analyze.ts # CLI: npm run reply:analyze [--skip-enrich] [--content=] [--overview=]
└── utils/
└── logger.ts # Leveled, colored console logging
templates/
└── angle-template.md # Copy to ~/Dev/vault/templates/angle-template.md
tests/
├── database.test.ts # 8 tests: schema, posts CRUD
├── feed-fetcher.test.ts # 14 tests: sources, articles, states, mocked fetch
├── content-filter.test.ts # 10 tests: filter DB, scoring, mocked Anthropic, fallback
├── research-briefer.test.ts # 7 tests: cache, related query, mocked Sonnet, fallback
└── reply-tracking.test.ts # 32 tests: niche lookup, CSV parser, DB idempotency, snapshot builder, enricher, orchestrator
| Tier | Nguồn | Loại | Ngôn ngữ | Tần suất | Status |
|---|---|---|---|---|---|
| 1 | Decrypt, Bankless, CoinDesk, Blockworks | crypto / defi | en | 1h | enabled |
| 1 | ETH Research Forum, Bitcoin Optech | research-technical | en | 1h | enabled |
| 1 | The Block | crypto | en | 1h | disabled — Cloudflare 403 |
| 2 | Ethereum Foundation Blog, Arbitrum Foundation | protocol / protocol-l2 | en | 1h | enabled |
| 2 | Base Blog | protocol | en | 1h | disabled — Cloudflare JS challenge |
| 2 | Optimism Blog | protocol-l2 | en | 1h | disabled — feed stale since Jun 2025 |
| 2 | Vitalik's Blog | protocol | en | 1h | disabled — kept for FK integrity (articles in DB) |
| 3 | CoinCu News | vietnamese | vi | 2h | enabled |
| 3 | Coin68, Tạp Chí Bitcoin | vietnamese | vi | 2h | disabled — kept for FK integrity |
| 4 | MarkTechPost | ai | en | 2h | disabled — kept for FK integrity |
Thêm/xóa/sửa nguồn: sửa file src/config/rss-sources.ts → thay đổi tự sync vào DB lần fetch tiếp.
Haiku scoring dùng hệ thống prompt theo niche priority stack của creator:
| Priority | Niche | Ví dụ |
|---|---|---|
| PRIMARY (~50%) | Security | Hacks, exploits, bridge attacks, post-mortems, smart contract vulnerabilities, audit findings, phishing, bug bounties |
| SECONDARY (~30%) | Tokenomics | Token design, emission schedules, liquid/restaking (Lido, EigenLayer), protocol revenue, fee mechanisms, treasury, airdrop design |
| TERTIARY (~20%) | L1/L2 Infra | Ethereum upgrades + EIPs, L2 rollups, Bitcoin Optech/Lightning, DA layers, MEV/PBS |
| Score | Ý nghĩa | Hành động |
|---|---|---|
| 9.0–10.0 | Sự cố bảo mật lớn có phân tích, nâng cấp protocol landmark, thay đổi tokenomics tầm ecosystem | HOT — hiện mặc định |
| 7.5–8.9 | Phát triển rõ trong 3 niche — có angle mạnh để viết take | HOT — hiện mặc định |
| 5.0–7.4 | Liên quan nhẹ, ít tiềm năng angle | OTHER — hiện khi --other |
| < 5.0 | Spam, listicle, price prediction, tin ngoài niche | Auto-dismissed (< 6.0) |
Penalty (score thấp hơn): price analysis thuần, celebrity endorsements, meme coins, exchange listings, airdrop farming guides, generic regulation, price predictions.
Chi phí thực tế: ~$0.50 cho 1000 bài (Haiku pricing: $0.80/1M input, $4.00/1M output tokens).
Sonnet brief cho mỗi article, cá nhân hóa theo voice/tone của user:
| Thành phần | Mô tả |
|---|---|
| WHY IT MATTERS | 2–3 câu giải thích ý nghĩa, dùng số liệu cụ thể |
| RELATED STORIES | Tối đa 5 bài liên quan (7 ngày, score ≥ 6, cùng category/keyword) |
| SUGGESTED ANGLES | 2–3 góc VN + 1 góc EN tối đa; hook đầu dòng, 2–3 câu, không tóm tắt lại headline |
| Cache | Kết quả lưu trong filter_results.ai_context — lần 2 không gọi Sonnet; --refresh để gọi lại |
| Angle file | Markdown vào ~/Dev/vault/projects/content-creator/angles/YYYY-MM-DD-<id>-<slug>.md (template __KEY__) |
User context: đọc động từ ~/Dev/projects/Content-Creator/about-me.md + tone-guidelines.md. Không có file thì vẫn chạy (graceful fallback).
Chi phí: ~$0.01/brief (Sonnet pricing: $3.00/1M input, $15.00/1M output tokens).
- Slice 1 — First Cast (DB schema, posts CRUD, config, logger)
- Slice 2 — First Fetch (RSS scrape → dedup → SQLite → OG image → CLI list)
- Slice 3 — Smart Filter (Claude Haiku scoring → tiered inbox, auto-dismiss, decimal scores, recency filter)
- Slice 4 — Research Brief (Claude Sonnet brief → WHY IT MATTERS + related stories + suggested angles, cache, user context)
- v0.2.0 — Feed fixes (disable The Block + Base Blog, Cloudflare-blocked),
npm run sources:status,--verboseflag - v0.3.0 — Source expansion (add Blockworks, ETH Research, Bitcoin Optech, Arbitrum, Optimism; remove 4 stale sources; new categories
protocol-l2+research-technical) - v0.3.1 — Data integrity fix (939 articles re-mapped after source_id orphan bug; disable stale Optimism feed; add
repairArticleSourceIds) - v0.4.0 — HOT pipeline diagnostics (
stats:scoring,stats:sources,inspect:near-hot) - v0.4.1 — HOT threshold 8.0 → 7.5, fixed 12-category Haiku enum, recency filter bug fix
- v0.4.2 — List footer shows
"X of Y"total count when limit is hit; refactoredlist.tscondition builder - v0.4.3 — Haiku niche priority stack (Security > Tokenomics > L1/L2 Infra); fix process hang on fetch/brief completion
- v0.5.0 — Reply Tracking:
reply_trackingtable +npm run reply:analyze(X Analytics CSV → KOL niche → TwitterAPI.io enrichment → snapshot JSON for Newsroom);kol-niches.tsconfig;TWITTERAPI_IO_KEY - v0.5.1 — Fix
reply:analyzescope + enrichment:summary/enrichment scoped to latest week, idempotent re-runs, QPS throttle + 429 backoff (TWITTERAPI_IO_QPS) - v0.5.2 — Brief model → Claude Sonnet 5 (
claude-sonnet-5); disable adaptive thinking + raisemax_tokens1000 → 1300 for the new tokenizer - Slice 5 — X API Adapter (publish to Twitter)
- Slice 6 — TUI / Ink (interactive terminal UI)
- Future — realtime Reply Monitor (builds on the
reply_trackingdata layer)
| Lỗi | Giải pháp |
|---|---|
Missing required env var: ANTHROPIC_API_KEY |
Điền ANTHROPIC_API_KEY trong .env |
| Haiku/Sonnet API error / rate limit | Scoring bỏ qua — bài giữ state new; brief trả partial result |
| The Block / Base Blog không fetch được | Đã disabled do Cloudflare block — xem npm run sources:status |
| Feed timeout / 4xx errors | Bình thường — feed lỗi được bỏ qua, các nguồn khác vẫn chạy |
No scored articles found |
Chạy npm run fetch trước; dùng --all để xem tất cả kể cả chưa score |
Article #X not found |
Kiểm tra ID bằng npm run list -- --all; có thể article không tồn tại |
Article #X has not been scored yet |
Chạy npm run fetch để score article trước khi tạo brief |
| Brief hiện "(cached — no API call)" | Bình thường — brief đã cache; npm run brief -- <id> --refresh để gọi lại Sonnet |
Could not save angle file / template not found |
Copy templates/angle-template.md → ~/Dev/vault/templates/angle-template.md; thư mục output được tạo tự động |
Missing required env var: TWITTERAPI_IO_KEY |
Điền TWITTERAPI_IO_KEY trong .env (lấy tại twitterapi.io), hoặc chạy npm run reply:analyze -- --skip-enrich để bỏ qua enrich |
Content CSV not found |
Export content-latest.csv vào ~/Dev/vault/projects/content-creator/analytics/raw/, hoặc dùng --content=PATH |
reply:analyze enrich báo "failed: N" |
Bình thường — lỗi API 1 reply được skip + warn, các reply khác vẫn enrich; byHour/parentSizeCorrelation chỉ tính từ reply enrich thành công |
| DB migration log | Lần đầu chạy sau update sẽ thấy migration log — bình thường, tự xử lý |
MIT