Coverage for src/inheritance_calculator_core/models/person.py: 100%
91 statements
« prev ^ index » next coverage.py v7.11.0, created at 2025-10-17 05:31 +0900
« prev ^ index » next coverage.py v7.11.0, created at 2025-10-17 05:31 +0900
1"""人物モデル"""
2from datetime import date
3from typing import Optional, Any
4from enum import Enum
6from pydantic import Field, field_validator, EmailStr
8from .base import Neo4jNode
11class Gender(str, Enum):
12 """性別"""
13 MALE = "male"
14 FEMALE = "female"
15 OTHER = "other"
16 UNKNOWN = "unknown"
19class Person(Neo4jNode):
20 """
21 人物モデル
23 被相続人および相続人候補を表現するモデル。
24 Neo4jのPersonノードに対応。
25 """
27 name: str = Field(..., description="氏名", min_length=1)
28 is_alive: bool = Field(default=True, description="生存しているか")
29 death_date: Optional[date] = Field(default=None, description="死亡日")
30 is_decedent: bool = Field(default=False, description="被相続人フラグ")
31 birth_date: Optional[date] = Field(default=None, description="生年月日")
32 gender: Gender = Field(default=Gender.UNKNOWN, description="性別")
33 died_before_division: bool = Field(
34 default=False,
35 description="遺産分割前に死亡したか(再転相続の対象)"
36 )
38 # 連絡先情報(オプショナル)
39 address: Optional[str] = Field(default=None, description="住所")
40 phone: Optional[str] = Field(default=None, description="電話番号")
41 email: Optional[EmailStr] = Field(default=None, description="メールアドレス")
43 @field_validator('death_date')
44 @classmethod
45 def validate_death_date(cls, v: Optional[date], info: Any) -> Optional[date]:
46 """死亡日の検証"""
47 if v is not None:
48 # 死亡日がある場合、is_aliveはFalseであるべき
49 # ただし、バリデーション時点ではis_aliveがまだ設定されていない可能性がある
50 pass
51 return v
53 @field_validator('birth_date')
54 @classmethod
55 def validate_birth_date(cls, v: Optional[date], info: Any) -> Optional[date]:
56 """生年月日の検証"""
57 if v is not None:
58 # 未来の日付は不可
59 from datetime import date as date_class
60 if v > date_class.today():
61 raise ValueError("Birth date cannot be in the future")
62 return v
64 def mark_as_deceased(self, death_date: date) -> None:
65 """
66 死亡として記録
68 Args:
69 death_date: 死亡日
70 """
71 self.is_alive = False
72 self.death_date = death_date
73 self.mark_updated()
75 def mark_as_decedent(self) -> None:
76 """被相続人として記録"""
77 self.is_decedent = True
78 self.mark_updated()
80 def set_contact_info(
81 self,
82 address: Optional[str] = None,
83 phone: Optional[str] = None,
84 email: Optional[str] = None
85 ) -> None:
86 """
87 連絡先情報を設定
89 Args:
90 address: 住所
91 phone: 電話番号
92 email: メールアドレス
93 """
94 if address is not None:
95 self.address = address
96 if phone is not None:
97 self.phone = phone
98 if email is not None:
99 self.email = email
100 self.mark_updated()
102 @property
103 def age_at_death(self) -> Optional[int]:
104 """
105 死亡時の年齢を取得
107 Returns:
108 死亡時の年齢(生年月日または死亡日が不明な場合はNone)
109 """
110 if self.birth_date is None or self.death_date is None:
111 return None
113 age = self.death_date.year - self.birth_date.year
114 # 誕生日前なら1歳引く
115 if (self.death_date.month, self.death_date.day) < (self.birth_date.month, self.birth_date.day):
116 age -= 1
118 return age
120 @property
121 def current_age(self) -> Optional[int]:
122 """
123 現在の年齢を取得
125 Returns:
126 現在の年齢(生年月日が不明な場合はNone)
127 """
128 if self.birth_date is None:
129 return None
131 if not self.is_alive and self.death_date is not None:
132 return self.age_at_death
134 from datetime import date as date_class
135 today = date_class.today()
136 age = today.year - self.birth_date.year
137 # 誕生日前なら1歳引く
138 if (today.month, today.day) < (self.birth_date.month, self.birth_date.day):
139 age -= 1
141 return age
143 def is_older_than(self, other: 'Person') -> bool:
144 """
145 指定された人物より年上かどうかを判定
147 Args:
148 other: 比較対象の人物
150 Returns:
151 年上の場合True(生年月日が不明な場合はFalse)
152 """
153 if self.birth_date is None or other.birth_date is None:
154 return False
156 return self.birth_date < other.birth_date
158 def died_before(self, other: 'Person') -> bool:
159 """
160 指定された人物より先に死亡したかどうかを判定
162 Args:
163 other: 比較対象の人物
165 Returns:
166 先に死亡している場合True
167 """
168 if self.death_date is None:
169 return False
171 if other.death_date is None:
172 # 自分は死亡、相手は生存 → 自分が先
173 return True
175 return self.death_date < other.death_date
177 def __str__(self) -> str:
178 """文字列表現"""
179 status = "故人" if not self.is_alive else "存命"
180 decedent_mark = "(被相続人)" if self.is_decedent else ""
182 # 年齢情報の追加
183 age_info = ""
184 if self.current_age is not None:
185 if self.is_alive:
186 age_info = f", {self.current_age}歳"
187 else:
188 age_info = f", 享年{self.current_age}歳"
190 return f"{self.name} ({status}{age_info}){decedent_mark}"
192 def __repr__(self) -> str:
193 """デバッグ用文字列表現"""
194 return f"Person(name='{self.name}', is_alive={self.is_alive}, is_decedent={self.is_decedent})"