Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
99 changes: 95 additions & 4 deletions .github/scripts/daily-digest.sh
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,75 @@ echo "new issues: $ISSUES_COUNT"
echo "commits main/dev: $COMMITS_MAIN_COUNT/$COMMITS_DEV_COUNT"
echo "::endgroup::"

# --- admin verify stats (assets DB + operation DB) ---
# 두 DB 모두 secret 설정되어 있을 때만 활성. 없으면 silent skip.
echo "::group::verify-stats"

VERIFY_STATS_JSON="[]"
VERIFY_TOTALS_JSON='{"yesterday":0,"last_7d":0}'
NAG_ADMINS_TEXT=""

if [ -n "${ASSETS_DATABASE_URL_RO:-}" ] && [ -n "${OPERATION_DATABASE_URL_RO:-}" ]; then
# 1) admins from operation DB
admins_json=$(PGCONNECT_TIMEOUT=10 psql "$OPERATION_DATABASE_URL_RO" -t -A -F$'\t' -c \
"SELECT id::text, username, COALESCE(display_name, '') FROM public.users WHERE is_admin = true ORDER BY username" \
2>/dev/null \
| jq -R -s 'split("\n") | map(select(length > 0) | split("\t") | {id:.[0], username:.[1], display_name:(.[2] // "" | if length == 0 then null else . end)})' \
|| echo "[]")

admin_count=$(echo "$admins_json" | jq 'length')
echo "admins: $admin_count"

if [ "$admin_count" -gt 0 ]; then
# 2) verified counts per admin from assets DB
# yesterday 윈도우 = [어제 00:00 KST, 오늘 00:00 KST). last_7d 윈도우 = 7일 rolling.
admin_ids_array=$(echo "$admins_json" | jq -r '[.[].id] | join("\",\"")')
counts_json=$(PGCONNECT_TIMEOUT=10 psql "$ASSETS_DATABASE_URL_RO" -t -A -F$'\t' -c \
"SELECT verified_by::text,
COUNT(*) FILTER (
WHERE verified_at >= (date_trunc('day', now() AT TIME ZONE 'Asia/Seoul') - interval '1 day') AT TIME ZONE 'Asia/Seoul'
AND verified_at < date_trunc('day', now() AT TIME ZONE 'Asia/Seoul') AT TIME ZONE 'Asia/Seoul'
) AS yesterday,
COUNT(*) FILTER (WHERE verified_at >= now() - interval '7 days') AS last_7d
FROM public.raw_posts
WHERE verified_by = ANY(ARRAY[\"$admin_ids_array\"]::uuid[])
AND verified_at IS NOT NULL
AND verified_at >= now() - interval '7 days'
GROUP BY verified_by" \
2>/dev/null \
| jq -R -s 'split("\n") | map(select(length > 0) | split("\t") | {admin_id:.[0], yesterday:(.[1] | tonumber), last_7d:(.[2] | tonumber)})' \
|| echo "[]")

# 3) zip — admin + counts (fall-through to 0 if no row)
VERIFY_STATS_JSON=$(jq -n \
--argjson admins "$admins_json" \
--argjson counts "$counts_json" \
'[$admins[] as $a
| ($counts[] | select(.admin_id == $a.id)) // {yesterday: 0, last_7d: 0}
| {admin_id: $a.id, username: $a.username, display_name: $a.display_name,
yesterday: .yesterday, last_7d: .last_7d, needs_nag: (.yesterday == 0)}]
| sort_by(-.yesterday)')

# 4) totals + nag list
VERIFY_TOTALS_JSON=$(echo "$VERIFY_STATS_JSON" | jq '{
yesterday: (map(.yesterday) | add // 0),
last_7d: (map(.last_7d) | add // 0)
}')

NAG_ADMINS_TEXT=$(echo "$VERIFY_STATS_JSON" | jq -r '
map(select(.needs_nag)) |
if length == 0 then "" else
map((.display_name // .username)) | join(", ")
end')

echo "totals: $(echo "$VERIFY_TOTALS_JSON" | jq -c .)"
echo "nag: ${NAG_ADMINS_TEXT:-(none)}"
fi
else
echo "skip — ASSETS_DATABASE_URL_RO and/or OPERATION_DATABASE_URL_RO not set"
fi
echo "::endgroup::"

# --- Claude Haiku summary ---
echo "::group::summarize"

Expand All @@ -69,25 +138,32 @@ DATA_JSON=$(jq -n \
--argjson open_issues_assigned "$OPEN_ISSUES_ASSIGNED" \
--argjson commits_main "$COMMITS_MAIN" \
--argjson commits_dev "$COMMITS_DEV" \
--argjson verify_stats "$VERIFY_STATS_JSON" \
--argjson verify_totals "$VERIFY_TOTALS_JSON" \
'{
merged_prs: $merged_prs,
open_prs: $open_prs,
new_issues: $new_issues,
open_issues_assigned: $open_issues_assigned,
commits_main: $commits_main,
commits_dev: $commits_dev
commits_dev: $commits_dev,
admin_verify_stats: $verify_stats,
admin_verify_totals: $verify_totals
}')

PROMPT_TEXT='당신은 decoded 모노레포의 일일 리포트를 한국어로 작성합니다.
아래 JSON은 지난 24시간의 GitHub 활동입니다.
아래 JSON은 지난 24시간의 GitHub 활동 + admin verify 통계입니다.

브랜치 맥락: decoded는 `feature/* → dev → main` 플로우. 대부분 작업은 dev에 병합되고, main은 릴리즈/CI 전용.

admin_verify_stats: admin 별 어제 verify (raw_post 검수) 카운트. `needs_nag=true` 인 admin 은 어제 0건이므로 부드럽게 독촉 메시지 추가.

요청:
- 주요 변화 3~5개를 **base 브랜치별로 그룹핑**해서 제시 (main → dev 순서)
- 브랜치에 해당 항목이 없으면 해당 그룹 생략
- review 대기중이거나 오래된 open PR 있으면 "주의" 섹션 (주의는 그룹핑 없이 평평하게)
- 전체 400자 이내, plain text (마크다운 금지)
- admin_verify_stats 가 있고 needs_nag 인 admin 이 있으면 "독촉" 섹션 추가 (이름 명시, 가볍게 — 죄책감 X, 동기부여 톤)
- 전체 500자 이내, plain text (마크다운 금지)
- 형식 (정확히 이 들여쓰기/기호 사용):
✨ 하이라이트

Expand All @@ -98,7 +174,10 @@ PROMPT_TEXT='당신은 decoded 모노레포의 일일 리포트를 한국어로
• 내용 요약 (#PR번호)

⚠️ 주의
• 이슈 설명 (#번호, →base) — (해당 없으면 이 섹션 전체 생략)'
• 이슈 설명 (#번호, →base) — (해당 없으면 이 섹션 전체 생략)

📣 독촉 — (needs_nag=true 인 admin 이 있을 때만 등장, 없으면 섹션 전체 생략)
• [이름]님 어제 검수 한 건도 없네요. 오늘은 한 건만이라도 부탁드려요!'

CLAUDE_REQ=$(jq -n \
--arg model "claude-haiku-4-5-20251001" \
Expand Down Expand Up @@ -176,6 +255,18 @@ if [ -n "$top_issues" ]; then
BODY+="📌 new issues (${ISSUES_COUNT})"$'\n'"$top_issues"$'\n\n'
fi

# admin verify table (deterministic — Claude 요약과 별개로 항상 노출)
verify_table=$(echo "$VERIFY_STATS_JSON" | jq -r '
if length == 0 then "" else
map("• \(.display_name // .username): 어제 \(.yesterday) / 7d \(.last_7d)\(if .needs_nag then " ⚠️" else "" end)")
| join("\n")
end')
if [ -n "$verify_table" ]; then
vy=$(echo "$VERIFY_TOTALS_JSON" | jq -r '.yesterday')
v7=$(echo "$VERIFY_TOTALS_JSON" | jq -r '.last_7d')
BODY+="🔍 admin verify (어제 ${vy} / 7d ${v7})"$'\n'"$verify_table"$'\n\n'
fi

MSG=$(cat <<EOF
🌅 decoded 일일 요약 — ${TODAY_KST}
━━━━━━━━━━━━━━━━
Expand Down
12 changes: 12 additions & 0 deletions .github/workflows/daily-digest.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# Daily digest of decoded monorepo activity → Telegram.
# Required repo secrets: TELEGRAM_BOT_TOKEN, TELEGRAM_CHAT_ID, ANTHROPIC_API_KEY
# Optional repo secrets (admin verify section): ASSETS_DATABASE_URL_RO, OPERATION_DATABASE_URL_RO
# 둘 다 없으면 verify 섹션 silent skip (회귀 안전).
name: Daily digest

on:
Expand Down Expand Up @@ -46,11 +48,21 @@ jobs:
exit 1
fi

- name: Install postgresql-client (admin verify section)
run: |
# ubuntu-latest 이미 postgresql-client 포함 — 누락 시 fallback.
if ! command -v psql >/dev/null 2>&1; then
sudo apt-get update -qq && sudo apt-get install -y -qq postgresql-client
fi
psql --version

- name: Run daily digest
env:
GH_TOKEN: ${{ github.token }}
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
TELEGRAM_BOT_TOKEN: ${{ secrets.TELEGRAM_BOT_TOKEN }}
TELEGRAM_CHAT_ID: ${{ secrets.TELEGRAM_CHAT_ID }}
WINDOW_HOURS: ${{ github.event.inputs.window_hours || '24' }}
ASSETS_DATABASE_URL_RO: ${{ secrets.ASSETS_DATABASE_URL_RO }}
OPERATION_DATABASE_URL_RO: ${{ secrets.OPERATION_DATABASE_URL_RO }}
run: bash .github/scripts/daily-digest.sh
6 changes: 5 additions & 1 deletion packages/api-server/src/domains/admin/handlers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use super::{
badges, categories, curations, dashboard, editorial_article_chat, editorial_articles,
editorial_candidates, editorial_discovery_settings, editorial_pipeline_settings,
editorial_recommendations, gemini_cost, magazine_sessions, monitoring, posts, solutions, spots,
synonyms,
synonyms, verify_stats,
};
use crate::domains::reports;

Expand Down Expand Up @@ -58,6 +58,10 @@ pub fn router(state: AppState, app_config: AppConfig) -> Router<AppState> {
"/gemini-cost",
gemini_cost::router(state.clone(), app_config.clone()),
)
.nest(
"/verify-stats",
verify_stats::router(state.clone(), app_config.clone()),
)
.nest("/badges", badges::router(app_config.clone()))
.nest("/reports", reports::admin_router(app_config.clone()))
.nest(
Expand Down
1 change: 1 addition & 0 deletions packages/api-server/src/domains/admin/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ pub mod posts;
pub mod solutions;
pub mod spots;
pub mod synonyms;
pub mod verify_stats;

pub use handlers::router;

Expand Down
Loading
Loading