liquid_feedback_core

diff core.sql @ 147:37a264fb5eef

Merged bugfix in function "close_voting": Create autoreject ballots only for members with "voting_right" in the unit
author jbe
date Thu Jun 02 01:25:08 2011 +0200 (2011-06-02)
parents 0fa3c4739ca7 259dd3e127c0
children ec1fdf2fc8c9
line diff
     1.1 --- a/core.sql	Thu Jun 02 01:23:49 2011 +0200
     1.2 +++ b/core.sql	Thu Jun 02 01:25:08 2011 +0200
     1.3 @@ -7,7 +7,7 @@
     1.4  BEGIN;
     1.5  
     1.6  CREATE VIEW "liquid_feedback_version" AS
     1.7 -  SELECT * FROM (VALUES ('1.4.0_rc2', 1, 4, -1))
     1.8 +  SELECT * FROM (VALUES ('1.4.0_rc1', 1, 4, -1))
     1.9    AS "subquery"("string", "major", "minor", "revision");
    1.10  
    1.11  
    1.12 @@ -310,7 +310,9 @@
    1.13          "initiative_quorum_den" INT4            NOT NULL,
    1.14          "majority_num"          INT4            NOT NULL DEFAULT 1,
    1.15          "majority_den"          INT4            NOT NULL DEFAULT 2,
    1.16 -        "majority_strict"       BOOLEAN         NOT NULL DEFAULT TRUE );
    1.17 +        "majority_strict"       BOOLEAN         NOT NULL DEFAULT TRUE,
    1.18 +        "majority_positive"     INT4            NOT NULL DEFAULT 0,
    1.19 +        "majority_non_negative" INT4            NOT NULL DEFAULT 0 );
    1.20  CREATE INDEX "policy_active_idx" ON "policy" ("active");
    1.21  
    1.22  COMMENT ON TABLE "policy" IS 'Policies for a particular proceeding type (timelimits, quorum)';
    1.23 @@ -325,9 +327,11 @@
    1.24  COMMENT ON COLUMN "policy"."issue_quorum_den"      IS 'Denominator of potential supporter quorum to be reached by one initiative of an issue to be "accepted"';
    1.25  COMMENT ON COLUMN "policy"."initiative_quorum_num" IS   'Numerator of satisfied supporter quorum  to be reached by an initiative to be "admitted" for voting';
    1.26  COMMENT ON COLUMN "policy"."initiative_quorum_den" IS 'Denominator of satisfied supporter quorum to be reached by an initiative to be "admitted" for voting';
    1.27 -COMMENT ON COLUMN "policy"."majority_num"          IS   'Numerator of fraction of majority to be reached during voting by an initiative to be aggreed upon';
    1.28 -COMMENT ON COLUMN "policy"."majority_den"          IS 'Denominator of fraction of majority to be reached during voting by an initiative to be aggreed upon';
    1.29 +COMMENT ON COLUMN "policy"."majority_num"          IS   'Numerator of fraction of majority to be reached during voting by an initiative to be "attainable"';
    1.30 +COMMENT ON COLUMN "policy"."majority_den"          IS 'Denominator of fraction of majority to be reached during voting by an initiative to be "attainable"';
    1.31  COMMENT ON COLUMN "policy"."majority_strict"       IS 'If TRUE, then the majority must be strictly greater than "majority_num"/"majority_den", otherwise it may also be equal.';
    1.32 +COMMENT ON COLUMN "policy"."majority_positive"     IS 'Absolute number of "positive_votes" neccessary for an initiative to be "attainable".';
    1.33 +COMMENT ON COLUMN "policy"."majority_non_negative" IS 'Absolute number of sum of "positive_votes" and abstentions neccessary for an initiative to be "attainable".';
    1.34  
    1.35  
    1.36  CREATE TABLE "unit" (
    1.37 @@ -441,8 +445,6 @@
    1.38          "snapshot"              TIMESTAMPTZ,
    1.39          "latest_snapshot_event" "snapshot_event",
    1.40          "population"            INT4,
    1.41 -        "vote_now"              INT4,
    1.42 -        "vote_later"            INT4,
    1.43          "voter_count"           INT4,
    1.44          CONSTRAINT "valid_state" CHECK ((
    1.45            ("accepted" ISNULL  AND "half_frozen" ISNULL  AND "fully_frozen" ISNULL  AND "closed" ISNULL  AND "ranks_available" = FALSE) OR
    1.46 @@ -502,11 +504,9 @@
    1.47  COMMENT ON COLUMN "issue"."discussion_time"       IS 'Copied from "policy" table at creation of issue';
    1.48  COMMENT ON COLUMN "issue"."verification_time"     IS 'Copied from "policy" table at creation of issue';
    1.49  COMMENT ON COLUMN "issue"."voting_time"           IS 'Copied from "policy" table at creation of issue';
    1.50 -COMMENT ON COLUMN "issue"."snapshot"              IS 'Point in time, when snapshot tables have been updated and "population", "vote_now", "vote_later" and *_count values were precalculated';
    1.51 +COMMENT ON COLUMN "issue"."snapshot"              IS 'Point in time, when snapshot tables have been updated and "population" and *_count values were precalculated';
    1.52  COMMENT ON COLUMN "issue"."latest_snapshot_event" IS 'Event type of latest snapshot for issue; Can be used to select the latest snapshot data in the snapshot tables';
    1.53  COMMENT ON COLUMN "issue"."population"            IS 'Sum of "weight" column in table "direct_population_snapshot"';
    1.54 -COMMENT ON COLUMN "issue"."vote_now"              IS 'Number of votes in favor of voting now, as calculated from table "direct_interest_snapshot"';
    1.55 -COMMENT ON COLUMN "issue"."vote_later"            IS 'Number of votes against voting now, as calculated from table "direct_interest_snapshot"';
    1.56  COMMENT ON COLUMN "issue"."voter_count"           IS 'Total number of direct and delegating voters; This value is related to the final voting, while "population" is related to snapshots before the final voting';
    1.57  
    1.58  
    1.59 @@ -537,8 +537,13 @@
    1.60          "satisfied_informed_supporter_count" INT4,
    1.61          "positive_votes"        INT4,
    1.62          "negative_votes"        INT4,
    1.63 -        "agreed"                BOOLEAN,
    1.64 -        "rank"                  INT4,
    1.65 +        "attainable"            BOOLEAN,
    1.66 +        "favored"               BOOLEAN,
    1.67 +        "unfavored"             BOOLEAN,
    1.68 +        "preliminary_rank"      INT4,
    1.69 +        "final_rank"            INT4,
    1.70 +        "disqualified"          BOOLEAN,
    1.71 +        "winner"                BOOLEAN,
    1.72          "text_search_data"      TSVECTOR,
    1.73          CONSTRAINT "all_or_none_of_revoked_and_revoked_by_member_id_must_be_null"
    1.74            CHECK ("revoked" NOTNULL = "revoked_by_member_id" NOTNULL),
    1.75 @@ -546,12 +551,13 @@
    1.76            CHECK ("revoked" NOTNULL OR "suggested_initiative_id" ISNULL),
    1.77          CONSTRAINT "revoked_initiatives_cant_be_admitted"
    1.78            CHECK ("revoked" ISNULL OR "admitted" ISNULL),
    1.79 -        CONSTRAINT "non_admitted_initiatives_cant_contain_voting_results"
    1.80 -          CHECK (("admitted" NOTNULL AND "admitted" = TRUE) OR ("positive_votes" ISNULL AND "negative_votes" ISNULL AND "agreed" ISNULL)),
    1.81 -        CONSTRAINT "all_or_none_of_positive_votes_negative_votes_and_agreed_must_be_null"
    1.82 -          CHECK ("positive_votes" NOTNULL = "negative_votes" NOTNULL AND "positive_votes" NOTNULL = "agreed" NOTNULL),
    1.83 -        CONSTRAINT "non_agreed_initiatives_cant_get_a_rank"
    1.84 -          CHECK (("agreed" NOTNULL AND "agreed" = TRUE) OR "rank" ISNULL) );
    1.85 +        CONSTRAINT "non_admitted_initiatives_cant_contain_voting_results" CHECK (
    1.86 +          ( "admitted" NOTNULL AND "admitted" = TRUE ) OR
    1.87 +          ( "positive_votes" ISNULL AND "negative_votes" ISNULL AND
    1.88 +            "attainable" ISNULL AND "favored" ISNULL AND "unfavored" ISNULL AND
    1.89 +            "disqualified" ISNULL AND "preliminary_rank" ISNULL AND
    1.90 +            "final_rank" ISNULL AND "winner" ISNULL ) ),
    1.91 +        CONSTRAINT "favored_excludes_unfavored" CHECK (NOT ("favored" AND "unfavored")) );
    1.92  CREATE INDEX "initiative_created_idx" ON "initiative" ("created");
    1.93  CREATE INDEX "initiative_revoked_idx" ON "initiative" ("revoked");
    1.94  CREATE INDEX "initiative_text_search_data_idx" ON "initiative" USING gin ("text_search_data");
    1.95 @@ -571,22 +577,33 @@
    1.96  COMMENT ON COLUMN "initiative"."informed_supporter_count"           IS 'Calculated from table "direct_supporter_snapshot"';
    1.97  COMMENT ON COLUMN "initiative"."satisfied_supporter_count"          IS 'Calculated from table "direct_supporter_snapshot"';
    1.98  COMMENT ON COLUMN "initiative"."satisfied_informed_supporter_count" IS 'Calculated from table "direct_supporter_snapshot"';
    1.99 -COMMENT ON COLUMN "initiative"."positive_votes" IS 'Calculated from table "direct_voter"';
   1.100 -COMMENT ON COLUMN "initiative"."negative_votes" IS 'Calculated from table "direct_voter"';
   1.101 -COMMENT ON COLUMN "initiative"."agreed"         IS 'TRUE, if "positive_votes"/("positive_votes"+"negative_votes") is strictly greater or greater-equal than "majority_num"/"majority_den"';
   1.102 -COMMENT ON COLUMN "initiative"."rank"           IS 'Rank of approved initiatives (winner is 1), calculated from table "direct_voter"';
   1.103 +COMMENT ON COLUMN "initiative"."positive_votes"       IS 'Calculated from table "direct_voter"';
   1.104 +COMMENT ON COLUMN "initiative"."negative_votes"       IS 'Calculated from table "direct_voter"';
   1.105 +COMMENT ON COLUMN "initiative"."attainable"           IS 'TRUE, if "positive_votes"/("positive_votes"+"negative_votes") is strictly greater or greater-equal than "majority_num"/"majority_den", and "positive_votes" is greater-equal than "majority_positive", and ("positive_votes"+abstentions) is greater-equal than "majority_non_negative"';
   1.106 +COMMENT ON COLUMN "initiative"."favored"              IS 'TRUE, if initiative has a schulze-ranking better than the status quo (without tie-breaking)';
   1.107 +COMMENT ON COLUMN "initiative"."unfavored"            IS 'TRUE, if initiative has a schulze-ranking worse than the status quo (without tie-breaking)';
   1.108 +COMMENT ON COLUMN "initiative"."preliminary_rank"     IS 'Schulze-Ranking without tie-breaking';
   1.109 +COMMENT ON COLUMN "initiative"."final_rank"           IS 'Schulze-Ranking after tie-breaking';
   1.110 +COMMENT ON COLUMN "initiative"."disqualified"         IS 'TRUE, if initiative may not win, because it either (a) has no better rank than the status quo, or (b) because there exists a better ranked initiative X, which directly beats this initiative, and either more voters prefer X to this initiative than voters preferring X to the status quo or less voters prefer this initiative to X than voters preferring the status quo to X';
   1.111 +COMMENT ON COLUMN "initiative"."winner"               IS 'TRUE, if initiative is final winner (best ranked initiative being "attainable" and not "disqualified")';
   1.112  
   1.113  
   1.114  CREATE TABLE "battle" (
   1.115 -        PRIMARY KEY ("issue_id", "winning_initiative_id", "losing_initiative_id"),
   1.116 -        "issue_id"              INT4,
   1.117 +        "issue_id"              INT4            NOT NULL,
   1.118          "winning_initiative_id" INT4,
   1.119          FOREIGN KEY ("issue_id", "winning_initiative_id") REFERENCES "initiative" ("issue_id", "id") ON DELETE CASCADE ON UPDATE CASCADE,
   1.120          "losing_initiative_id"  INT4,
   1.121          FOREIGN KEY ("issue_id", "losing_initiative_id") REFERENCES "initiative" ("issue_id", "id") ON DELETE CASCADE ON UPDATE CASCADE,
   1.122 -        "count"                 INT4            NOT NULL);
   1.123 -
   1.124 -COMMENT ON TABLE "battle" IS 'Number of members preferring one initiative to another; Filled by "battle_view" when closing an issue';
   1.125 +        "count"                 INT4            NOT NULL,
   1.126 +        CONSTRAINT "initiative_ids_not_equal" CHECK (
   1.127 +          "winning_initiative_id" != "losing_initiative_id" OR
   1.128 +          ( ("winning_initiative_id" NOTNULL AND "losing_initiative_id" ISNULL) OR
   1.129 +            ("winning_initiative_id" ISNULL AND "losing_initiative_id" NOTNULL) ) ) );
   1.130 +CREATE UNIQUE INDEX "battle_winning_losing_idx" ON "battle" ("issue_id", "winning_initiative_id", "losing_initiative_id");
   1.131 +CREATE UNIQUE INDEX "battle_winning_null_idx" ON "battle" ("issue_id", "winning_initiative_id") WHERE "losing_initiative_id" ISNULL;
   1.132 +CREATE UNIQUE INDEX "battle_null_losing_idx" ON "battle" ("issue_id", "losing_initiative_id") WHERE "winning_initiative_id" ISNULL;
   1.133 +
   1.134 +COMMENT ON TABLE "battle" IS 'Number of members preferring one initiative to another; Filled by "battle_view" when closing an issue; NULL as initiative_id denotes virtual "status-quo" initiative';
   1.135  
   1.136  
   1.137  CREATE TABLE "ignored_initiative" (
   1.138 @@ -731,14 +748,12 @@
   1.139          PRIMARY KEY ("issue_id", "member_id"),
   1.140          "issue_id"              INT4            REFERENCES "issue" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
   1.141          "member_id"             INT4            REFERENCES "member" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
   1.142 -        "autoreject"            BOOLEAN,
   1.143 -        "voting_requested"      BOOLEAN );
   1.144 +        "autoreject"            BOOLEAN );
   1.145  CREATE INDEX "interest_member_id_idx" ON "interest" ("member_id");
   1.146  
   1.147  COMMENT ON TABLE "interest" IS 'Interest of members in a particular issue; Frontends must ensure that interest for fully_frozen or closed issues is not added or removed.';
   1.148  
   1.149  COMMENT ON COLUMN "interest"."autoreject"       IS 'TRUE = member votes against all initiatives in case of not explicitly taking part in the voting procedure';
   1.150 -COMMENT ON COLUMN "interest"."voting_requested" IS 'TRUE = member wants to vote now, FALSE = member wants to vote later, NULL = policy rules should apply';
   1.151  
   1.152  
   1.153  CREATE TABLE "initiator" (
   1.154 @@ -854,15 +869,13 @@
   1.155          "issue_id"              INT4            REFERENCES "issue" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
   1.156          "event"                 "snapshot_event",
   1.157          "member_id"             INT4            REFERENCES "member" ("id") ON DELETE RESTRICT ON UPDATE RESTRICT,
   1.158 -        "weight"                INT4,
   1.159 -        "voting_requested"      BOOLEAN );
   1.160 +        "weight"                INT4 );
   1.161  CREATE INDEX "direct_interest_snapshot_member_id_idx" ON "direct_interest_snapshot" ("member_id");
   1.162  
   1.163  COMMENT ON TABLE "direct_interest_snapshot" IS 'Snapshot of active members having an "interest" in the "issue"';
   1.164  
   1.165  COMMENT ON COLUMN "direct_interest_snapshot"."event"            IS 'Reason for snapshot, see "snapshot_event" type for details';
   1.166  COMMENT ON COLUMN "direct_interest_snapshot"."weight"           IS 'Weight of member (1 or higher) according to "delegating_interest_snapshot"';
   1.167 -COMMENT ON COLUMN "direct_interest_snapshot"."voting_requested" IS 'Copied from column "voting_requested" of table "interest"';
   1.168  
   1.169  
   1.170  CREATE TABLE "delegating_interest_snapshot" (
   1.171 @@ -1870,6 +1883,18 @@
   1.172  COMMENT ON VIEW "critical_opinion" IS 'Opinions currently causing dissatisfaction';
   1.173  
   1.174  
   1.175 +CREATE VIEW "battle_participant" AS
   1.176 +    SELECT "initiative"."id", "initiative"."issue_id"
   1.177 +    FROM "issue" JOIN "initiative"
   1.178 +    ON "issue"."id" = "initiative"."issue_id"
   1.179 +    WHERE "initiative"."admitted"
   1.180 +  UNION ALL
   1.181 +    SELECT NULL, "id" AS "issue_id"
   1.182 +    FROM "issue";
   1.183 +
   1.184 +COMMENT ON VIEW "battle_participant" IS 'Helper view for "battle_view" containing admitted initiatives plus virtual "status-quo" initiative denoted by NULL reference';
   1.185 +
   1.186 +
   1.187  CREATE VIEW "battle_view" AS
   1.188    SELECT
   1.189      "issue"."id" AS "issue_id",
   1.190 @@ -1884,12 +1909,10 @@
   1.191    FROM "issue"
   1.192    LEFT JOIN "direct_voter"
   1.193    ON "issue"."id" = "direct_voter"."issue_id"
   1.194 -  JOIN "initiative" AS "winning_initiative"
   1.195 +  JOIN "battle_participant" AS "winning_initiative"
   1.196      ON "issue"."id" = "winning_initiative"."issue_id"
   1.197 -    AND "winning_initiative"."agreed"
   1.198 -  JOIN "initiative" AS "losing_initiative"
   1.199 +  JOIN "battle_participant" AS "losing_initiative"
   1.200      ON "issue"."id" = "losing_initiative"."issue_id"
   1.201 -    AND "losing_initiative"."agreed"
   1.202    LEFT JOIN "vote" AS "better_vote"
   1.203      ON "direct_voter"."member_id" = "better_vote"."member_id"
   1.204      AND "winning_initiative"."id" = "better_vote"."initiative_id"
   1.205 @@ -1898,13 +1921,16 @@
   1.206      AND "losing_initiative"."id" = "worse_vote"."initiative_id"
   1.207    WHERE "issue"."closed" NOTNULL
   1.208    AND "issue"."cleaned" ISNULL
   1.209 -  AND "winning_initiative"."id" != "losing_initiative"."id"
   1.210 +  AND (
   1.211 +    "winning_initiative"."id" != "losing_initiative"."id" OR
   1.212 +    ( ("winning_initiative"."id" NOTNULL AND "losing_initiative"."id" ISNULL) OR
   1.213 +      ("winning_initiative"."id" ISNULL AND "losing_initiative"."id" NOTNULL) ) )
   1.214    GROUP BY
   1.215      "issue"."id",
   1.216      "winning_initiative"."id",
   1.217      "losing_initiative"."id";
   1.218  
   1.219 -COMMENT ON VIEW "battle_view" IS 'Number of members preferring one initiative to another; Used to fill "battle" table';
   1.220 +COMMENT ON VIEW "battle_view" IS 'Number of members preferring one initiative (or status-quo) to another initiative (or status-quo); Used to fill "battle" table';
   1.221  
   1.222  
   1.223  CREATE VIEW "expired_session" AS
   1.224 @@ -2918,12 +2944,11 @@
   1.225          WHERE "issue_id" = "issue_id_p"
   1.226          AND "event" = 'periodic';
   1.227        INSERT INTO "direct_interest_snapshot"
   1.228 -        ("issue_id", "event", "member_id", "voting_requested")
   1.229 +        ("issue_id", "event", "member_id")
   1.230          SELECT
   1.231            "issue_id_p"  AS "issue_id",
   1.232            'periodic'    AS "event",
   1.233 -          "member"."id" AS "member_id",
   1.234 -          "interest"."voting_requested"
   1.235 +          "member"."id" AS "member_id"
   1.236          FROM "issue"
   1.237          JOIN "area" ON "issue"."area_id" = "area"."id"
   1.238          JOIN "interest" ON "issue"."id" = "interest"."issue_id"
   1.239 @@ -3001,20 +3026,6 @@
   1.240            FROM "direct_population_snapshot"
   1.241            WHERE "issue_id" = "issue_id_p"
   1.242            AND "event" = 'periodic'
   1.243 -        ),
   1.244 -        "vote_now" = (
   1.245 -          SELECT coalesce(sum("weight"), 0)
   1.246 -          FROM "direct_interest_snapshot"
   1.247 -          WHERE "issue_id" = "issue_id_p"
   1.248 -          AND "event" = 'periodic'
   1.249 -          AND "voting_requested" = TRUE
   1.250 -        ),
   1.251 -        "vote_later" = (
   1.252 -          SELECT coalesce(sum("weight"), 0)
   1.253 -          FROM "direct_interest_snapshot"
   1.254 -          WHERE "issue_id" = "issue_id_p"
   1.255 -          AND "event" = 'periodic'
   1.256 -          AND "voting_requested" = FALSE
   1.257          )
   1.258          WHERE "id" = "issue_id_p";
   1.259        FOR "initiative_id_v" IN
   1.260 @@ -3407,6 +3418,7 @@
   1.261        PERFORM "lock_issue"("issue_id_p");
   1.262        SELECT "area_id" INTO "area_id_v" FROM "issue" WHERE "id" = "issue_id_p";
   1.263        SELECT "unit_id" INTO "unit_id_v" FROM "area"  WHERE "id" = "area_id_v";
   1.264 +      -- consider delegations and auto-reject:
   1.265        DELETE FROM "delegating_voter"
   1.266          WHERE "issue_id" = "issue_id_p";
   1.267        DELETE FROM "direct_voter"
   1.268 @@ -3492,6 +3504,7 @@
   1.269            FROM "initiative" WHERE "issue_id" = "issue_id_p";
   1.270        END LOOP;
   1.271        PERFORM "add_vote_delegations"("issue_id_p");
   1.272 +      -- set voter count and mark issue as being calculated:
   1.273        UPDATE "issue" SET
   1.274          "state"  = 'calculation',
   1.275          "closed" = now(),
   1.276 @@ -3500,18 +3513,23 @@
   1.277            FROM "direct_voter" WHERE "issue_id" = "issue_id_p"
   1.278          )
   1.279          WHERE "id" = "issue_id_p";
   1.280 +      -- calculate "positive_votes", "negative_votes" and "attainable":
   1.281        UPDATE "initiative" SET
   1.282          "positive_votes" = "vote_counts"."positive_votes",
   1.283          "negative_votes" = "vote_counts"."negative_votes",
   1.284 -        "agreed" = CASE WHEN "majority_strict" THEN
   1.285 -          "vote_counts"."positive_votes" * "majority_den" >
   1.286 -          "majority_num" *
   1.287 -          ("vote_counts"."positive_votes"+"vote_counts"."negative_votes")
   1.288 -        ELSE
   1.289 -          "vote_counts"."positive_votes" * "majority_den" >=
   1.290 -          "majority_num" *
   1.291 -          ("vote_counts"."positive_votes"+"vote_counts"."negative_votes")
   1.292 -        END
   1.293 +        "attainable" =
   1.294 +          CASE WHEN "majority_strict" THEN
   1.295 +            "vote_counts"."positive_votes" * "majority_den" >
   1.296 +            "majority_num" *
   1.297 +            ("vote_counts"."positive_votes"+"vote_counts"."negative_votes")
   1.298 +          ELSE
   1.299 +            "vote_counts"."positive_votes" * "majority_den" >=
   1.300 +            "majority_num" *
   1.301 +            ("vote_counts"."positive_votes"+"vote_counts"."negative_votes")
   1.302 +          END
   1.303 +          AND "vote_counts"."positive_votes" >= "policy"."majority_positive"
   1.304 +          AND "issue"."voter_count"-"vote_counts"."negative_votes" >=
   1.305 +              "policy"."majority_non_negative"
   1.306          FROM
   1.307            ( SELECT
   1.308                "initiative"."id" AS "initiative_id",
   1.309 @@ -3544,6 +3562,7 @@
   1.310          WHERE "vote_counts"."initiative_id" = "initiative"."id"
   1.311          AND "issue"."id" = "initiative"."issue_id"
   1.312          AND "policy"."id" = "issue"."policy_id";
   1.313 +      -- materialize battle_view:
   1.314        -- NOTE: "closed" column of issue must be set at this point
   1.315        DELETE FROM "battle" WHERE "issue_id" = "issue_id_p";
   1.316        INSERT INTO "battle" (
   1.317 @@ -3646,26 +3665,23 @@
   1.318    RETURNS VOID
   1.319    LANGUAGE 'plpgsql' VOLATILE AS $$
   1.320      DECLARE
   1.321 -      "dimension_v"     INTEGER;
   1.322 -      "vote_matrix"     INT4[][];  -- absolute votes
   1.323 -      "matrix"          INT8[][];  -- defeat strength / best paths
   1.324 -      "i"               INTEGER;
   1.325 -      "j"               INTEGER;
   1.326 -      "k"               INTEGER;
   1.327 -      "battle_row"      "battle"%ROWTYPE;
   1.328 -      "rank_ary"        INT4[];
   1.329 -      "rank_v"          INT4;
   1.330 -      "done_v"          INTEGER;
   1.331 -      "winners_ary"     INTEGER[];
   1.332 -      "initiative_id_v" "initiative"."id"%TYPE;
   1.333 +      "dimension_v"       INTEGER;
   1.334 +      "vote_matrix"       INT4[][];  -- absolute votes
   1.335 +      "matrix"            INT8[][];  -- defeat strength / best paths
   1.336 +      "i"                 INTEGER;
   1.337 +      "j"                 INTEGER;
   1.338 +      "k"                 INTEGER;
   1.339 +      "battle_row"        "battle"%ROWTYPE;
   1.340 +      "rank_ary"          INT4[];
   1.341 +      "rank_v"            INT4;
   1.342 +      "done_v"            INTEGER;
   1.343 +      "winners_ary"       INTEGER[];
   1.344 +      "initiative_id_v"   "initiative"."id"%TYPE;
   1.345      BEGIN
   1.346        PERFORM NULL FROM "issue" WHERE "id" = "issue_id_p" FOR UPDATE;
   1.347 -      SELECT count(1) INTO "dimension_v" FROM "initiative"
   1.348 -        WHERE "issue_id" = "issue_id_p" AND "agreed";
   1.349 -      IF "dimension_v" = 1 THEN
   1.350 -        UPDATE "initiative" SET "rank" = 1
   1.351 -          WHERE "issue_id" = "issue_id_p" AND "agreed";
   1.352 -      ELSIF "dimension_v" > 1 THEN
   1.353 +      SELECT count(1) INTO "dimension_v"
   1.354 +        FROM "battle_participant" WHERE "issue_id" = "issue_id_p";
   1.355 +      IF "dimension_v" > 1 THEN
   1.356          -- Create "vote_matrix" with absolute number of votes in pairwise
   1.357          -- comparison:
   1.358          "vote_matrix" := "square_matrix_init_string"("dimension_v");  -- TODO: replace by "array_fill" function (PostgreSQL 8.4)
   1.359 @@ -3673,7 +3689,9 @@
   1.360          "j" := 2;
   1.361          FOR "battle_row" IN
   1.362            SELECT * FROM "battle" WHERE "issue_id" = "issue_id_p"
   1.363 -          ORDER BY "winning_initiative_id", "losing_initiative_id"
   1.364 +          ORDER BY
   1.365 +          "winning_initiative_id" NULLS LAST,
   1.366 +          "losing_initiative_id" NULLS LAST
   1.367          LOOP
   1.368            "vote_matrix"["i"]["j"] := "battle_row"."count";
   1.369            IF "j" = "dimension_v" THEN
   1.370 @@ -3777,42 +3795,93 @@
   1.371            EXIT WHEN "done_v" = "dimension_v";
   1.372            "rank_v" := "rank_v" + 1;
   1.373          END LOOP;
   1.374 -        -- write preliminary ranks:
   1.375 +        -- write preliminary results:
   1.376          "i" := 1;
   1.377          FOR "initiative_id_v" IN
   1.378            SELECT "id" FROM "initiative"
   1.379 -          WHERE "issue_id" = "issue_id_p" AND "agreed"
   1.380 +          WHERE "issue_id" = "issue_id_p" AND "admitted"
   1.381            ORDER BY "id"
   1.382          LOOP
   1.383 -          UPDATE "initiative" SET "rank" = "rank_ary"["i"]
   1.384 +          UPDATE "initiative" SET
   1.385 +            "favored"          = "rank_ary"["i"] < "rank_ary"["dimension_v"],
   1.386 +            "unfavored"        = "rank_ary"["i"] > "rank_ary"["dimension_v"],
   1.387 +            "preliminary_rank" = "rank_ary"["i"],
   1.388 +            "disqualified"     = "rank_ary"["i"] >= "rank_ary"["dimension_v"],
   1.389 +            "winner"           = FALSE
   1.390              WHERE "id" = "initiative_id_v";
   1.391            "i" := "i" + 1;
   1.392          END LOOP;
   1.393 -        IF "i" != "dimension_v" + 1 THEN
   1.394 +        IF "i" != "dimension_v" THEN
   1.395            RAISE EXCEPTION 'Wrong winner count (should not happen)';
   1.396          END IF;
   1.397 -        -- straighten ranks (start counting with 1, no equal ranks):
   1.398 +        -- remove possible gap in preliminary ranks:
   1.399 +        IF NOT EXISTS (
   1.400 +          SELECT NULL FROM "initiative"
   1.401 +          WHERE "issue_id" = "issue_id_p"
   1.402 +          AND "favored"=FALSE AND "unfavored"=FALSE
   1.403 +        ) THEN
   1.404 +          UPDATE "initiative" SET "preliminary_rank" = "preliminary_rank" - 1
   1.405 +            WHERE "issue_id" = "issue_id_p" AND "unfavored";
   1.406 +        END IF;
   1.407 +        -- disqualify certain initiatives to enforce a stable result:
   1.408 +         UPDATE "initiative" SET "disqualified" = TRUE
   1.409 +          FROM (
   1.410 +            SELECT "losing_initiative"."id" AS "initiative_id"
   1.411 +            FROM "initiative" "losing_initiative"
   1.412 +            JOIN "initiative" "winning_initiative"
   1.413 +              ON "winning_initiative"."issue_id" = "issue_id_p"
   1.414 +              AND "winning_initiative"."admitted"
   1.415 +            JOIN "battle" "battle_win"
   1.416 +              ON "battle_win"."issue_id" = "issue_id_p"
   1.417 +              AND "battle_win"."winning_initiative_id" = "winning_initiative"."id"
   1.418 +              AND "battle_win"."losing_initiative_id" = "losing_initiative"."id"
   1.419 +            JOIN "battle" "battle_lose"
   1.420 +              ON "battle_lose"."issue_id" = "issue_id_p"
   1.421 +              AND "battle_lose"."losing_initiative_id" = "winning_initiative"."id"
   1.422 +              AND "battle_lose"."winning_initiative_id" = "losing_initiative"."id"
   1.423 +            WHERE "losing_initiative"."issue_id" = "issue_id_p"
   1.424 +            AND "losing_initiative"."admitted"
   1.425 +            AND "winning_initiative"."preliminary_rank" <
   1.426 +                "losing_initiative"."preliminary_rank"
   1.427 +            AND "battle_win"."count" > "battle_lose"."count"
   1.428 +            AND (
   1.429 +              "battle_win"."count" > "winning_initiative"."positive_votes" OR
   1.430 +              "battle_lose"."count" < "losing_initiative"."negative_votes" )
   1.431 +          ) AS "subquery"
   1.432 +          WHERE "id" = "subquery"."initiative_id";
   1.433 +        -- calculate final ranks (start counting with 1, no equal ranks):
   1.434          "rank_v" := 1;
   1.435          FOR "initiative_id_v" IN
   1.436            SELECT "id" FROM "initiative"
   1.437 -          WHERE "issue_id" = "issue_id_p" AND "rank" NOTNULL
   1.438 -          ORDER BY
   1.439 -            "rank",
   1.440 -            "vote_ratio"("positive_votes", "negative_votes") DESC,
   1.441 -            "id"
   1.442 +          WHERE "issue_id" = "issue_id_p" AND "admitted"
   1.443 +          ORDER BY "preliminary_rank", "id"
   1.444          LOOP
   1.445 -          UPDATE "initiative" SET "rank" = "rank_v"
   1.446 +          UPDATE "initiative" SET "final_rank" = "rank_v"
   1.447              WHERE "id" = "initiative_id_v";
   1.448            "rank_v" := "rank_v" + 1;
   1.449          END LOOP;
   1.450 +        -- mark final winner:
   1.451 +        UPDATE "initiative" SET "winner" = TRUE
   1.452 +          FROM (
   1.453 +            SELECT "id" AS "initiative_id"
   1.454 +            FROM "initiative"
   1.455 +            WHERE "issue_id" = "issue_id_p"
   1.456 +            AND "attainable" AND NOT "disqualified"
   1.457 +            ORDER BY "final_rank"
   1.458 +            LIMIT 1
   1.459 +          ) AS "subquery"
   1.460 +         WHERE "id" = "subquery"."initiative_id";
   1.461        END IF;
   1.462 -      -- mark issue as finished
   1.463 +      -- mark issue as finished:
   1.464        UPDATE "issue" SET
   1.465          "state" =
   1.466 -          CASE WHEN "dimension_v" = 0 THEN
   1.467 +          CASE WHEN EXISTS (
   1.468 +            SELECT NULL FROM "initiative"
   1.469 +            WHERE "issue_id" = "issue_id_p" AND "winner"
   1.470 +          ) THEN
   1.471 +            'finished_with_winner'::"issue_state"
   1.472 +          ELSE
   1.473              'finished_without_winner'::"issue_state"
   1.474 -          ELSE
   1.475 -            'finished_with_winner'::"issue_state"
   1.476            END,
   1.477          "ranks_available" = TRUE
   1.478          WHERE "id" = "issue_id_p";
   1.479 @@ -3838,7 +3907,6 @@
   1.480      DECLARE
   1.481        "issue_row"         "issue"%ROWTYPE;
   1.482        "policy_row"        "policy"%ROWTYPE;
   1.483 -      "voting_requested_v" BOOLEAN;
   1.484      BEGIN
   1.485        PERFORM "lock_issue"("issue_id_p");
   1.486        SELECT * INTO "issue_row" FROM "issue" WHERE "id" = "issue_id_p";
   1.487 @@ -3886,21 +3954,8 @@
   1.488            "issue_row"."accepted" NOTNULL AND
   1.489            "issue_row"."half_frozen" ISNULL
   1.490          THEN
   1.491 -          SELECT
   1.492 -            CASE
   1.493 -              WHEN "vote_now" * 2 > "issue_row"."population" THEN
   1.494 -                TRUE
   1.495 -              WHEN "vote_later" * 2 > "issue_row"."population" THEN
   1.496 -                FALSE
   1.497 -              ELSE NULL
   1.498 -            END
   1.499 -            INTO "voting_requested_v"
   1.500 -            FROM "issue" WHERE "id" = "issue_id_p";
   1.501            IF
   1.502 -            "voting_requested_v" OR (
   1.503 -              "voting_requested_v" ISNULL AND
   1.504 -              now() >= "issue_row"."accepted" + "issue_row"."discussion_time"
   1.505 -            )
   1.506 +            now() >= "issue_row"."accepted" + "issue_row"."discussion_time"
   1.507            THEN
   1.508              PERFORM "set_snapshot_event"("issue_id_p", 'half_freeze');
   1.509              -- NOTE: "issue_row" used later
   1.510 @@ -4018,6 +4073,7 @@
   1.511          FROM "issue" WHERE "id" = "issue_id_p"
   1.512          FOR UPDATE;
   1.513        IF "issue_row"."cleaned" ISNULL THEN
   1.514 +        -- TODO: might be broken due to new constraints!
   1.515          UPDATE "issue" SET
   1.516            "closed" = NULL,
   1.517            "ranks_available" = FALSE

Impressum / About Us