Skip to content

feat: 정책 핀 sync·배치 가공·DB 적재 파이프라인 및 관리자 API 추가#118

Merged
selnem merged 8 commits into
feat/72-policy-apifrom
feat/policy-api-db-insert
Jun 12, 2026
Merged

feat: 정책 핀 sync·배치 가공·DB 적재 파이프라인 및 관리자 API 추가#118
selnem merged 8 commits into
feat/72-policy-apifrom
feat/policy-api-db-insert

Conversation

@selnem

@selnem selnem commented Jun 12, 2026

Copy link
Copy Markdown
Collaborator

✨ 작업 개요

정책뉴스 API 수집 → LLM 가공(pin_content·카드뉴스) → DB INSERT까지 이어지는 파이프라인과 /policy-admin 관리자 API를 추가했습니다.

  • sync_pipeline: 수집·배치 가공·배치 DB 적재를 한 번에 실행
  • /policy-admin 엔드포인트: sync, transform-batch, import-batch, status, scheduler/run-once
  • KST 기준 자동 sync 스케줄러(PolicyPinSchedulerService) 앱 lifespan 등록
  • 카드뉴스 S3 업로드 및 CardnewsImageS3 적재
  • EventPin.policy_api_id, Community 확장으로 중복 적재 방지
  • JSON 기반 카드뉴스 템플릿·Pretendard 폰트·마스코트 에셋 포함

체크리스트

  • Reviewers, Assignees, Labels를 모두 등록했나요?
  • .gitignore 설정을 하였나요?
  • PR 머지 전 반드시 CI가 정상적으로 작동하는지 확인해주세요!

📷 이미지 첨부 (선택)

  • POST /policy-admin/sync 응답 샘플
  • GET /policy-admin/status 파이프라인 상태 응답
  • 카드뉴스 S3 업로드 결과

🧐 집중 리뷰 요청

리뷰어가 특별히 봐주었으면 하는 부분이 있다면 작성해주세요.

  • PolicyEventIngestService.import_handoff_batch — Pin / EventPin / Community / CardnewsImageS3 트랜잭션·중복 스킵 로직
  • PolicyPinService.sync_pipeline — 수집→가공→적재 배치 루프 및 policy_api_id 기준 중복 처리
  • PolicyPinSchedulerService — KST 스케줄 간격(POLICY_SYNC_INTERVAL_DAYS) 및 lifespan 등록/종료
  • 카드뉴스 S3 업로드 후 로컬 캐시 정리(policy_pipeline_cleanup, POLICY_PRUNE_PIPELINE_AFTER_IMPORT)
  • Community·EventPin 모델 변경(policy_api_id, community_type 등) — 기존 데이터·마이그레이션 영향

정책뉴스 수집→LLM 가공→DB INSERT 일괄 파이프라인(sync_pipeline)과
/policy-admin 엔드포인트(sync, transform-batch, import-batch, pipeline-status)를 추가한다.
KST 기준 자동 sync 스케줄러, 카드뉴스 S3 업로드·CardnewsImageS3 적재,
중복 건 스킵·파이프라인 정리, Community/EventPin(policy_api_id) 모델 확장을 포함한다.
@selnem selnem requested a review from qkwltkwkd1 June 12, 2026 11:33
@selnem selnem self-assigned this Jun 12, 2026
@selnem selnem added the ⭐ feat 새로운 기능 label Jun 12, 2026
gemini-code-assist[bot]

This comment was marked as resolved.

@qkwltkwkd1 qkwltkwkd1 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

고생하셨습니다!

selnem added 2 commits June 12, 2026 23:16
sync_pipeline 다배치 실행 시 import errors/items/pin_ids가 마지막 배치만 반영되던 문제를 누적하도록 수정하고, 스케줄러 간격 비교에 1시간 버퍼를 적용해 실행 시각 오차로 sync가 하루 밀리는 현상을 방지한다. 동작하지 않던 cardnews_image_urls fallback을 제거하고, 카드뉴스 handoff_path 절대 경로 처리를 보완한다.
@selnem

selnem commented Jun 12, 2026

Copy link
Copy Markdown
Collaborator Author

/gemini review

gemini-code-assist[bot]

This comment was marked as resolved.

@selnem

selnem commented Jun 12, 2026

Copy link
Copy Markdown
Collaborator Author

/gemini review

gemini-code-assist[bot]

This comment was marked as resolved.

/policy-admin 접근 제어와 정책 파이프라인 캡슐화를 보강하고, 카드뉴스 정리 및 nested transaction 실패 처리의 데이터 유실 위험을 줄인다.

Co-authored-by: Cursor <cursoragent@cursor.com>
@selnem

selnem commented Jun 12, 2026

Copy link
Copy Markdown
Collaborator Author

/gemini review

gemini-code-assist[bot]

This comment was marked as resolved.

/policy-admin 날짜 검증 예외가 400으로 처리되도록 보완하고, Community.pin_id nullable 변경으로 기존 데이터 마이그레이션 리스크를 줄인다.
@selnem

selnem commented Jun 12, 2026

Copy link
Copy Markdown
Collaborator Author

/gemini review

gemini-code-assist[bot]

This comment was marked as resolved.

handoff 적재와 sync_pipeline의 중복 DB 조회를 줄이고, 카드뉴스 파일 읽기·파이프라인 정리 작업을 스레드 풀에서 실행해 이벤트 루프 블로킹을 방지한다.
@selnem

selnem commented Jun 12, 2026

Copy link
Copy Markdown
Collaborator Author

/gemini review

@selnem selnem requested a review from qkwltkwkd1 June 12, 2026 15:06

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces an automated background scheduler and admin API endpoints for syncing, transforming, and importing policy pins and their associated cardnews images to S3 and the database. Key additions include the PolicyPinSchedulerService, PolicyEventIngestService, and the CardnewsImageS3 model, along with comprehensive unit tests. Feedback on these changes highlights a potential runtime lookup error due to a mismatch between the configuration alias POLICY_ADMIN_NICKNAME and the database query field, a blocking synchronous file I/O operation in sync_pipeline that should be offloaded to a thread pool, and redundant slicing logic in import_handoff_batch that can be simplified.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread app/core/config.py
)
policy_admin_user_name: str = Field(
default="admin",
validation_alias=AliasChoices("POLICY_ADMIN_USER_NAME", "POLICY_ADMIN_NICKNAME"),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

설정 필드 policy_admin_user_name에서 POLICY_ADMIN_NICKNAME을 별칭(alias)으로 허용하고 있지만, PolicyEventIngestService.resolve_admin_uid에서는 이 값을 항상 user_name 컬럼 기준으로만 조회(get_by_user_name)하고 있습니다. 만약 사용자가 POLICY_ADMIN_NICKNAME 환경 변수를 설정할 경우, 실제 DB의 nickname 컬럼이 아닌 user_name 컬럼에서 조회되어 사용자를 찾지 못하는 런타임 오류가 발생할 수 있습니다. 별칭에서 POLICY_ADMIN_NICKNAME을 제거하거나, 조회 시 닉네임과 유저명을 모두 고려하도록 로직을 수정하는 것이 안전합니다.

Suggested change
validation_alias=AliasChoices("POLICY_ADMIN_USER_NAME", "POLICY_ADMIN_NICKNAME"),
validation_alias=AliasChoices("POLICY_ADMIN_USER_NAME"),


if settings.policy_prune_pipeline_after_import:
db_ids_before = await ingest_service.get_imported_policy_api_ids()
prune_pipeline_imported(db_ids_before)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

동기식 파일 I/O를 수행하는 prune_pipeline_imported 함수를 비동기 이벤트 루프 내에서 직접 호출하고 있어, 파일 크기가 커질 경우 이벤트 루프가 블로킹될 위험이 있습니다. PolicyEventIngestService에서와 마찬가지로 anyio.to_thread.run_sync를 사용하여 별도 스레드 풀에서 실행하도록 개선하는 것을 권장합니다.

Suggested change
prune_pipeline_imported(db_ids_before)
from anyio import to_thread
await to_thread.run_sync(prune_pipeline_imported, db_ids_before)

Comment on lines +152 to +154
target_rows = pending_rows if import_all else pending_rows[: (limit or len(pending_rows))]
if limit is not None and import_all:
target_rows = pending_rows[:limit]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

라인 152~154의 슬라이싱 로직은 다소 복잡하고 중복되어 있습니다. 특히 import_allFalse이고 limitNone인 경우, pending_rows[:len(pending_rows)]가 되어 결국 전체 행을 가져오게 되므로 import_all=True일 때와 동일하게 동작합니다. 이는 import_all 매개변수의 의도와 다르게 동작할 수 있으므로, 로직을 더 명확하고 단순하게 개선하는 것이 좋습니다.

Suggested change
target_rows = pending_rows if import_all else pending_rows[: (limit or len(pending_rows))]
if limit is not None and import_all:
target_rows = pending_rows[:limit]
if limit is not None:
target_rows = pending_rows[:limit]
elif not import_all:
target_rows = []
else:
target_rows = pending_rows

sync가 remaining_pending 기준으로 배치를 끝까지 돌고, import 후 캐시 정리로 handoff가 비어도 DB 적재 완료로 처리하도록 status·hint를 보강한다.
@selnem selnem merged commit bdf3ab6 into feat/72-policy-api Jun 12, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

⭐ feat 새로운 기능

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants