-
Notifications
You must be signed in to change notification settings - Fork 0
[FEAT] policy api 구현 #86
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
37 commits
Select commit
Hold shift + click to select a range
27c4926
feat: policy api 구현
qkwltkwkd1 9b64403
feat: policy 카드뉴스 템플릿 json으로
qkwltkwkd1 6585cea
feat: policy api 구현
qkwltkwkd1 e3e1170
fix: 충돌 해결
qkwltkwkd1 f549c4b
refactor: 카드뉴스 템프릿 수정
qkwltkwkd1 f8129d8
refactor: 병합 대상 슬라이드 수정
qkwltkwkd1 e08c97b
refactor: 단순 슬라이싱 수정
qkwltkwkd1 71002cc
fix: 중복 코드 제거
qkwltkwkd1 635ec77
fix: 서비스별 독립 키로, config 설명 수정
qkwltkwkd1 07b8a38
fix: 불필요한 매개변수를 제거
qkwltkwkd1 ce3a0c0
fix: UTF-8 한글 문자열로 수정
qkwltkwkd1 0dcc5fe
fix: BOM 없이 UTF-8로
qkwltkwkd1 c752af9
fix: 충돌 해결
qkwltkwkd1 2699982
refactor: 데코레이터를 추가
qkwltkwkd1 eda612f
refactor: 중복 코드를 제거
qkwltkwkd1 bc9c55f
refactor: 비동기적으로 병렬 처리
qkwltkwkd1 7fbb8e7
refactor: AsyncClient 수정
qkwltkwkd1 6f7d7ec
refactor: 유효한 날짜인지 검증
qkwltkwkd1 077d082
refactor: 처리 속도 향상
qkwltkwkd1 4dc0f06
refactor: 중복 제거
qkwltkwkd1 2a226e7
fix: 충돌 해결
qkwltkwkd1 5032b91
fix: ci 해결
qkwltkwkd1 73d99ed
fix: 충돌 해결
qkwltkwkd1 1a82afe
refactor: 중복 블록 제거
qkwltkwkd1 3aed1aa
refactor: 폰트 하드코딩 수정
qkwltkwkd1 b4d0e68
refactor: 임포트 정리
qkwltkwkd1 345b892
refactor: 임포트 정리
qkwltkwkd1 daab579
refactor: fallback wrapping 되도록 보완
qkwltkwkd1 52142b7
feat: 정책 핀 sync·배치 가공·DB 적재 파이프라인 및 관리자 API 추가
selnem 1a29a06
fix: 정책 핀 파이프라인 코드리뷰 지적 4건 반영
selnem 6eec2df
fix: import_handoff_batch 메타데이터 오타 수정 및 정책 파이프라인 테스트 추가
selnem dc70187
fix: 정책 핀 sync 날짜 검증·handoff 적재 drain·모델 unique 동기화
selnem 89e4707
fix: 정책 관리자 API 리뷰 지적 보완
selnem 1b97e63
fix: 정책 관리자 리뷰 후속 지적 반영
selnem f1fc0a3
fix: 정책 파이프라인 N+1 쿼리·동기 I/O 리뷰 반영
selnem 5f4bade
fix: 정책 sync 루프·handoff 비어 있을 때 DB 완료 상태 인식
selnem bdf3ab6
Merge pull request #118 from IssueIssyu/feat/policy-api-db-insert
selnem File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
Binary file not shown.
Binary file not shown.
Binary file not shown.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| { | ||
| "files": [ | ||
| "기본핀.png", | ||
| "행복함핀.png", | ||
| "뿌듯핀.png", | ||
| "캐릭터_음흉씨핀.png" | ||
| ] | ||
| } |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,51 @@ | ||
| { | ||
| "palettes": { | ||
| "issueissyu_blue": { | ||
| "outer": [29, 135, 255], | ||
| "offset": [255, 230, 90], | ||
| "accent": [29, 135, 255] | ||
| }, | ||
|
|
||
| "fresh_mint": { | ||
| "outer": [52, 199, 153], | ||
| "offset": [214, 255, 236], | ||
| "accent": [36, 148, 108] | ||
| }, | ||
|
|
||
| "soft_purple": { | ||
| "outer": [132, 94, 247], | ||
| "offset": [229, 219, 255], | ||
| "accent": [96, 64, 201] | ||
| }, | ||
|
|
||
| "sunset_orange": { | ||
| "outer": [255, 126, 95], | ||
| "offset": [255, 225, 180], | ||
| "accent": [220, 88, 52] | ||
| }, | ||
|
|
||
| "modern_slate": { | ||
| "outer": [58, 72, 95], | ||
| "offset": [210, 220, 235], | ||
| "accent": [35, 45, 65] | ||
| }, | ||
|
|
||
| "rose_pink": { | ||
| "outer": [238, 108, 133], | ||
| "offset": [255, 220, 228], | ||
| "accent": [198, 72, 98] | ||
| }, | ||
|
|
||
| "deep_ocean": { | ||
| "outer": [0, 119, 182], | ||
| "offset": [202, 240, 248], | ||
| "accent": [2, 62, 138] | ||
| }, | ||
|
|
||
| "lime_energy": { | ||
| "outer": [140, 198, 62], | ||
| "offset": [240, 255, 204], | ||
| "accent": [92, 148, 32] | ||
| } | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,35 @@ | ||
| { | ||
| "name": "template_cover", | ||
| "canvas": { "width": 1080, "height": 1350 }, | ||
| "frame": { "card_inset": 36, "content_pad": 36, "offset_px": 12 }, | ||
| "layout": { | ||
| "text_bottom_ratio_with_hero": 0.44, | ||
| "mascot_zone_ratio": 0.52, | ||
| "mascot_min_h": 420 | ||
| }, | ||
| "fonts": { | ||
| "eyebrow_start": 34, | ||
| "eyebrow_min": 26, | ||
| "eyebrow_max_lines": 2, | ||
| "headline_start": 72, | ||
| "headline_min": 48, | ||
| "headline_max_lines": 3, | ||
| "highlight_start": 68, | ||
| "highlight_min": 44, | ||
| "highlight_max_lines": 2 | ||
| }, | ||
| "highlight": { | ||
| "type": "accent_badge", | ||
| "radius": 8, | ||
| "pad_x": 18, | ||
| "pad_y": 5, | ||
| "tape_extra_h": 14, | ||
| "text_fill": [20, 24, 32] | ||
| }, | ||
| "hero": { | ||
| "enabled": true, | ||
| "box_inset": 22, | ||
| "corner_radius": 28, | ||
| "fill_mode": "contain" | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,53 @@ | ||
| { | ||
| "name": "template_cta", | ||
| "canvas": { "width": 1080, "height": 1350 }, | ||
| "frame": { "card_inset": 36, "content_pad": 36, "offset_px": 12 }, | ||
| "defaults": { | ||
| "eyebrow": "마무리", | ||
| "headline": "자세한 내용은 원문에서", | ||
| "cta": "원문 뉴스 보기", | ||
| "speech": "궁금하면 원문 봐!", | ||
| "speech_max_len": 18 | ||
| }, | ||
| "layout": { | ||
| "gap_above_mascot": 36, | ||
| "panel_inset": 22, | ||
| "content_scale_base": 180, | ||
| "content_scale_min": 1.08, | ||
| "content_scale_max": 1.48, | ||
| "separator_width": 2 | ||
| }, | ||
| "fonts": { | ||
| "url_start": 20, | ||
| "url_min": 18, | ||
| "body_start": 24, | ||
| "body_min": 20, | ||
| "term_title": 22, | ||
| "term_body": 20 | ||
| }, | ||
| "cta_panel": { | ||
| "height_start": 96, | ||
| "height_min": 76, | ||
| "height_max": 108, | ||
| "radius": 14, | ||
| "label_start": 40, | ||
| "label_min": 28, | ||
| "inset_panel": 22 | ||
| }, | ||
| "url": { | ||
| "display_max": 34, | ||
| "prefix": "앱에서 링크 · " | ||
| }, | ||
| "term_guide": { | ||
| "title": "쉬운 말로", | ||
| "panel_radius": 12, | ||
| "max_guides": 2, | ||
| "max_body_lines": 3 | ||
| }, | ||
| "header": { | ||
| "eyebrow_start": 32, | ||
| "eyebrow_min": 24, | ||
| "headline_start": 52, | ||
| "headline_min": 36 | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,24 @@ | ||
| { | ||
| "name": "template_grid", | ||
| "canvas": { "width": 1080, "height": 1350 }, | ||
| "frame": { "card_inset": 36, "content_pad": 36, "offset_px": 12 }, | ||
| "defaults": { | ||
| "headline": "이렇게 확인하세요", | ||
| "label_prefix": "포인트" | ||
| }, | ||
| "layout": { | ||
| "max_items": 4, | ||
| "gap_tile": 18, | ||
| "tile_min_h": 120, | ||
| "tile_pad": 24, | ||
| "panel_radius": 14, | ||
| "ribbon_ratio": 0.28, | ||
| "ribbon_min_h": 52 | ||
| }, | ||
| "fonts": { | ||
| "label_start": 42, | ||
| "label_min": 30, | ||
| "body_start": 44, | ||
| "body_min": 30 | ||
| } | ||
| } |
30 changes: 30 additions & 0 deletions
30
app/assets/policy_cardnews_templates/template_numbered.json
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,30 @@ | ||
| { | ||
| "name": "template_numbered", | ||
| "canvas": { "width": 1080, "height": 1350 }, | ||
| "frame": { "card_inset": 36, "content_pad": 36, "offset_px": 12 }, | ||
| "defaults": { | ||
| "eyebrow": "한 장 요약", | ||
| "speech": "핵심만!", | ||
| "speech_max_len": 18 | ||
| }, | ||
| "layout": { | ||
| "max_items": 4, | ||
| "mascot_max_items": 3, | ||
| "panel_inset": 22, | ||
| "gap_row": 14, | ||
| "row_min_h": 48, | ||
| "scale_base_per_row": 64, | ||
| "scale_max": 1.65, | ||
| "separator_width": 2, | ||
| "separator_inset_left": 56, | ||
| "max_wrap_lines": 3 | ||
| }, | ||
| "fonts": { | ||
| "row_title_start": 36, | ||
| "row_title_min": 28, | ||
| "row_title_max": 48, | ||
| "num_start": 30, | ||
| "num_min": 24, | ||
| "num_max": 38 | ||
| } | ||
| } |
29 changes: 29 additions & 0 deletions
29
app/assets/policy_cardnews_templates/template_three_col.json
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,29 @@ | ||
| { | ||
| "name": "template_three_col", | ||
| "canvas": { "width": 1080, "height": 1350 }, | ||
| "frame": { "card_inset": 36, "content_pad": 36, "offset_px": 12 }, | ||
| "defaults": { | ||
| "headline": "핵심만 정리", | ||
| "speech": "핵심포인트!", | ||
| "speech_max_len": 18, | ||
| "label_prefix": "항목" | ||
| }, | ||
| "layout": { | ||
| "max_items": 3, | ||
| "col_gap": 20, | ||
| "panel_radius": 14, | ||
| "panel_inset": 22, | ||
| "badge_min": 44, | ||
| "badge_max": 56, | ||
| "badge_ratio": 0.13, | ||
| "label_h_ratio": 0.18, | ||
| "label_h_min": 36 | ||
| }, | ||
| "fonts": { | ||
| "badge_num_start": 22, | ||
| "label_start": 32, | ||
| "label_min": 24, | ||
| "body_start": 30, | ||
| "body_min": 22 | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,92 @@ | ||
| from __future__ import annotations | ||
|
|
||
| import asyncio | ||
| import logging | ||
| from typing import Any | ||
| from urllib.parse import urlencode | ||
|
|
||
| import httpx | ||
|
|
||
| from app.core.config import Settings, settings | ||
| from app.utils.policy_news_parse import parse_policy_news_xml, policy_result_ok | ||
|
|
||
| logger = logging.getLogger(__name__) | ||
|
|
||
| ENDPOINT_POLICY_NEWS_LIST = "policyNewsList" | ||
|
|
||
| class PolicyNewsClient: | ||
| # 문화체육관광부 정책브리핑 정책뉴스 OpenAPI (공공데이터포털) | ||
|
|
||
| def __init__( | ||
| self, | ||
| *, | ||
| service_key: str, | ||
| base_url: str = settings.policy_news_api_base_url, | ||
| timeout_seconds: float = settings.policy_news_request_timeout_seconds, | ||
| request_interval_seconds: float = settings.policy_news_request_interval_seconds, | ||
| http_client: httpx.AsyncClient | None = None, | ||
| ) -> None: | ||
| self._service_key = service_key | ||
| self._base_url = base_url.rstrip("/") | ||
| self._timeout = timeout_seconds | ||
| self._interval = max(0.0, request_interval_seconds) | ||
| self._http = http_client | ||
| self._owns_client = http_client is None | ||
|
|
||
| @classmethod | ||
| def from_settings(cls, config: Settings | None = None) -> PolicyNewsClient: | ||
| s = config or settings | ||
| secret = s.policy_news_service_key | ||
| if secret is None: | ||
| raise RuntimeError( | ||
| "POLICY_NEWS_SERVICE_KEY가 설정되어 있지 않습니다.", | ||
| ) | ||
| return cls( | ||
| service_key=secret.get_secret_value(), | ||
| base_url=s.policy_news_api_base_url, | ||
| timeout_seconds=s.policy_news_request_timeout_seconds, | ||
| request_interval_seconds=s.policy_news_request_interval_seconds, | ||
| ) | ||
|
|
||
| async def __aenter__(self) -> PolicyNewsClient: | ||
| if self._http is None: | ||
| self._http = httpx.AsyncClient(timeout=self._timeout) | ||
| return self | ||
|
|
||
| async def __aexit__(self, *args: object) -> None: | ||
| if self._owns_client and self._http is not None: | ||
| await self._http.aclose() | ||
|
|
||
| async def _get_xml(self, endpoint: str, **params: Any) -> str: | ||
| if self._http is None: | ||
| raise RuntimeError( | ||
| "PolicyNewsClient is not started; use async with PolicyNewsClient.from_settings()", | ||
| ) | ||
| query_params = { | ||
| "serviceKey": self._service_key, | ||
| **{k: str(v) for k, v in params.items() if v is not None}, | ||
| } | ||
| query = urlencode(query_params) | ||
| url = f"{self._base_url}/{endpoint}?{query}" | ||
| response = await self._http.get(url) | ||
| response.raise_for_status() | ||
| if self._interval > 0: | ||
| await asyncio.sleep(self._interval) | ||
| return response.text | ||
|
|
||
| async def policy_news_list( | ||
| self, | ||
| *, | ||
| start_date: str, | ||
| end_date: str, | ||
| ) -> tuple[dict[str, str], list[dict[str, str]]]: | ||
| xml_text = await self._get_xml( | ||
| ENDPOINT_POLICY_NEWS_LIST, | ||
| startDate=start_date, | ||
| endDate=end_date, | ||
| ) | ||
| header, items = parse_policy_news_xml(xml_text) | ||
| ok, msg = policy_result_ok(header) | ||
| if not ok: | ||
| raise RuntimeError(f"policyNewsList 실패 ({start_date}~{end_date}): {msg}") | ||
| return header, items | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.