2009年9月22日火曜日

Drools で法定相続分を求めてみる

Drools の自由研究3回目。遺産相続時の各相続人の法定相続分を推論するルールを書いてみた。
遺産相続のルールは、簡単に言うと以下のような分け方になる。
  • 子がいる場合
       
    • 配偶者がいるなら、配偶者が1/2、子が残りを等分  
    • 配偶者がいないなら、子が全てを等分

  • 子がいなくて直系尊属がいる場合
       
    • 配偶者がいるなら、配偶者が2/3、直系尊属が残りを等分  
    • 配偶者がいないなら、直系尊属が全てを等分

  • 子も直系尊属もいないが、兄弟姉妹がいる場合
       
    • 配偶者がいるなら、配偶者が3/4、兄弟姉妹が残りを等分  
    • 配偶者がいないなら、兄弟姉妹が全てを等分

  • 子も直系尊属も兄弟姉妹もいない場合
       
    • 配偶者がいるなら、配偶者が全額取得  
    • 配偶者がいないなら国庫へ

ただし現実のルールよりも、以下のような点で簡略化してある。
  • 直系尊属は本当は最近親ということになるが、ここでは親のみとした
  • 嫡出子と非嫡出子で本当は取り分が違うが、区別しないことにした
  • 子が死んでるときに代襲相続が発生するが、省略した
package com.sample

global com.sample.Person decedent;

function void printSuccession(Succession s) {
 System.out.println(
  String.format("%s:%s %d/%d",
   s.getRelation(), 
   s.getPerson().getName(), 
   s.getNumerator(), s.getDenominator()));
}

rule "配偶者の識別"
 when
  $spouse: Person(spouse == decedent)
 then 
  insert(new Succession($spouse, "配偶者"));
end

rule "子の識別"
 when
  $oyako: ParentsAndChildren(parents contains decedent)
  $child: Person()
  eval($oyako.getChildren().contains($child)) 
 then 
  insert(new Succession($child, "子"));
end

rule "直系尊属の識別"
 when
  $oyako: ParentsAndChildren(children contains decedent)
  $parent: Person()
  eval($oyako.getParents().contains($parent)) 
 then 
  insert(new Succession($parent, "直系尊属"));
end

rule "兄弟姉妹の識別"
 when
  $oyako: ParentsAndChildren(children contains decedent)
  $sibling: Person()
  eval($sibling != decedent && $oyako.getChildren().contains($sibling)) 
 then 
  insert(new Succession($sibling, "兄弟姉妹"));
end

rule "配偶者と分けるときの子の取り分"
  salience -100
 when
  Succession(relation=="配偶者")
  $child: Succession(relation=="子")
  Number(c: intValue) 
   from accumulate (s: Succession(relation=="子"), count(s))
 then
  $child.setRatio(1, c * 2);
  printSuccession($child);
end

rule "配偶者と分けるときの直系尊属の取り分"
  salience -100
 when
  Succession(relation=="配偶者")
  not Succession(relation=="子")
  $parent: Succession(relation=="直系尊属")
  Number(c: intValue) 
   from accumulate (s: Succession(relation=="直系尊属"), count(s))
 then
  $parent.setRatio(1, c * 3);
  printSuccession($parent);
end

rule "配偶者と分けるときの兄弟姉妹の取り分"
  salience -100
 when
  Succession(relation=="配偶者")
  not Succession(relation=="子")
  not Succession(relation=="直系尊属")
  $sibling: Succession(relation=="兄弟姉妹")
  Number(c: intValue) 
   from accumulate (s: Succession(relation=="兄弟姉妹"), count(s))
 then
  $sibling.setRatio(1, c * 4);
  printSuccession($sibling);
end

rule "子のみの場合の取り分"
  salience -100
 when
  not Succession(relation=="配偶者")
  $child: Succession(relation=="子")
  Number(c: intValue) 
   from accumulate (s: Succession(relation=="子"), count(s))
 then
  $child.setRatio(1, c);
  printSuccession($child);
end

rule "直系尊属のみの場合の取り分"
  salience -100
 when
  not Succession(relation=="配偶者")
  not Succession(relation=="子")
  $parent: Succession(relation=="直系尊属")
  Number(c: intValue) 
   from accumulate (s: Succession(relation=="直系尊属"), count(s))
 then
  $parent.setRatio(1, c);
  printSuccession($parent);
end

rule "兄弟姉妹のみの場合の取り分"
  salience -100
 when
  not Succession(relation=="配偶者")
  not Succession(relation=="子")
  not Succession(relation=="直系尊属")
  $sibling: Succession(relation=="兄弟姉妹")
  Number(c: intValue) 
   from accumulate (s: Succession(relation=="兄弟姉妹"), count(s))
 then
  $sibling.setRatio(1, c);
  printSuccession($sibling);
end

rule "子と分けるときの配偶者の取り分"
  salience -100
 when
  $spouse: Succession(relation=="配偶者")
  exists Succession(relation=="子")
 then
  $spouse.setRatio(1, 2);
  printSuccession($spouse);
end

rule "直系尊属と分けるときの配偶者の取り分"
  salience -100
 when
  $spouse: Succession(relation=="配偶者")
  not Succession(relation=="子")
  exists Succession(relation=="直系尊属")
 then
  $spouse.setRatio(2, 3);
  printSuccession($spouse);
end

rule "兄弟姉妹と分けるときの配偶者の取り分"
  salience -100
 when
  $spouse: Succession(relation=="配偶者")
  not Succession(relation=="子")
  not Succession(relation=="直系尊属")
  exists Succession(relation=="兄弟姉妹")
 then
  $spouse.setRatio(3, 4);
  printSuccession($spouse);
end

rule "配偶者のみで分けるときの配偶者の取り分"
  salience -100
 when
  $spouse: Succession(relation=="配偶者")
  not Succession(relation=="子")
  not Succession(relation=="直系尊属")
  not Succession(relation=="兄弟姉妹")
 then
  $spouse.setRatio(1, 1);
  printSuccession($spouse);
end

rule "相続人なしの場合"
  salience -100
 when
  not Succession()
 then
  System.out.println("全て国庫へ");
end
Java コードでは親子関係と配偶関係で家系図表現し、その中の死んだ人をグローバル変数 decedentに設定し、その上でルールを実行する。親子関係のモデリングはいろいろやり方があると思うが、親側と子側に複数の個人を保持するクラス ParentsAndChildrenを定義して、これを用いて表現してみた。(親子関係のモデリングなんて一見自明なようだけど、離婚とか養子縁組とかを考え始めると、意外と複雑で、いろいろと工夫や割り切りが必要になる事に、やってみて改めて気付いたりする)

ルールの適用は、まず最初の段階で故人(decedent)に関連する相続人候補を整理し、次の段階で相続分を判定すると言う形で、2段階になるようにしてみた。そのため salience を用いて順序を指定している。本当は、ルール定義で使う型を declare キーワードを用いてルールの一部として定義したかったが、Drools 5.0 のバグで accumulate からの count で誤ったClassCastException が発生する(Drools 5.1で直るらしい)ということで、仕方なくJava コードで定義した。

上述のとおり、実際の法律より単純化してはいるが一応上手く動く。例えば、子と親は無いが、配偶者と2人の兄弟がいる場合、コンソールの以下のように出力される
兄弟姉妹:C 1/8
兄弟姉妹:D 1/8
配偶者:B 3/4
ほかのパターンもどうやら上手く動いている。

今回やってみて、やはりエンドユーザなりビジネスパーソンなりが自分でルールを書くのは、ちょっと敷居が高い気がする。そもそも、そういうものではないのかもしれない。これでもかなり現実の相続ルールを簡略化しているのだけど、プログラマの自分でもそう簡単にスラスラとは書けるわけではない(まあ触り始めたばかりというのもあるけど)。本来のルールだと例えば最近親をちゃんと求める事も必要になり、おそらく再帰的なロジックが必要になるはずだけど、Drools でどう表現したらいいのかすぐにはイメージできなかったりする。

ドキュメントを見ると、個々のドメインに更に特化した DSL を定義する事もできるようだけど、これはこれで開発者側の負担が大きいはず。やはりドメイン知識を相当程度理解している Drools 技術者がルールを書くのが現実的かもしれない。あとは ビジネスパーソンには SBVR なんかを使ってルールを書いてもらって、それから Droolsのルール定義を自動か半自動で生成するような仕組みもありかも。まあ、それも開発者の負担が大きいか・・・

0 件のコメント:

コメントを投稿