forked from yvv/decision
v3 : inertie à cinq crans — moteur, données, serveur (L10)
Moteur (ld-engine/inertia.rs) : cinq crans, M_k = 50, 50, 55, 60, 66 % ; s_k = 60, 67, 75, 83, 90 % de « pour » au-delà de C à la participation habituelle p̃ ; G_k = ln(1 − (s_k − M_k)/(1 − M_k)) / ln(p̃). p̃ = médiane des votants des propositions du groupe (au moins 5, votées dans l'année), sinon la référence 2C/W ; bornée à [1 %, 50 %]. Le changement d'inertie hérite du cran de l'article (remplace « inertie + »). Un seuil qui tombe sur un entier n'y gagne plus une voix par arrondi. Données : migration 0005 (les quatre mots deviennent 2 à 5, gardés tels quels dans *_legacy ; adoptions intactes ; journaux inertia_changes et inertia_amendments). Seed : 3 partout, 4 pour E1 et E2. Amendement d'inertie gardé (E1, E2 : 3 → 4, seulement si aucun vote n'a changé leur inertie ; une proposition d'inertie ouverte reste ouverte, votée au nouveau cran), journalisé, une seule fois, republié. Serveur : participation mesurée une fois par jour (settings), échelle dans les seuils, le balayage et l'adoption ; crans, M, G, s_k, p̃ et sa source servis au client. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,8 +1,9 @@
|
||||
//! Entering into force — automatic, as soon as a proposal reaches its
|
||||
//! threshold (Yvv, 2026-10-21: « Dès qu'une proposition atteint le score, elle
|
||||
//! remonte en vigueur »). The human gesture is the vote itself; the engine
|
||||
//! only counts. Every entry is recorded with its numbers and queued for
|
||||
//! publication to the texts repository.
|
||||
//! only counts. Every entry is recorded with its numbers — the level of
|
||||
//! inertia, M, G, s_k and the usual participation it was judged by — and
|
||||
//! queued for publication to the texts repository.
|
||||
|
||||
use crate::AppState;
|
||||
use crate::docs::{self, DocRow, DocState};
|
||||
@@ -66,16 +67,16 @@ pub async fn run(state: &AppState, doc_id: &str) -> AppResult<Vec<Entered>> {
|
||||
let st = docs::load_state(state, doc_id).await?;
|
||||
confirm_seeds(state, &st).await?;
|
||||
let units = st.unit_states();
|
||||
let winners = compose::adoptions(&units, &st.proposal_states(), st.electorate, st.doc.vote_floor)
|
||||
let scale = st.scale();
|
||||
let winners = compose::adoptions(&units, &st.proposal_states(), &scale)
|
||||
.map_err(|_| crate::error::AppError::Invalid("electorate"))?;
|
||||
let mut entered = Vec::new();
|
||||
for id in winners {
|
||||
let Some(p) = st.proposals.iter().find(|p| p.id == id) else { continue };
|
||||
let Some(unit) = st.units.iter().find(|u| u.id == p.unit_id) else { continue };
|
||||
let us = units.iter().find(|u| u.id == unit.id);
|
||||
let tally = st.tally(&p.id);
|
||||
let eval = st.evaluation(unit, p);
|
||||
let params = us.map(|u| compose::params_for(u, p.kind(), st.doc.vote_floor));
|
||||
let params = kept_params(&st, unit, p);
|
||||
let now = util::now();
|
||||
let mut tx = state.db.begin().await?;
|
||||
// The text that enters is the text that was counted: a revision that landed
|
||||
@@ -140,10 +141,22 @@ pub async fn run(state: &AppState, doc_id: &str) -> AppResult<Vec<Entered>> {
|
||||
.execute(&mut *tx)
|
||||
.await?;
|
||||
sqlx::query("UPDATE units SET inertia = ? WHERE id = ?")
|
||||
.bind(target.as_str())
|
||||
.bind(target.as_db())
|
||||
.bind(&unit.id)
|
||||
.execute(&mut *tx)
|
||||
.await?;
|
||||
sqlx::query(
|
||||
"INSERT INTO inertia_changes (unit_id, doc_id, from_level, to_level, cause, proposal_id, at)
|
||||
VALUES (?, ?, ?, ?, 'vote', ?, ?)",
|
||||
)
|
||||
.bind(&unit.id)
|
||||
.bind(&st.doc.id)
|
||||
.bind(docs::inertia_of(unit).as_db())
|
||||
.bind(target.as_db())
|
||||
.bind(&p.id)
|
||||
.bind(&now)
|
||||
.execute(&mut *tx)
|
||||
.await?;
|
||||
"inertia"
|
||||
}
|
||||
};
|
||||
@@ -159,7 +172,7 @@ pub async fn run(state: &AppState, doc_id: &str) -> AppResult<Vec<Entered>> {
|
||||
.bind(i64::from(tally.against))
|
||||
.bind(i64::from(st.electorate))
|
||||
.bind(eval.map_or(0, |e| i64::from(e.required)))
|
||||
.bind(serde_json::to_string(¶ms).unwrap_or_default())
|
||||
.bind(params.to_string())
|
||||
.bind(&now)
|
||||
.execute(&mut *tx)
|
||||
.await?;
|
||||
@@ -174,12 +187,30 @@ pub async fn run(state: &AppState, doc_id: &str) -> AppResult<Vec<Entered>> {
|
||||
Ok(entered)
|
||||
}
|
||||
|
||||
/// What an adoption keeps of the formula it was judged by. The four letters
|
||||
/// stay where the rows of before have them (`majority_pct`, `gradient`, `base`,
|
||||
/// `constant`: read as they were written); the level and the scale join them.
|
||||
fn kept_params(st: &DocState, unit: &docs::UnitRow, p: &docs::ProposalRow) -> serde_json::Value {
|
||||
let level = st.voting_level(unit, p);
|
||||
let f = st.scale().params(level);
|
||||
serde_json::json!({
|
||||
"majority_pct": f.majority_pct, "gradient": f.gradient, "base": f.base, "constant": f.constant,
|
||||
"level": level, "share_pct": level.share_pct(),
|
||||
"participation": st.participation.ratio, "participation_source": st.participation.source,
|
||||
"participation_sample": st.participation.sample, "participation_median": st.participation.median,
|
||||
})
|
||||
}
|
||||
|
||||
/// The electorate changes (the web of trust grows or shrinks): every few
|
||||
/// minutes, every document is passed through the engine again.
|
||||
/// minutes, every document is passed through the engine again — after the
|
||||
/// usual participation of each group is measured again, once a day.
|
||||
pub async fn sweeper(state: AppState) {
|
||||
let mut tick = tokio::time::interval(Duration::from_secs(300));
|
||||
loop {
|
||||
tick.tick().await;
|
||||
if let Err(e) = crate::participation::refresh(&state, false).await {
|
||||
tracing::warn!(error = %e, "usual participation");
|
||||
}
|
||||
let ids: Vec<String> = match sqlx::query_scalar("SELECT id FROM documents").fetch_all(&state.db).await {
|
||||
Ok(ids) => ids,
|
||||
Err(e) => {
|
||||
|
||||
Reference in New Issue
Block a user