v3 : modules d'ordre et de titre — moteur, données, serveur (L11)

L'ordre des articles de chaque section (cran 2) et le titre du document
(cran 4) deviennent des modules : des unités à part, dont les versions
sont des propositions votées comme les articles (dépliées, panier, chaîne).

- moteur : ld-engine/src/order.rs (qui est dans l'ordre, ordre proposé,
  complété à la fin quand un article entre ou sort, places redistribuées,
  nouvel article en fin de section) ; pending : un ordre de moins de deux
  articles n'est jamais à voter ;
- données : migration 0007 (units.kind, module_amendments) ; le seed
  déclare ses modules ; module_amend.rs les ajoute une fois, gardé et
  journalisé, aux documents nés sans eux, sans toucher articles ni votes ;
- serveur : modules.rs, order.rs, doc_title.rs (portes pour proposer,
  entrée en vigueur : les articles changent de place, le document change
  de titre, en et es passent « à revoir ») ; publication des modules ;
- reste mineur : la médiane compte les statuts open, in_force, applied,
  et eux seuls.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Yvv
2026-10-31 02:08:44 +01:00
co-authored by Claude Opus 5.5
parent 1a35d0f521
commit 3831ddb1bf
21 changed files with 2249 additions and 115 deletions
+10 -6
View File
@@ -2,8 +2,13 @@
//! of inertia (ADR 0007, `ld_engine::inertia`).
//!
//! What is measured: the proposals of the group (every document, every kind,
//! drafts aside) that hold at least one vote on their current revision, the
//! last one cast within the past year. For each, T is the number of those
//! modules included) that hold at least one vote on their current revision, the
//! last one cast within the past year — those whose status is named here, and
//! only those: `open` (under vote), `in_force` (in force, still under vote) and
//! `applied` (a removal or a change of inertia that passed: a decision taken).
//! Drafts (nobody votes on them) and proposals withdrawn by their author (their
//! votes can no longer be taken back) stay out, as would any status to come
//! until it is named. For each, T is the number of those
//! votes **at the time of measuring** — the same T the formula sees, since the
//! vote is permanent. The median of these T, and how many proposals it rests
//! on, are kept in `settings` under `participation:<group>`.
@@ -37,16 +42,15 @@ pub struct Measure {
pub measured_at: String,
}
/// The numbers of voters of the group's proposals voted on since `since`.
/// Drafts and withdrawn proposals stay out: neither is shown, and votes on a
/// withdrawn proposal can no longer be taken back.
/// The numbers of voters of the group's proposals voted on since `since`, for
/// the statuses counted only (the list is explicit: see the head of this file).
pub async fn sample(state: &AppState, group: &str, since: &str) -> sqlx::Result<Vec<u32>> {
let rows: Vec<i64> = sqlx::query_scalar(
"SELECT COUNT(*) FROM votes v
JOIN proposals p ON p.id = v.proposal_id
JOIN units u ON u.id = p.unit_id
JOIN documents d ON d.id = u.doc_id
WHERE d.group_slug = ? AND p.status NOT IN ('draft', 'withdrawn') AND v.revision = p.revision
WHERE d.group_slug = ? AND p.status IN ('open', 'in_force', 'applied') AND v.revision = p.revision
GROUP BY v.proposal_id
HAVING MAX(v.cast_at) >= ?",
)