Coverage for src/inheritance_calculator_core/models/person.py: 100%

1"""人物モデル"""

2 from datetime import date

3 from typing import Optional, Any

4 from enum import Enum

5

6 from pydantic import Field, field_validator, EmailStr

7

8 from .base import Neo4jNode

9

10

11 class Gender(str, Enum):

12 """性別"""

13 MALE = "male"

14 FEMALE = "female"

15 OTHER = "other"

16 UNKNOWN = "unknown"

17

18

19 class Person(Neo4jNode):

20 """

21 人物モデル

22

23 被相続人および相続人候補を表現するモデル。

24 Neo4jのPersonノードに対応。

25 """

26

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 )

37

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="メールアドレス")

42

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

52

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

63

64 def mark_as_deceased(self, death_date: date) -> None:

65 """

66 死亡として記録

67

68 Args:

69 death_date: 死亡日

70 """

71 self.is_alive = False

72 self.death_date = death_date

73 self.mark_updated()

74

75 def mark_as_decedent(self) -> None:

76 """被相続人として記録"""

77 self.is_decedent = True

78 self.mark_updated()

79

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 連絡先情報を設定

88

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()

101

102 @property

103 def age_at_death(self) -> Optional[int]:

104 """

105 死亡時の年齢を取得

106

107 Returns:

108 死亡時の年齢（生年月日または死亡日が不明な場合はNone）

109 """

110 if self.birth_date is None or self.death_date is None:

111 return None

112

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

117

118 return age

119

120 @property

121 def current_age(self) -> Optional[int]:

122 """

123 現在の年齢を取得

124

125 Returns:

126 現在の年齢（生年月日が不明な場合はNone）

127 """

128 if self.birth_date is None:

129 return None

130

131 if not self.is_alive and self.death_date is not None:

132 return self.age_at_death

133

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

140

141 return age

142

143 def is_older_than(self, other: 'Person') -> bool:

144 """

145 指定された人物より年上かどうかを判定

146

147 Args:

148 other: 比較対象の人物

149

150 Returns:

151 年上の場合True（生年月日が不明な場合はFalse）

152 """

153 if self.birth_date is None or other.birth_date is None:

154 return False

155

156 return self.birth_date < other.birth_date

157

158 def died_before(self, other: 'Person') -> bool:

159 """

160 指定された人物より先に死亡したかどうかを判定

161

162 Args:

163 other: 比較対象の人物

164

165 Returns:

166 先に死亡している場合True

167 """

168 if self.death_date is None:

169 return False

170

171 if other.death_date is None:

172 # 自分は死亡、相手は生存 → 自分が先

173 return True

174

175 return self.death_date < other.death_date

176

177 def __str__(self) -> str:

178 """文字列表現"""

179 status = "故人" if not self.is_alive else "存命"

180 decedent_mark = "（被相続人）" if self.is_decedent else ""

181

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}歳"

189

190 return f"{self.name} ({status}{age_info}){decedent_mark}"

191

192 def __repr__(self) -> str:

193 """デバッグ用文字列表現"""

194 return f"Person(name='{self.name}', is_alive={self.is_alive}, is_decedent={self.is_decedent})"