Sprint 3 : protocoles de vote et boite a outils
Backend: - Sessions de vote : list, close, tally, threshold details, auto-expiration - Protocoles : update, simulate, meta-gouvernance, formulas CRUD - Service vote enrichi : close_session, get_threshold_details, nuanced breakdown - Schemas : ThresholdDetailOut, VoteResultOut, FormulaSimulationRequest/Result - WebSocket broadcast sur chaque vote + fermeture session - 25 nouveaux tests (threshold details, close, nuanced, simulation) Frontend: - 5 composants vote : VoteBinary, VoteNuanced, ThresholdGauge, FormulaDisplay, VoteHistory - 3 composants protocoles : ProtocolPicker, FormulaEditor, ModeParamsDisplay - Simulateur de formules interactif (page /protocols/formulas) - Page detail protocole (/protocols/[id]) - Composable useWebSocket (live updates) - Composable useVoteFormula (calcul client-side reactif) - Integration KaTeX pour rendu LaTeX des formules Documentation: - API reference : 8 nouveaux endpoints documentes - Formules : tables d'inertie, parametres detailles, simulation API - Guide vote : vote binaire/nuance, jauge, historique, simulateur, meta-gouvernance 55 tests passes (+ 1 skipped), 126 fichiers total. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -32,9 +32,52 @@ export interface VotingProtocol {
|
||||
formula_config: FormulaConfig
|
||||
}
|
||||
|
||||
export interface ProtocolCreate {
|
||||
name: string
|
||||
description: string | null
|
||||
vote_type: string
|
||||
formula_config_id: string
|
||||
}
|
||||
|
||||
export interface FormulaCreate {
|
||||
name: string
|
||||
description?: string | null
|
||||
duration_days: number
|
||||
majority_pct: number
|
||||
base_exponent: number
|
||||
gradient_exponent: number
|
||||
constant_base: number
|
||||
smith_exponent?: number | null
|
||||
techcomm_exponent?: number | null
|
||||
nuanced_min_participants?: number | null
|
||||
nuanced_threshold_pct?: number | null
|
||||
}
|
||||
|
||||
export interface SimulateParams {
|
||||
wot_size: number
|
||||
total_votes: number
|
||||
majority_pct: number
|
||||
base_exponent: number
|
||||
gradient_exponent: number
|
||||
constant_base: number
|
||||
smith_wot_size?: number
|
||||
smith_exponent?: number
|
||||
techcomm_size?: number
|
||||
techcomm_exponent?: number
|
||||
}
|
||||
|
||||
export interface SimulateResult {
|
||||
threshold: number
|
||||
smith_threshold: number | null
|
||||
techcomm_threshold: number | null
|
||||
inertia_factor: number
|
||||
required_ratio: number
|
||||
}
|
||||
|
||||
interface ProtocolsState {
|
||||
protocols: VotingProtocol[]
|
||||
formulas: FormulaConfig[]
|
||||
currentProtocol: VotingProtocol | null
|
||||
loading: boolean
|
||||
error: string | null
|
||||
}
|
||||
@@ -43,6 +86,7 @@ export const useProtocolsStore = defineStore('protocols', {
|
||||
state: (): ProtocolsState => ({
|
||||
protocols: [],
|
||||
formulas: [],
|
||||
currentProtocol: null,
|
||||
loading: false,
|
||||
error: null,
|
||||
}),
|
||||
@@ -96,5 +140,89 @@ export const useProtocolsStore = defineStore('protocols', {
|
||||
this.loading = false
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Fetch a single protocol by ID.
|
||||
*/
|
||||
async fetchProtocolById(id: string) {
|
||||
this.loading = true
|
||||
this.error = null
|
||||
|
||||
try {
|
||||
const { $api } = useApi()
|
||||
this.currentProtocol = await $api<VotingProtocol>(`/protocols/${id}`)
|
||||
} catch (err: any) {
|
||||
this.error = err?.data?.detail || err?.message || 'Protocole introuvable'
|
||||
} finally {
|
||||
this.loading = false
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Create a new voting protocol.
|
||||
*/
|
||||
async createProtocol(data: ProtocolCreate) {
|
||||
this.loading = true
|
||||
this.error = null
|
||||
|
||||
try {
|
||||
const { $api } = useApi()
|
||||
const protocol = await $api<VotingProtocol>('/protocols/', {
|
||||
method: 'POST',
|
||||
body: data,
|
||||
})
|
||||
this.protocols.push(protocol)
|
||||
return protocol
|
||||
} catch (err: any) {
|
||||
this.error = err?.data?.detail || err?.message || 'Erreur lors de la creation du protocole'
|
||||
throw err
|
||||
} finally {
|
||||
this.loading = false
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Create a new formula configuration.
|
||||
*/
|
||||
async createFormula(data: FormulaCreate) {
|
||||
this.loading = true
|
||||
this.error = null
|
||||
|
||||
try {
|
||||
const { $api } = useApi()
|
||||
const formula = await $api<FormulaConfig>('/protocols/formulas', {
|
||||
method: 'POST',
|
||||
body: data,
|
||||
})
|
||||
this.formulas.push(formula)
|
||||
return formula
|
||||
} catch (err: any) {
|
||||
this.error = err?.data?.detail || err?.message || 'Erreur lors de la creation de la formule'
|
||||
throw err
|
||||
} finally {
|
||||
this.loading = false
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Simulate formula computation on the backend.
|
||||
*/
|
||||
async simulate(params: SimulateParams) {
|
||||
this.loading = true
|
||||
this.error = null
|
||||
|
||||
try {
|
||||
const { $api } = useApi()
|
||||
return await $api<SimulateResult>('/protocols/simulate', {
|
||||
method: 'POST',
|
||||
body: params,
|
||||
})
|
||||
} catch (err: any) {
|
||||
this.error = err?.data?.detail || err?.message || 'Erreur lors de la simulation'
|
||||
throw err
|
||||
} finally {
|
||||
this.loading = false
|
||||
}
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
@@ -62,6 +62,17 @@ export interface VoteResult {
|
||||
techcomm_pass: boolean
|
||||
}
|
||||
|
||||
export interface ThresholdDetails {
|
||||
wot_threshold: number
|
||||
smith_threshold: number | null
|
||||
techcomm_threshold: number | null
|
||||
wot_pass: boolean
|
||||
smith_pass: boolean | null
|
||||
techcomm_pass: boolean | null
|
||||
inertia_factor: number
|
||||
required_ratio: number
|
||||
}
|
||||
|
||||
export interface VoteCreate {
|
||||
session_id: string
|
||||
vote_value: string
|
||||
@@ -71,10 +82,27 @@ export interface VoteCreate {
|
||||
signed_payload: string
|
||||
}
|
||||
|
||||
export interface VoteSessionCreate {
|
||||
decision_id?: string | null
|
||||
item_version_id?: string | null
|
||||
voting_protocol_id: string
|
||||
wot_size?: number
|
||||
smith_size?: number
|
||||
techcomm_size?: number
|
||||
}
|
||||
|
||||
export interface SessionFilters {
|
||||
status?: string
|
||||
voting_protocol_id?: string
|
||||
decision_id?: string
|
||||
}
|
||||
|
||||
interface VotesState {
|
||||
currentSession: VoteSession | null
|
||||
votes: Vote[]
|
||||
result: VoteResult | null
|
||||
thresholdDetails: ThresholdDetails | null
|
||||
sessions: VoteSession[]
|
||||
loading: boolean
|
||||
error: string | null
|
||||
}
|
||||
@@ -84,6 +112,8 @@ export const useVotesStore = defineStore('votes', {
|
||||
currentSession: null,
|
||||
votes: [],
|
||||
result: null,
|
||||
thresholdDetails: null,
|
||||
sessions: [],
|
||||
loading: false,
|
||||
error: null,
|
||||
}),
|
||||
@@ -166,6 +196,94 @@ export const useVotesStore = defineStore('votes', {
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Fetch threshold details for a session.
|
||||
*/
|
||||
async fetchThresholdDetails(sessionId: string) {
|
||||
this.loading = true
|
||||
this.error = null
|
||||
|
||||
try {
|
||||
const { $api } = useApi()
|
||||
this.thresholdDetails = await $api<ThresholdDetails>(
|
||||
`/votes/sessions/${sessionId}/threshold`,
|
||||
)
|
||||
} catch (err: any) {
|
||||
this.error = err?.data?.detail || err?.message || 'Erreur lors du chargement des details du seuil'
|
||||
} finally {
|
||||
this.loading = false
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Close a vote session.
|
||||
*/
|
||||
async closeSession(sessionId: string) {
|
||||
this.loading = true
|
||||
this.error = null
|
||||
|
||||
try {
|
||||
const { $api } = useApi()
|
||||
const session = await $api<VoteSession>(`/votes/sessions/${sessionId}/close`, {
|
||||
method: 'POST',
|
||||
})
|
||||
this.currentSession = session
|
||||
|
||||
// Refresh result after closing
|
||||
this.result = await $api<VoteResult>(`/votes/sessions/${sessionId}/result`)
|
||||
} catch (err: any) {
|
||||
this.error = err?.data?.detail || err?.message || 'Erreur lors de la fermeture de la session'
|
||||
throw err
|
||||
} finally {
|
||||
this.loading = false
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Fetch a list of vote sessions with optional filters.
|
||||
*/
|
||||
async fetchSessions(filters?: SessionFilters) {
|
||||
this.loading = true
|
||||
this.error = null
|
||||
|
||||
try {
|
||||
const { $api } = useApi()
|
||||
const query: Record<string, string> = {}
|
||||
if (filters?.status) query.status = filters.status
|
||||
if (filters?.voting_protocol_id) query.voting_protocol_id = filters.voting_protocol_id
|
||||
if (filters?.decision_id) query.decision_id = filters.decision_id
|
||||
|
||||
this.sessions = await $api<VoteSession[]>('/votes/sessions', { query })
|
||||
} catch (err: any) {
|
||||
this.error = err?.data?.detail || err?.message || 'Erreur lors du chargement des sessions'
|
||||
} finally {
|
||||
this.loading = false
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Create a new vote session.
|
||||
*/
|
||||
async createSession(data: VoteSessionCreate) {
|
||||
this.loading = true
|
||||
this.error = null
|
||||
|
||||
try {
|
||||
const { $api } = useApi()
|
||||
const session = await $api<VoteSession>('/votes/sessions', {
|
||||
method: 'POST',
|
||||
body: data,
|
||||
})
|
||||
this.sessions.push(session)
|
||||
return session
|
||||
} catch (err: any) {
|
||||
this.error = err?.data?.detail || err?.message || 'Erreur lors de la creation de la session'
|
||||
throw err
|
||||
} finally {
|
||||
this.loading = false
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Clear the current session state.
|
||||
*/
|
||||
@@ -173,6 +291,7 @@ export const useVotesStore = defineStore('votes', {
|
||||
this.currentSession = null
|
||||
this.votes = []
|
||||
this.result = null
|
||||
this.thresholdDetails = null
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user