1"""Pydantic モデル定義。
2
3外部 API のレスポンスを統一形式へ正規化するためのスキーマを定義する。
4全てのフィールドに型を明示し、mypy strict 通過と JSON スキーマ自動生成 (FastAPI の OpenAPI) を担保する。
5"""
6from __future__ import annotations
7
8from datetime import datetime
9from typing import Literal
10
11from pydantic import BaseModel, ConfigDict, Field
12
13
14
15StrictModel = BaseModel
16StrictModel.model_config = ConfigDict(extra="forbid", frozen=False)
17
18
19
20
21class LawItem(BaseModel):
22 """e-Gov 法令 API の1件分の法令情報。"""
23
24 law_id: str = Field(..., description="法令ID 例: 405AC0000000049")
25 law_num: str = Field(..., description="法令番号 例: 平成15年法律第49号")
26 law_title: str = Field(..., description="法令名 例: 個人情報の保護に関する法律")
27 promulgation_date: str = Field("", description="公布日 (YYYY-MM-DD)")
28 source: Literal["egov"] = "egov"
29
30
31class LawSearchResult(BaseModel):
32 """法令検索結果のラッパー。"""
33
34 query: str = Field(..., description="検索クエリ")
35 count: int = Field(..., description="ヒット件数")
36 items: list[LawItem] = Field(default_factory=list, description="法令一覧")
37
38
39
40
41class WeatherEntry(BaseModel):
42 """JMA 1日分の天気予報エントリ。"""
43
44 date: str = Field(..., description="予報対象日 (YYYY-MM-DD)")
45 weather: str = Field(..., description="天気概況")
46 temp_min: str | None = Field(None, description="最低気温 (摂氏)")
47 temp_max: str | None = Field(None, description="最高気温 (摂氏)")
48 precipitation: str | None = Field(None, description="降水確率 (%)")
49
50
51class WeatherWarning(BaseModel):
52 """JMA 警報・注意報情報。"""
53
54 type: str = Field(..., description="警報種別 例: 警報 / 注意報")
55 areas: list[str] = Field(default_factory=list, description="対象地域")
56 headline: str = Field("", description="見出し")
57
58
59class WeatherForecast(BaseModel):
60 """気象予報の統合レスポンス。"""
61
62 area_code: str = Field(..., description="JMA AREACODE")
63 area_name: str = Field(..., description="地域名 例: 東京")
64 publishing_office: str = Field("", description="発表気象台")
65 report_datetime: str = Field("", description="発表日時 (ISO8601)")
66 forecasts: list[WeatherEntry] = Field(default_factory=list, description="予報リスト")
67 warnings: list[WeatherWarning] = Field(default_factory=list, description="警報リスト")
68 source: Literal["jma"] = "jma"
69
70
71
72
73class CorporateItem(BaseModel):
74 """国税庁 法人番号公表サイトの1件分の法人情報。"""
75
76 corporate_number: str = Field(..., description="法人番号 (13桁)")
77 name: str = Field(..., description="法人名 (商号)")
78 prefecture: str = Field("", description="所在地都道府県")
79 city: str = Field("", description="所在地市区町村")
80 address_remainder: str = Field("", description="所在地その他")
81 source: Literal["nta"] = "nta"
82
83
84class CorporateSearchResult(BaseModel):
85 """法人番号検索結果のラッパー。"""
86
87 query: str = Field(..., description="検索クエリ")
88 count: int = Field(..., description="ヒット件数")
89 items: list[CorporateItem] = Field(default_factory=list, description="法人一覧")
90
91
92
93
94class MedicalFacility(BaseModel):
95 """厚生労働省公開CSVの1件分の医療施設情報。"""
96
97 facility_id: str = Field(..., description="施設ID")
98 name: str = Field(..., description="施設名称")
99 prefecture: str = Field(..., description="都道府県名")
100 city: str = Field("", description="市区町村名")
101 address: str = Field("", description="詳細住所")
102 phone: str = Field("", description="電話番号")
103 bed_count: int = Field(0, description="病床数")
104 source: Literal["mhlw"] = "mhlw"
105
106
107class MedicalSearchResult(BaseModel):
108 """医療施設検索結果のラッパー。"""
109
110 prefecture: str = Field(..., description="検索対象都道府県")
111 count: int = Field(..., description="ヒット件数")
112 items: list[MedicalFacility] = Field(default_factory=list, description="施設一覧")
113
114
115
116
117class HealthStatus(BaseModel):
118 """サービスヘルスチェック用レスポンス。"""
119
120 status: Literal["ok"] = "ok"
121 service: str = Field(..., description="サービス名")
122 version: str = Field(..., description="バージョン")
123 timestamp: str = Field(..., description="応答生成時刻 (ISO8601)")
124 sources: list[str] = Field(
125 default_factory=lambda: ["egov", "jma", "nta", "mhlw"],
126 description="統合済みソース一覧",
127 )
128
129
130
131
132class ApifyInput(BaseModel):
133 """Apify Actor の入力スキーマ。"""
134
135 endpoint: Literal[
136 "laws_search",
137 "law_detail",
138 "weather",
139 "corporate",
140 "medical",
141 ] = "laws_search"
142 query: str = ""
143 law_id: str = ""
144 area_code: str = "130000"
145 prefecture: str = "東京都"
146
147
148class ApifyOutput(BaseModel):
149 """Apify Actor の出力ラッパー。"""
150
151 endpoint: str = Field(..., description="呼び出されたエンドポイント")
152 timestamp: str = Field(
153 default_factory=lambda: datetime.utcnow().isoformat(),
154 description="実行時刻 (UTC ISO8601)",
155 )
156 payload: dict[str, object] = Field(
157 default_factory=dict,
158 description="エンドポイントから返された正規化済み JSON",
159 )