use Newslash::Model::Base -base;
use Data::Dumper;
+use List::Util qw(reduce any);
+use POSIX;
my $m2_params = {
m2_mintokes => 0,
m2_consensus => 9,
+
+ count_threshold => 3,
+ agreement_threshold_ratio => 0.66,
+ prevent_params => {
+ alpha => 0.85,
+ beta => 0.43,
+ min => 0.5,
+ max => 1.0,
+ min_count => 3,
+ max_count => 20,
+ reduction => 1.0,
+ },
+ enforce_params => {
+ alpha => 0.85,
+ beta => 0.086,
+ min => 0.05,
+ max => 0.15,
+ min_count => 3,
+ max_count => 20,
+ reduction => 0.1,
+ },
+ token_unfair_params => {
+ alpha => 0.56,
+ beta => 21,
+ min => 1.0,
+ max => 20.0,
+ min_count => 3,
+ max_count => 20,
+ },
+ token_fair_params => {
+ alpha => 0.56,
+ beta => 4.2,
+ min => 0.2,
+ max => 4.0,
+ min_count => 3,
+ max_count => 20,
+ },
};
+sub create {
+ my ($self, $user, $moderation, $value) = @_;
+ my $moderations = $self->new_instance_of("Moderations");
+
+ # user validation
+ if (!$moderations->is_metamoderatable($user, $moderation)
+ && !$user->{is_admin}
+ && !$user->{editor} ) {
+ $self->set_error("you cannot metamoderate the moderation");
+ return;
+ }
+
+ my $metamods = $self->select(id => $moderation->{id});
+ return if !defined $metamods;
+ my $mods = $moderations->select(cid => $moderation->{cid});
+ return if !defined $mods;
+
+ # calc unchanged moderation's fixed scores
+ my $unchanged_scores = [];
+ for my $mod (@$mods) {
+ next if $mod->{id} == $moderation->{id};
+ my $mms = $self->select(id => $mod->{id});
+ push $unchanged_scores, $self->_get_fixed_scores($mod, $mms);
+ }
+ my $base_scores = {
+ points => reduce { $a->{points} + $b->{points} } $unchanged_scores,
+ karma => reduce { $a->{karma} + $b->{karma} } $unchanged_scores,
+ };
+
+ # calc changed moderation's old score
+ my $old_score = $self->_get_fixed_scores($moderation, $metamods);
+ my $old_total = {
+ points => floor($base_scores->{points} + $old_score->{points}),
+ karma => floor($base_scores->{karma} + $old_score->{karma}),
+ };
+ my $old_result = $self->_get_votes_result($moderation, $metamods);
+ my $old_moderator_scores = $self->_get_fixed_moderator_scores($moderation, $old_result);
+
+ $self->start_transaction;
+
+ # create metamod
+ my $rs = $self->_create_metamodlog($user, $moderation, $value);
+ if (!$rs) {
+ $self->rollback;
+ return;
+ }
+
+ # calc changed moderation's new score
+ my $new_metamod = {
+ val => $value,
+ uid => $user->{uid},
+ mmid => $moderation->{id},
+ active => 1,
+ };
+ push @$metamods, $new_metamod;
+ my $new_score = $self->_get_fixed_scores($moderation, $metamods);
+ my $new_total = {
+ points => floor($base_scores->{points} + $new_score->{points}),
+ karma => floor($base_scores->{karma} + $new_score->{karma}),
+ };
+ my $delta = {
+ points => $new_total->{points} - $old_total->{points},
+ karma => $new_total->{karma} - $old_total->{karma},
+ };
+
+ # update comment's point
+ my $comments = $self->new_instance_of("Comments");
+ my $rs = $comments->update(cid => $moderation->{cid},
+ points => { add => $delta->{points} });
+ if (!$rs) {
+ $self->rollback;
+ return;
+ }
+
+ # update comment author's karma
+ my $users = $self->new_instance_of("Users");
+ $rs = $users->update_info(uid => $moderation->{cuid},
+ karma => { add => $delta->{karma} });
+ if (!$rs) {
+ $self->rollback;
+ return;
+ }
+
+ # update moderator's info
+ my $new_result = $self->_get_votes_result($moderation, $metamods);
+ my $new_moderator_scores = $self->_get_fixed_moderator_scores($moderation, $old_result);
+ my $u_delta = {};
+ for my $k (qw(karma tokens up_fair down_fair up_unfair down_unfair)) {
+ $u_delta->{$k} = $old_result->{$k} - $new_result->{$k};
+ }
+ $users->update_info(uid => $moderation->{uid},
+ tokens => { add => $u_delta->{tokens} },
+ up_fair => { add => $u_delta->{up_fair} },
+ down_fair => { add => $u_delta->{down_fair} },
+ up_unfair => { add => $u_delta->{up_unfair} },
+ down_unfair => { add => $u_delta->{down_unfair} });
+
+ # update metamoderator's info
+ my $new_params = {};
+ $new_params->{m2_fair} = 1 if $value > 0;
+ $new_params->{m2_unfair} = 1 if $value < 0;
+ if (($value > 0) && ($moderation->{val} > 0)) {
+ $new_params->{m2voted_up_fair} = 1;
+ }
+ if (($value > 0) && ($moderation->{val} < 0)) {
+ $new_params->{m2voted_down_fair} = 1;
+ }
+ if (($value < 0) && ($moderation->{val} > 0)) {
+ $new_params->{m2voted_up_unfair} = 1;
+ }
+ if (($value < 0) && ($moderation->{val} < 0)) {
+ $new_params->{m2voted_down_unfair} = 1;
+ }
+ $users->update_info(uid => $user->{uid}, %$new_params);
+
+ $self->commit;
+ return 1;
+}
+
+sub _get_fixed_moderator_scores {
+ my ($self, $mod, $result) = @_;
+ my $rs = {
+ karma => 0,
+ tokens => 0,
+ up_fair => 0,
+ down_fair => 0,
+ up_unfair => 0,
+ down_unfair => 0,
+ };
+
+ return $rs if $result->{result} == 0;
+
+ my $max_ratio = 0;
+ my $p;
+ my $votes = $result->{votes};
+
+ if ($result->{result} == 1) {
+ $p = $m2_params->{token_fair_params};
+ }
+ elsif ($result->{result} == -1) {
+ $p = $m2_params->{token_unfair_params};
+ }
+
+ if ($p) {
+ if ($votes <= $p->{min_count}) {
+ $max_ratio = $p->{min};
+ }
+ elsif ($votes >= $p->{max_count}) {
+ $max_ratio = $p->{max};
+ }
+ else {
+ $max_ratio =
+ log(($votes - $p->{min_count}) * $p->{alpha})/log(10) * $p->{beta} + $p->{min};
+ }
+ }
+ $rs->{tokens} = $result->{result} * $max_ratio * $result->{ratio};
+
+ if ($result->{result} == 1) {
+ $rs->{karma} = 0.5;
+ $rs->{up_fair} = 1 if $mod->{val} > 0;
+ $rs->{down_fair} = 1 if $mod->{val} < 0;
+ }
+ elsif ($result->{result} == -1) {
+ $rs->{karma} = -1;
+ $rs->{up_unfair} = 1 if $mod->{val} > 0;
+ $rs->{down_unfair} = 1 if $mod->{val} < 0;
+ }
+ return $rs;
+}
+
+sub _get_fixed_scores {
+ my ($self, $mod, $metamods) = @_;
+ return if !defined $mod;
+ my $rs = {
+ points => 0,
+ karma => 0,
+ };
+ return $rs if !$mod->{active};
+ return $rs if !defined $metamods;
+
+ my $affect = $self->_get_votes_result($mod, $metamods);
+
+ if ($affect->{result} > 0) {
+ # nod
+ $rs->{points} = $mod->{val} * (1.0 + $affect->{enforce});
+ $rs->{karma} = $mod->{val} * (1.0 + $affect->{enforce});
+ }
+ elsif ($affect->{result} < 0) {
+ # nix
+ $rs->{points} = $mod->{val} * (1.0 - $affect->{prevent});
+ $rs->{karma} = $mod->{val} * (1.0 - $affect->{prevent});
+ }
+ else {
+ # no agreement
+ $rs->{points} = $mod->{val};
+ $rs->{karma} = $mod->{val};
+ }
+ return $rs;
+}
+
+sub _get_votes_result {
+ my ($self, $mod, $metamods) = @_;
+ my $count = @$metamods;
+ my $rs = {
+ result => 0,
+ ratio => 0,
+ enforce => 0,
+ prevent => 0,
+ votes => $count,
+ };
+ if ($count < $m2_params->{count_threshold}) {
+ return $rs;
+ }
+
+ my $nods = grep { $_->{val} == 1 && $_->{active} } @$metamods;
+ my $nixes = grep { $_->{val} == -1 && $_->{active} } @$metamods;
+
+ my $fraction = $nods / $count;
+ if ($fraction > $m2_params->{agreement_threshold_ratio}) {
+ $rs->{result} = 1;
+ $rs->{ratio} = $fraction;
+ }
+ else {
+ $fraction = $nixes / $count;
+ if ($fraction > $m2_params->{agreement_threshold_ratio}) {
+ $rs->{result} = -1;
+ $rs->{ratio} = $fraction;
+ }
+ }
+
+ if ($rs->{result} == 0) {
+ # no agreement
+ return $rs;
+ }
+ elsif ($rs->{result} < 0) {
+ # agreement is nix
+ $rs->{prevent} =
+ $self->_calc_ratio($m2_params->{prevent_params}, $count, $fraction);
+ }
+ elsif ($rs->{result} > 0) {
+ # agreement is nod
+ $rs->{enforce} =
+ $self->_calc_ratio($m2_params->{enfoce_params}, $count, $fraction);
+ }
+ return $rs;
+
+}
+
+sub _calc_ratio {
+ my ($self, $p, $votes, $agreement_ratio) = @_;
+ my $max_prevent_ratio;
+ if ($votes <= $p->{min_count}) {
+ $max_prevent_ratio = $p->{min};
+ }
+ elsif ($votes >= $p->{max_count}) {
+ $max_prevent_ratio = $p->{max};
+ }
+ else {
+ $max_prevent_ratio =
+ log(($votes - $p->{min_count}) * $p->{alpha})/log(10) * $p->{beta} + $p->{min};
+ }
+ return $max_prevent_ratio - $p->{reduction} * (1.0 - $agreement_ratio);
+}
+
+sub select {
+ my $self = shift;
+ my $params = {@_};
+ if ($params->{moderation_id}) {
+ $params->{mmid} = $params->{moderation_id};
+ }
+ my $uniques = [qw(id)];
+ my $keys = [qw(uid val mmid)];
+ return $self->base_select("metamodlog", $uniques, $keys, [%$params]);
+}
+
+sub _create_metamodlog {
+ my ($self, $user, $moderation, $value) = @_;
+ my $dbh = $self->connect_db;
+ my $sql = <<"EOSQL";
+INSERT INTO metamodlog
+ (mmid, uid, val, ts, active)
+ VALUES
+ (?, ?, ?, NOW(), 1)
+EOSQL
+
+ my $rs = $dbh->do($sql, undef, $moderation->{id}, $user->{uid}, $value);
+ if (!$rs) {
+ $self->disconnect_db;
+ return;
+ }
+ $self->disconnect_db;
+ return $rs;
+}
+
+
# import from ::Metamod
sub getM2Needed {
return 1;
}
-sub create {
- my ($self, $params, $user, $extra_params, $opts) = @_;
- my $users = $self->new_instance_of("Users");
- my $user_info = $users->select(target => "info", uid => $user->{uid});
-
- # user validation
- if (!$user_info->{m2_mods_saved}
- && !$user->{is_admin}
- && !$extra_params->{inherited}) {
- return;
- }
-}
-
sub adjustForNewMod {
my ($self, $user, $comment, $reason, $active, $mod_id) = @_;
my $i_m2s = $self->getInheritedM2sForMod($user, $comment, $reason, $active, $mod_id);