moontally

    Auditable ranked-ballot counting with Schulze, Ranked Pairs and explicit IRV tie exploration

    Download zip
    Author
    Version
    0.1.0
    License
    MIT
    Last updated
    7 hours ago
    Downloads
    3

    No dependencies

    #MoonTally

    MoonBit 原生排序投票计票与审计库。面向开源社区提案排序、社团方案选择和评审小组的加权偏好比较,提供逐步可复核的计票证据。

    支持有权重、并列名次及不完整选票的两两偏好矩阵;实现 Schulze(winning votes)、Ranked Pairs(明确强度排序规则)、严格排序 IRV。IRV 淘汰平票默认不擅自决定,可指定完整裁决顺序,也可枚举全部合法裁决下的可能赢家,并为每个赢家给出一条可重放的淘汰序列。

    #运行

    安装 MoonBit 工具链和 Node.js 24,在仓库目录执行:

    moon test --target js --deny-warn npm run verify node scripts/cli.mjs examples/panel.json

    Windows 可执行 powershell -File scripts/verify.ps1。CI 另外执行 Wasm、Wasm GC 和 Linux native 测试。Node 仅处理文件和 JSON;选票校验、矩阵、计票和审计证据均由 MoonBit 生成。

    #三个使用场景

    1. panel.json:加权评审小组比较三个方案;A 是两两比较均获胜的 Condorcet 赢家。
    2. club-cycle.json:社区成员形成 A>B、B>C、C>A 的循环偏好;Schulze 保留并列集合,Ranked Pairs 展示被拒绝的成环边及其路径。
    3. irv-tie.json:社团投票的淘汰平票;报告不同合法裁决可能产生的两名赢家,而不是掩盖不确定性。

    npm run examples 将真实结果写入 _build/reports/。独立路径穷举与 IRV 分支模型的验证记录也写入该目录。

    #安装与接口

    moon add forey217/moontally@0.1.0

    导入 forey217/moontally。见 独立消费者、规则契约、JSON 接口 和 验证说明。API 清单在 pkg.generated.mbti。

    #范围

    这是离线计票核心,不提供身份认证、票据收集、匿名性或防篡改服务,也不实现 STV 多席位计票。计票变体和平票规则均明确声明;不声称符合任何地区的法定选举规范。输入权重为正整数,总权重最多十亿;无有效偏好的空选举不产生赢家。

    MIT。原始算法与同类项目检索记录见 contract.md。

    TallyError

    pub(all) suberror TallyError {
    Invalid(String)
    Limit(String)
    } derive(Eq,
    Debug
    )

    TallyError::equal

    fn TallyError::equal(TallyError, TallyError) -> Bool

    TallyError::not_equal

    fn TallyError::not_equal(x : TallyError, y : TallyError) -> Bool

    Ballot

    pub(all) struct Ballot {
    weight : Int
    ranking : Array[Array[String]]
    } derive(Eq,
    Debug
    )

    Ballot::equal

    fn Ballot::equal(Ballot, Ballot) -> Bool

    Ballot::not_equal

    fn Ballot::not_equal(x : Ballot, y : Ballot) -> Bool

    Ballot::to_repr

    Beatpath

    pub(all) struct Beatpath {
    from : String
    to : String
    strength : Int
    path : Array[String]
    } derive(Eq, ToJson,
    Debug
    )

    Beatpath::equal

    fn Beatpath::equal(Beatpath, Beatpath) -> Bool

    Beatpath::not_equal

    fn Beatpath::not_equal(x : Beatpath, y : Beatpath) -> Bool

    Beatpath::to_json

    fn Beatpath::to_json(Beatpath) -> Json

    Beatpath::to_repr

    Election

    pub struct Election {
    // private fields
    }

    Election::candidates

    fn Election::candidates(self : Election) -> Array[String]

    Election::condorcet

    fn Election::condorcet(self : Election) -> Array[String]

    Election::irv

    fn Election::irv(self : Election, tie_order : Array[String]) -> IrvResult raise TallyError

    Without an explicit full tie-order permutation, an elimination tie is returned unresolved.

    Election::irv_possibilities

    fn Election::irv_possibilities(self : Election, max_states : Int) -> IrvPossibilities raise TallyError

    Enumerates all legal single-elimination tie resolutions, memoizing active-candidate masks. A state budget exhaustion returns Limit rather than an incomplete winner set.

    Election::pairwise

    fn Election::pairwise(self : Election) -> Array[Array[Int]]

    Election::ranked_pairs

    fn Election::ranked_pairs(self : Election) -> RankedPairsResult

    Ranked Pairs variant: margin descending, winning votes descending, then lexical IDs. The deterministic equal-strength priority is explicit and is not parallel-universe RP.

    Election::schulze

    fn Election::schulze(self : Election) -> SchulzeResult

    Schulze winning-votes variant. Returns the full unbeaten winner set; no hidden tie-break.

    Election::total_weight

    fn Election::total_weight(self : Election) -> Int

    IrvPossibilities

    pub(all) struct IrvPossibilities {
    possible_winners : Array[String]
    explored_states : Int
    witnesses : Array[TieWitness]
    } derive(Eq, ToJson,
    Debug
    )

    IrvPossibilities::equal

    IrvPossibilities::not_equal

    fn IrvPossibilities::not_equal(x : IrvPossibilities, y : IrvPossibilities) -> Bool

    IrvPossibilities::to_json

    IrvResult

    pub(all) struct IrvResult {
    winners : Array[String]
    rounds : Array[Round]
    needs_tiebreak : Bool
    } derive(Eq, ToJson,
    Debug
    )

    IrvResult::equal

    fn IrvResult::equal(IrvResult, IrvResult) -> Bool

    IrvResult::not_equal

    fn IrvResult::not_equal(x : IrvResult, y : IrvResult) -> Bool

    IrvResult::to_json

    fn IrvResult::to_json(IrvResult) -> Json

    LockedEdge

    pub(all) struct LockedEdge {
    from : String
    to : String
    margin : Int
    winning_votes : Int
    locked : Bool
    cycle_path : Array[String]
    } derive(Eq, ToJson,
    Debug
    )

    LockedEdge::equal

    fn LockedEdge::equal(LockedEdge, LockedEdge) -> Bool

    LockedEdge::not_equal

    fn LockedEdge::not_equal(x : LockedEdge, y : LockedEdge) -> Bool

    LockedEdge::to_json

    fn LockedEdge::to_json(LockedEdge) -> Json

    RankedPairsResult

    pub(all) struct RankedPairsResult {
    winners : Array[String]
    decisions : Array[LockedEdge]
    } derive(Eq, ToJson,
    Debug
    )

    RankedPairsResult::equal

    RankedPairsResult::not_equal

    fn RankedPairsResult::not_equal(x : RankedPairsResult, y : RankedPairsResult) -> Bool

    RankedPairsResult::to_json

    Round

    pub(all) struct Round {
    active : Array[String]
    counts : Map[String, Int]
    continuing_weight : Int
    exhausted_weight : Int
    tied_lowest : Array[String]
    eliminated : String
    } derive(Eq, ToJson,
    Debug
    )

    Round::equal

    fn Round::equal(Round, Round) -> Bool

    Round::not_equal

    fn Round::not_equal(x : Round, y : Round) -> Bool

    Round::to_json

    fn Round::to_json(Round) -> Json

    Round::to_repr

    SchulzeResult

    pub(all) struct SchulzeResult {
    winners : Array[String]
    strengths : Array[Array[Int]]
    witnesses : Array[Beatpath]
    } derive(Eq, ToJson,
    Debug
    )

    SchulzeResult::equal

    SchulzeResult::not_equal

    fn SchulzeResult::not_equal(x : SchulzeResult, y : SchulzeResult) -> Bool

    SchulzeResult::to_json

    TieWitness

    pub(all) struct TieWitness {
    winner : String
    eliminations : Array[String]
    } derive(Eq, ToJson,
    Debug
    )

    TieWitness::equal

    fn TieWitness::equal(TieWitness, TieWitness) -> Bool

    TieWitness::not_equal

    fn TieWitness::not_equal(x : TieWitness, y : TieWitness) -> Bool

    TieWitness::to_json

    fn TieWitness::to_json(TieWitness) -> Json

    election

    fn election(candidates : Array[String], ballots : Array[Ballot]) -> Election raise TallyError

    run_json

    fn run_json(request : String) -> String

    Powered by MoonBit

    Site sourceReport issueModulesSkillsBuild queueStatistics

    © 2026 mooncakes.io