forked from yvv/decision
WIP lot S : Propositions seed (I1, E1, E2 ouvertes, cran 2), amendement gardé, publication suspendue sans article en vigueur
Travail en cours, sauvegardé à la demande d'orch : le moteur, le serveur et tests/seed_proposals.rs (verts) ; les autres tests et les e2e ne sont pas encore repris. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -3,7 +3,9 @@
|
||||
//! The text replaced is an article's version in force (a seed a minima), or, since
|
||||
//! 2026-11-02, one of its open proposals from the seed, named by its label: the
|
||||
//! rule articles F1.2, N1, N1.1 and O1 rewritten after the rules applied (Yvv).
|
||||
//! The guards are the same.
|
||||
//! The guards are the same. Since the same day, a starting text may be a seed
|
||||
//! proposal instead of a text in force (ADR 0011): where an article has no text
|
||||
//! in force, `Version::InForce` examines its seed proposal (a new base: present).
|
||||
//!
|
||||
//! A seeded document is never instantiated again: once in the database, it is
|
||||
//! runtime data. When the seed's text of an article changes (Yvv, 2026-10-30:
|
||||
@@ -33,21 +35,12 @@ use sqlx::SqliteConnection;
|
||||
/// Which version of the article an amendment replaces.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub enum Version {
|
||||
/// Its text in force, a seed a minima.
|
||||
/// Its starting text: in force (a seed a minima), or its seed proposal (ADR 0011).
|
||||
InForce,
|
||||
/// An open proposal of the seed, at zero votes, by its label.
|
||||
Open(&'static str),
|
||||
}
|
||||
|
||||
impl Version {
|
||||
fn status(self) -> &'static str {
|
||||
match self {
|
||||
Version::InForce => "in_force",
|
||||
Version::Open(_) => "open",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// One starting text to replace, in every document instantiated from a seed.
|
||||
pub struct Amendment {
|
||||
/// Stable identifier: each document's decision is kept under it.
|
||||
@@ -232,7 +225,8 @@ fn target_of<'a>(seeds: &'a [SeedDoc], a: &Amendment) -> Option<(&'a SeedDoc, &'
|
||||
};
|
||||
let version = seed.units.iter().find(|u| u.code == a.unit).and_then(|u| {
|
||||
u.versions.iter().find(|v| match a.version {
|
||||
Version::InForce => v.status == "in_force",
|
||||
// The starting text: in force, or since 2026-11-02 put forward (ADR 0011).
|
||||
Version::InForce => v.status == "in_force" || v.seed_proposal,
|
||||
Version::Open(label) => v.status == "open" && v.label.as_deref() == Some(label),
|
||||
})
|
||||
});
|
||||
@@ -252,7 +246,8 @@ fn target_of<'a>(seeds: &'a [SeedDoc], a: &Amendment) -> Option<(&'a SeedDoc, &'
|
||||
}
|
||||
}
|
||||
|
||||
async fn facts(conn: &mut SqliteConnection, proposal_id: &str, status: &str) -> anyhow::Result<Option<Facts>> {
|
||||
/// What the database says of a version holding `status` (`None`: it does not).
|
||||
pub(crate) async fn facts(conn: &mut SqliteConnection, proposal_id: &str, status: &str) -> anyhow::Result<Option<Facts>> {
|
||||
#[derive(sqlx::FromRow)]
|
||||
struct Row {
|
||||
origin: String,
|
||||
@@ -327,35 +322,39 @@ async fn decide(
|
||||
if decided > 0 {
|
||||
return Ok(None);
|
||||
}
|
||||
let target_id: Option<String> = match a.version {
|
||||
Version::InForce => {
|
||||
sqlx::query_scalar::<_, Option<String>>("SELECT in_force_id FROM units WHERE doc_id = ? AND code = ?")
|
||||
.bind(doc_id)
|
||||
.bind(a.unit)
|
||||
.fetch_optional(&mut *tx)
|
||||
.await?
|
||||
.flatten()
|
||||
}
|
||||
Version::Open(label) => {
|
||||
sqlx::query_scalar(
|
||||
"SELECT p.id FROM proposals p JOIN units u ON u.id = p.unit_id
|
||||
WHERE u.doc_id = ? AND u.code = ? AND p.status = 'open' AND p.origin = 'seed' AND p.label = ?
|
||||
ORDER BY p.rowid LIMIT 1",
|
||||
)
|
||||
let in_force = || {
|
||||
sqlx::query_scalar::<_, Option<String>>("SELECT in_force_id FROM units WHERE doc_id = ? AND code = ?")
|
||||
.bind(doc_id)
|
||||
.bind(a.unit)
|
||||
.bind(label)
|
||||
.fetch_optional(&mut *tx)
|
||||
.await?
|
||||
}
|
||||
};
|
||||
let found = match &target_id {
|
||||
Some(id) => facts(&mut tx, id, a.version.status()).await?,
|
||||
let open = |label: Option<&'static str>| {
|
||||
sqlx::query_scalar::<_, String>(
|
||||
"SELECT p.id FROM proposals p JOIN units u ON u.id = p.unit_id
|
||||
WHERE u.doc_id = ? AND u.code = ? AND p.status = 'open' AND p.origin = 'seed'
|
||||
AND (p.label = ?3 OR (?3 IS NULL AND p.seed_featured = 1))
|
||||
ORDER BY p.rowid LIMIT 1",
|
||||
)
|
||||
.bind(doc_id)
|
||||
.bind(a.unit)
|
||||
.bind(label)
|
||||
};
|
||||
// The version examined, and the status it holds.
|
||||
let examined: Option<(String, &'static str)> = match a.version {
|
||||
Version::InForce => match in_force().fetch_optional(&mut *tx).await?.flatten() {
|
||||
Some(id) => Some((id, "in_force")),
|
||||
// No text in force: the starting text may be a seed proposal (ADR 0011).
|
||||
None => open(None).fetch_optional(&mut *tx).await?.map(|id| (id, "open")),
|
||||
},
|
||||
Version::Open(label) => open(Some(label)).fetch_optional(&mut *tx).await?.map(|id| (id, "open")),
|
||||
};
|
||||
let target_id = examined.as_ref().map(|(id, _)| id.clone());
|
||||
let found = match &examined {
|
||||
Some((id, status)) => facts(&mut tx, id, status).await?,
|
||||
None => None,
|
||||
};
|
||||
let mut v = verdict(found.as_ref(), a);
|
||||
let now = util::now();
|
||||
if let (Verdict::Apply, Some(pid)) = (v, &target_id) {
|
||||
if let (Verdict::Apply, Some((pid, status))) = (v, &examined) {
|
||||
let src = seed.source_lang.as_str();
|
||||
let pick = |m: &util::LangMap| m.get(src).cloned().unwrap_or_default();
|
||||
let (title, body) = (pick(&target.title), pick(&target.body));
|
||||
@@ -369,7 +368,7 @@ async fn decide(
|
||||
.bind(&body)
|
||||
.bind(&now)
|
||||
.bind(pid)
|
||||
.bind(a.version.status())
|
||||
.bind(*status)
|
||||
.execute(&mut *tx)
|
||||
.await?;
|
||||
if done.rows_affected() == 1 {
|
||||
|
||||
Reference in New Issue
Block a user