Files
decision/frontend/app/stores/protocols.ts
Yvv cede2a585f 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>
2026-02-28 13:29:31 +01:00

229 lines
5.6 KiB
TypeScript

/**
* Protocols store: voting protocols and formula configurations.
*
* Maps to the backend /api/v1/protocols endpoints.
*/
export interface FormulaConfig {
id: string
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
created_at: string
}
export interface VotingProtocol {
id: string
name: string
description: string | null
vote_type: string
formula_config_id: string
mode_params: string | null
is_meta_governed: boolean
created_at: string
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
}
export const useProtocolsStore = defineStore('protocols', {
state: (): ProtocolsState => ({
protocols: [],
formulas: [],
currentProtocol: null,
loading: false,
error: null,
}),
getters: {
binaryProtocols: (state): VotingProtocol[] => {
return state.protocols.filter(p => p.vote_type === 'binary')
},
nuancedProtocols: (state): VotingProtocol[] => {
return state.protocols.filter(p => p.vote_type === 'nuanced')
},
metaGovernedProtocols: (state): VotingProtocol[] => {
return state.protocols.filter(p => p.is_meta_governed)
},
},
actions: {
/**
* Fetch all voting protocols with their formula configurations.
*/
async fetchProtocols(params?: { vote_type?: string }) {
this.loading = true
this.error = null
try {
const { $api } = useApi()
const query: Record<string, string> = {}
if (params?.vote_type) query.vote_type = params.vote_type
this.protocols = await $api<VotingProtocol[]>('/protocols/', { query })
} catch (err: any) {
this.error = err?.data?.detail || err?.message || 'Erreur lors du chargement des protocoles'
} finally {
this.loading = false
}
},
/**
* Fetch all formula configurations.
*/
async fetchFormulas() {
this.loading = true
this.error = null
try {
const { $api } = useApi()
this.formulas = await $api<FormulaConfig[]>('/protocols/formulas')
} catch (err: any) {
this.error = err?.data?.detail || err?.message || 'Erreur lors du chargement des formules'
} finally {
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
}
},
},
})