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

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

2from datetime import date 

3from typing import Optional, Any 

4from enum import Enum 

5 

6from pydantic import Field, field_validator, EmailStr 

7 

8from .base import Neo4jNode 

9 

10 

11class Gender(str, Enum): 

12 """性別""" 

13 MALE = "male" 

14 FEMALE = "female" 

15 OTHER = "other" 

16 UNKNOWN = "unknown" 

17 

18 

19class 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})"