Loading...
Loading...
02-reusable-code-python/news/models.py
"""
뉴스 수집·분석 데이터 모델.
Pydantic v2 기반 불변(frozen) 데이터 모델 6개를 정의한다.
모든 모델은 ConfigDict(frozen=True)로 불변 객체를 보장.
@source: 00-general-pro
@extracted: 2026-03-08
@version: 1.0.0
의존성:
- pydantic >= 2.0
사용법:
```python
from news.models import (
NewsItem,
TrendSignal,
AppOpportunity,
MarketingStrategy,
CollectionResult,
AnalysisReport,
)
item = NewsItem(
source="hackernews",
title="New AI Framework",
url="https://example.com",
score=150,
)
```
"""
from datetime import datetime
from typing import Any
from pydantic import BaseModel, ConfigDict, Field, model_validator
# ============================================
# 뉴스 항목
# ============================================
class NewsItem(BaseModel):
"""수집된 뉴스 단일 항목.
Attributes:
source: 소스 식별자
(hackernews, reddit, github, google_news, producthunt)
title: 뉴스 제목
url: 원본 URL
score: 업보트/스타 수
comment_count: 댓글 수
tags: 태그 목록
summary: 요약 또는 description
published_at: 게시 일시
collected_at: 수집 일시
metadata: 소스별 추가 데이터
"""
model_config = ConfigDict(frozen=True)
source: str
title: str
url: str
score: int = 0
comment_count: int = 0
tags: list[str] = []
summary: str = ""
published_at: datetime | None = None
collected_at: datetime = Field(default_factory=datetime.now)
metadata: dict[str, Any] = {}
# ============================================
# 트렌드 시그널
# ============================================
class TrendSignal(BaseModel):
"""트렌드 시그널.
키워드 빈도와 성장률을 기반으로 트렌드 단계를 판별한다.
Attributes:
keyword: 트렌드 키워드
frequency: 등장 횟수
sources: 발견된 소스 목록
growth_rate: 성장률 (-1 ~ 1 범위)
category: 카테고리 (AI, SaaS, Mobile 등)
related_items: 관련 NewsItem URL 목록
stage: 트렌드 단계
(emerging, growing, mainstream, declining)
"""
model_config = ConfigDict(frozen=True)
keyword: str
frequency: int
sources: list[str]
growth_rate: float = 0.0
category: str = ""
related_items: list[str] = []
stage: str = "unknown"
# ============================================
# 앱 기회
# ============================================
class AppOpportunity(BaseModel):
"""앱 개발 기회.
5차원 평가 매트릭스(kdyidea 기반)로 기회를 정량 평가한다.
frozen=True이므로 model_validator로 total_score를 자동 계산.
Attributes:
title: 기회 제목
description: 기회 설명
platform: 플랫폼 (web, mobile, both)
demand_signals: 수요 근거 목록
tech_feasibility: 기술 실현 가능성 (0~10)
market_demand: 시장 수요 (0~10)
differentiation: 차별화 (0~10)
timing: 타이밍 (0~10)
scalability: 확장성 (0~10)
total_score: 5차원 가중 합산 점수
competing_products: 경쟁 제품 목록
suggested_stack: 추천 기술 스택
related_trends: 관련 트렌드 키워드
"""
model_config = ConfigDict(frozen=True)
title: str
description: str
platform: str = "web"
demand_signals: list[str]
tech_feasibility: float = 0.0
market_demand: float = 0.0
differentiation: float = 0.0
timing: float = 0.0
scalability: float = 0.0
total_score: float = 0.0
competing_products: list[str] = []
suggested_stack: list[str] = []
related_trends: list[str] = []
@model_validator(mode="after")
def _auto_calculate_score(self) -> "AppOpportunity":
"""total_score를 5차원 가중 점수로 자동 계산."""
calculated = self.calculate_score()
if self.total_score != calculated:
object.__setattr__(self, "total_score", calculated)
return self
def calculate_score(self) -> float:
"""5차원 가중 점수 계산 (kdyidea 매트릭스 기반).
Returns:
가중 합산 점수.
tech_feasibility * 3.0
+ market_demand * 2.5
+ differentiation * 1.5
+ timing * 2.0
+ scalability * 1.0
"""
return (
self.tech_feasibility * 3.0
+ self.market_demand * 2.5
+ self.differentiation * 1.5
+ self.timing * 2.0
+ self.scalability * 1.0
)
# ============================================
# 마케팅 전략
# ============================================
class MarketingStrategy(BaseModel):
"""마케팅 전략.
앱 기회에 대한 마케팅 채널, 런칭 전략, 성장 전술을 정의한다.
Attributes:
app_title: 앱 제목
target_audience: 타겟 사용자
value_proposition: 가치 제안
channels: 마케팅 채널
(Reddit, ProductHunt, HN, Twitter 등)
launch_strategy: 런칭 전략
growth_tactics: 성장 전술 목록
estimated_budget: 예상 예산
timeline: 타임라인
"""
model_config = ConfigDict(frozen=True)
app_title: str
target_audience: str
value_proposition: str
channels: list[str]
launch_strategy: str
growth_tactics: list[str]
estimated_budget: str = "무료/저예산"
timeline: str = ""
# ============================================
# 수집 결과
# ============================================
class CollectionResult(BaseModel):
"""뉴스 수집 결과.
다중 소스 수집의 통합 결과를 담는다.
Attributes:
items: 수집된 뉴스 항목 목록
errors: 소스별 에러 메시지 목록
duration_seconds: 수집 소요 시간 (초)
items_by_source: 소스별 수집 건수
collected_at: 수집 완료 일시
"""
model_config = ConfigDict(frozen=True)
items: list[NewsItem]
errors: list[str] = []
duration_seconds: float = 0.0
items_by_source: dict[str, int] = {}
collected_at: datetime = Field(default_factory=datetime.now)
# ============================================
# 종합 분석 보고서
# ============================================
class AnalysisReport(BaseModel):
"""종합 분석 보고서.
트렌드 시그널, 앱 기회, 마케팅 전략을 종합한 최종 보고서.
Attributes:
trends: 트렌드 시그널 목록
opportunities: 앱 기회 목록
strategies: 마케팅 전략 목록
raw_item_count: 원본 수집 항목 수
analysis_duration_seconds: 분석 소요 시간 (초)
domains: 분석 도메인 목록
period_days: 분석 기간 (일)
generated_at: 보고서 생성 일시
"""
model_config = ConfigDict(frozen=True)
trends: list[TrendSignal]
opportunities: list[AppOpportunity]
strategies: list[MarketingStrategy] = []
raw_item_count: int = 0
analysis_duration_seconds: float = 0.0
domains: list[str] = []
period_days: int = 7
generated_at: datetime = Field(default_factory=datetime.now)