Coverage for src/inheritance_calculator_core/models/inheritance.py: 95%

96 statements  

« prev     ^ index     » next       coverage.py v7.11.0, created at 2025-10-17 05:31 +0900

1"""相続計算結果モデル""" 

2from typing import List, Optional, Any 

3from fractions import Fraction 

4from enum import Enum 

5from uuid import UUID 

6 

7from pydantic import BaseModel, Field, field_validator, ConfigDict 

8 

9from .person import Person 

10 

11 

12class HeritageRank(str, Enum): 

13 """相続順位""" 

14 SPOUSE = "spouse" # 配偶者(常に相続人) 

15 FIRST = "first" # 第1順位(子・直系卑属) 

16 SECOND = "second" # 第2順位(直系尊属) 

17 THIRD = "third" # 第3順位(兄弟姉妹) 

18 

19 

20class SubstitutionType(str, Enum): 

21 """代襲相続タイプ""" 

22 NONE = "none" # 代襲なし 

23 CHILD = "child" # 子の代襲(孫、曾孫...) 

24 SIBLING = "sibling" # 兄弟姉妹の代襲(甥・姪のみ) 

25 

26 

27class Heir(BaseModel): 

28 """ 

29 相続人 

30 

31 相続人の資格と相続割合を表現。 

32 """ 

33 

34 model_config = ConfigDict( 

35 validate_assignment=True, 

36 extra='forbid', 

37 ) 

38 

39 person: Person = Field(..., description="相続人") 

40 rank: HeritageRank = Field(..., description="相続順位") 

41 share: Fraction = Field(..., description="相続割合(法定相続分)") 

42 share_percentage: float = Field(..., description="相続割合(百分率)") 

43 is_substitution: bool = Field(default=False, description="代襲相続人か") 

44 substitution_type: SubstitutionType = Field( 

45 default=SubstitutionType.NONE, description="代襲相続タイプ" 

46 ) 

47 substituted_person: Optional[Person] = Field( 

48 default=None, description="被代襲者(代襲相続の場合)" 

49 ) 

50 generation: int = Field(default=1, description="代襲世代数(1=直接相続人)") 

51 is_retransfer: bool = Field(default=False, description="再転相続人か") 

52 retransfer_from: Optional[Person] = Field( 

53 default=None, description="再転相続元の相続人(遺産分割前に死亡した相続人)" 

54 ) 

55 original_share: Optional[Fraction] = Field( 

56 default=None, description="再転相続前の元の相続分" 

57 ) 

58 

59 @field_validator('share_percentage', mode='before') 

60 @classmethod 

61 def calculate_percentage(cls, v: Any, info: Any) -> float: 

62 """相続割合の百分率を計算""" 

63 # shareから計算(vが0.0の場合も含む) 

64 values = info.data 

65 share = values.get('share') 

66 if share is not None: 

67 return float(share) * 100 

68 

69 # すでに計算されている場合はそのまま返す 

70 if isinstance(v, float): 

71 return v 

72 

73 return 0.0 

74 

75 @field_validator('substitution_type') 

76 @classmethod 

77 def validate_substitution_type(cls, v: SubstitutionType, info: Any) -> SubstitutionType: 

78 """代襲相続タイプの検証""" 

79 values = info.data 

80 is_substitution = values.get('is_substitution', False) 

81 

82 # 代襲相続でない場合、タイプはNONEでなければならない 

83 if not is_substitution and v != SubstitutionType.NONE: 

84 raise ValueError("Non-substitution heir must have NONE type") 

85 

86 # 代襲相続の場合、タイプはNONE以外でなければならない 

87 if is_substitution and v == SubstitutionType.NONE: 

88 raise ValueError("Substitution heir must have valid substitution type") 

89 

90 return v 

91 

92 def __str__(self) -> str: 

93 """文字列表現""" 

94 substitution_mark = "" 

95 if self.is_substitution: 

96 substitution_mark = f"(代襲: {self.substituted_person.name if self.substituted_person else '不明'}" 

97 

98 share_str = f"{self.share} ({self.share_percentage:.2f}%)" 

99 return f"{self.person.name} - {self.rank.value} - {share_str}{substitution_mark}" 

100 

101 

102class InheritanceResult(BaseModel): 

103 """ 

104 相続計算結果 

105 

106 相続人の確定と相続割合の計算結果を表現。 

107 """ 

108 

109 model_config = ConfigDict( 

110 validate_assignment=True, 

111 extra='forbid', 

112 ) 

113 

114 decedent: Person = Field(..., description="被相続人") 

115 heirs: List[Heir] = Field(default_factory=list, description="相続人リスト") 

116 has_spouse: bool = Field(default=False, description="配偶者がいるか") 

117 has_children: bool = Field(default=False, description="子がいるか") 

118 has_parents: bool = Field(default=False, description="直系尊属がいるか") 

119 has_siblings: bool = Field(default=False, description="兄弟姉妹がいるか") 

120 calculation_basis: List[str] = Field( 

121 default_factory=list, description="計算根拠(適用した民法条文等)" 

122 ) 

123 

124 @field_validator('heirs') 

125 @classmethod 

126 def validate_total_share(cls, v: List[Heir]) -> List[Heir]: 

127 """相続割合の合計が1(100%)であることを検証""" 

128 if not v: 

129 return v 

130 

131 total = sum(heir.share for heir in v) 

132 if total != Fraction(1, 1): 

133 raise ValueError( 

134 f"Total share must be 1 (100%), but got {total} ({float(total) * 100}%)" 

135 ) 

136 

137 return v 

138 

139 def add_heir( 

140 self, 

141 person: Person, 

142 rank: HeritageRank, 

143 share: Fraction, 

144 is_substitution: bool = False, 

145 substitution_type: SubstitutionType = SubstitutionType.NONE, 

146 substituted_person: Optional[Person] = None, 

147 generation: int = 1 

148 ) -> None: 

149 """ 

150 相続人を追加 

151 

152 Args: 

153 person: 相続人 

154 rank: 相続順位 

155 share: 相続割合 

156 is_substitution: 代襲相続人か 

157 substitution_type: 代襲相続タイプ 

158 substituted_person: 被代襲者 

159 generation: 代襲世代数 

160 """ 

161 heir = Heir( 

162 person=person, 

163 rank=rank, 

164 share=share, 

165 share_percentage=float(share) * 100, 

166 is_substitution=is_substitution, 

167 substitution_type=substitution_type, 

168 substituted_person=substituted_person, 

169 generation=generation 

170 ) 

171 self.heirs.append(heir) 

172 

173 def add_calculation_basis(self, basis: str) -> None: 

174 """ 

175 計算根拠を追加 

176 

177 Args: 

178 basis: 計算根拠(民法条文等) 

179 """ 

180 self.calculation_basis.append(basis) 

181 

182 def get_heirs_by_rank(self, rank: HeritageRank) -> List[Heir]: 

183 """ 

184 指定された順位の相続人を取得 

185 

186 Args: 

187 rank: 相続順位 

188 

189 Returns: 

190 相続人のリスト 

191 """ 

192 return [heir for heir in self.heirs if heir.rank == rank] 

193 

194 def get_substitution_heirs(self) -> List[Heir]: 

195 """ 

196 代襲相続人のリストを取得 

197 

198 Returns: 

199 代襲相続人のリスト 

200 """ 

201 return [heir for heir in self.heirs if heir.is_substitution] 

202 

203 @property 

204 def total_heirs(self) -> int: 

205 """相続人の総数""" 

206 return len(self.heirs) 

207 

208 def __str__(self) -> str: 

209 """文字列表現""" 

210 result = [f"被相続人: {self.decedent.name}"] 

211 result.append(f"相続人総数: {self.total_heirs}") 

212 result.append("---") 

213 

214 for heir in self.heirs: 

215 result.append(str(heir)) 

216 

217 if self.calculation_basis: 

218 result.append("---") 

219 result.append("計算根拠:") 

220 for basis in self.calculation_basis: 

221 result.append(f" - {basis}") 

222 

223 return "\n".join(result)