Plateforme de decisions collectives pour Duniter/G1. Backend FastAPI async + PostgreSQL (14 tables, 8 routers, 6 services, moteur de vote avec formule d'inertie WoT/Smith/TechComm). Frontend Nuxt 4 + Nuxt UI v3 + Pinia (9 pages, 5 stores). Infrastructure Docker + Woodpecker CI + Traefik. Documentation technique et utilisateur (15 fichiers). Seed : Licence G1, Engagement Forgeron v2.0.0, 4 protocoles de vote. 30 tests unitaires (formules, mode params, vote nuance) -- tous verts. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
85 lines
2.5 KiB
TypeScript
85 lines
2.5 KiB
TypeScript
/**
|
|
* TypeScript mirror of the Python WoT threshold formula.
|
|
*
|
|
* Core formula:
|
|
* Result = C + B^W + (M + (1-M) * (1 - (T/W)^G)) * max(0, T - C)
|
|
*
|
|
* Where:
|
|
* C = constant_base
|
|
* B = base_exponent
|
|
* W = wot_size (corpus of eligible voters)
|
|
* T = total_votes (for + against)
|
|
* M = majority_ratio (majority_pct / 100)
|
|
* G = gradient_exponent
|
|
*
|
|
* Inertia behaviour:
|
|
* - Low participation (T << W) -> near-unanimity required
|
|
* - High participation (T -> W) -> simple majority M suffices
|
|
*
|
|
* Reference test case:
|
|
* wot_size=7224, votes_for=97, votes_against=23 (total=120)
|
|
* params M50 B.1 G.2 => threshold=94, adopted (97 >= 94)
|
|
*/
|
|
export function wotThreshold(
|
|
wotSize: number,
|
|
totalVotes: number,
|
|
majorityPct: number = 50,
|
|
baseExponent: number = 0.1,
|
|
gradientExponent: number = 0.2,
|
|
constantBase: number = 0.0,
|
|
): number {
|
|
if (wotSize <= 0) {
|
|
throw new Error('wotSize doit etre strictement positif')
|
|
}
|
|
if (totalVotes < 0) {
|
|
throw new Error('totalVotes ne peut pas etre negatif')
|
|
}
|
|
if (majorityPct < 0 || majorityPct > 100) {
|
|
throw new Error('majorityPct doit etre entre 0 et 100')
|
|
}
|
|
|
|
const M = majorityPct / 100
|
|
const T = totalVotes
|
|
const W = wotSize
|
|
const C = constantBase
|
|
const B = baseExponent
|
|
const G = gradientExponent
|
|
|
|
// Guard: if no votes, threshold is at least ceil(C + B^W)
|
|
if (T === 0) {
|
|
return Math.ceil(C + Math.pow(B, W))
|
|
}
|
|
|
|
// Core formula
|
|
const participationRatio = T / W
|
|
const inertiaFactor = 1.0 - Math.pow(participationRatio, G)
|
|
const requiredRatio = M + (1.0 - M) * inertiaFactor
|
|
const result = C + Math.pow(B, W) + requiredRatio * Math.max(0, T - C)
|
|
|
|
return Math.ceil(result)
|
|
}
|
|
|
|
/**
|
|
* Compute the Smith criterion threshold.
|
|
*
|
|
* @param smithWotSize - Number of Smith members
|
|
* @param smithExponent - Exponent S for the Smith criterion
|
|
* @returns Minimum number of Smith votes required
|
|
*/
|
|
export function smithThreshold(smithWotSize: number, smithExponent: number): number {
|
|
if (smithWotSize <= 0) return 0
|
|
return Math.ceil(Math.pow(smithWotSize, smithExponent))
|
|
}
|
|
|
|
/**
|
|
* Compute the TechComm criterion threshold.
|
|
*
|
|
* @param techcommSize - Number of TechComm members
|
|
* @param techcommExponent - Exponent T for the TechComm criterion
|
|
* @returns Minimum number of TechComm votes required
|
|
*/
|
|
export function techcommThreshold(techcommSize: number, techcommExponent: number): number {
|
|
if (techcommSize <= 0) return 0
|
|
return Math.ceil(Math.pow(techcommSize, techcommExponent))
|
|
}
|