Composants engagement: GenesisBlock, InertiaSlider, MiniVoteBoard, EngagementCard, DocumentTuto

Backend: genesis_json sur Document, section_tag/inertia_preset/is_permanent_vote sur DocumentItem
Frontend: 5 nouveaux composants pour vue detail document enrichie
- GenesisBlock: sources, outils, synthese forum, contributeurs (depliable)
- InertiaSlider: visualisation inertie 4 niveaux avec params formule G/M
- MiniVoteBoard: tableau vote compact (barre seuil, pour/contre, participation)
- EngagementCard: carte item enrichie integrant vote + inertie + actions
- DocumentTuto: modal pedagogique vote permanent/inertie/seuils
Seed et page [slug] enrichis pour exploiter les nouveaux champs

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Yvv
2026-03-02 07:59:05 +01:00
parent 11e4a4d60a
commit 62808b974d
10 changed files with 2116 additions and 120 deletions

View File

@@ -1,7 +1,7 @@
import uuid
from datetime import datetime
from sqlalchemy import String, Integer, Text, DateTime, ForeignKey, Uuid, func
from sqlalchemy import String, Integer, Text, DateTime, ForeignKey, Uuid, Boolean, func
from sqlalchemy.orm import Mapped, mapped_column, relationship
from app.database import Base
@@ -19,10 +19,11 @@ class Document(Base):
description: Mapped[str | None] = mapped_column(Text)
ipfs_cid: Mapped[str | None] = mapped_column(String(128))
chain_anchor: Mapped[str | None] = mapped_column(String(128))
genesis_json: Mapped[str | None] = mapped_column(Text) # JSON: source files, repos, forum URLs, formula trigger
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
updated_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now(), onupdate=func.now())
items: Mapped[list["DocumentItem"]] = relationship(back_populates="document", cascade="all, delete-orphan", order_by="DocumentItem.position")
items: Mapped[list["DocumentItem"]] = relationship(back_populates="document", cascade="all, delete-orphan", order_by="DocumentItem.sort_order")
class DocumentItem(Base):
@@ -31,11 +32,14 @@ class DocumentItem(Base):
id: Mapped[uuid.UUID] = mapped_column(Uuid, primary_key=True, default=uuid.uuid4)
document_id: Mapped[uuid.UUID] = mapped_column(ForeignKey("documents.id"), nullable=False)
position: Mapped[str] = mapped_column(String(16), nullable=False) # "1", "1.1", "3.2"
item_type: Mapped[str] = mapped_column(String(32), default="clause") # clause, rule, verification, preamble, section
item_type: Mapped[str] = mapped_column(String(32), default="clause") # clause, rule, verification, preamble, section, genesis
title: Mapped[str | None] = mapped_column(String(256))
current_text: Mapped[str] = mapped_column(Text, nullable=False)
voting_protocol_id: Mapped[uuid.UUID | None] = mapped_column(ForeignKey("voting_protocols.id"))
sort_order: Mapped[int] = mapped_column(Integer, default=0)
section_tag: Mapped[str | None] = mapped_column(String(64)) # genesis, fondamental, technique, annexe, formule, inertie, ordonnancement
inertia_preset: Mapped[str] = mapped_column(String(16), default="standard") # low, standard, high, very_high
is_permanent_vote: Mapped[bool] = mapped_column(default=True) # permanent vote vs time-bounded
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
updated_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now(), onupdate=func.now())

View File

@@ -42,6 +42,7 @@ class DocumentOut(BaseModel):
description: str | None = None
ipfs_cid: str | None = None
chain_anchor: str | None = None
genesis_json: str | None = None
created_at: datetime
updated_at: datetime
items_count: int = Field(default=0, description="Number of items in this document")
@@ -54,10 +55,13 @@ class DocumentItemCreate(BaseModel):
"""Payload for creating a document item (clause, rule, etc.)."""
position: str = Field(..., max_length=16, description='Hierarchical position e.g. "1", "1.1", "3.2"')
item_type: str = Field(default="clause", max_length=32, description="clause, rule, verification, preamble, section")
item_type: str = Field(default="clause", max_length=32, description="clause, rule, verification, preamble, section, genesis")
title: str | None = Field(default=None, max_length=256)
current_text: str = Field(..., min_length=1)
voting_protocol_id: UUID | None = None
section_tag: str | None = Field(default=None, max_length=64)
inertia_preset: str = Field(default="standard", max_length=16)
is_permanent_vote: bool = True
class DocumentItemUpdate(BaseModel):
@@ -82,6 +86,9 @@ class DocumentItemOut(BaseModel):
current_text: str
voting_protocol_id: UUID | None = None
sort_order: int
section_tag: str | None = None
inertia_preset: str = "standard"
is_permanent_vote: bool = True
created_at: datetime
updated_at: datetime
@@ -99,6 +106,9 @@ class DocumentItemFullOut(BaseModel):
current_text: str
voting_protocol_id: UUID | None = None
sort_order: int
section_tag: str | None = None
inertia_preset: str = "standard"
is_permanent_vote: bool = True
created_at: datetime
updated_at: datetime
versions: list[ItemVersionOut] = Field(default_factory=list)
@@ -118,6 +128,7 @@ class DocumentFullOut(BaseModel):
description: str | None = None
ipfs_cid: str | None = None
chain_anchor: str | None = None
genesis_json: str | None = None
created_at: datetime
updated_at: datetime
items: list[DocumentItemOut] = Field(default_factory=list)