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:
110
frontend/app/components/votes/FormulaDisplay.vue
Normal file
110
frontend/app/components/votes/FormulaDisplay.vue
Normal file
@@ -0,0 +1,110 @@
|
||||
<script setup lang="ts">
|
||||
/**
|
||||
* Display vote formula with KaTeX rendering.
|
||||
*
|
||||
* Renders the WoT threshold formula using KaTeX when available,
|
||||
* falling back to a code display. Shows parameter values and
|
||||
* optional Smith/TechComm criteria formulas.
|
||||
*/
|
||||
import type { FormulaConfig } from '~/stores/protocols'
|
||||
|
||||
const props = defineProps<{
|
||||
formulaConfig: FormulaConfig
|
||||
showExplanation?: boolean
|
||||
}>()
|
||||
|
||||
const showExplain = ref(props.showExplanation ?? false)
|
||||
|
||||
/**
|
||||
* Render a LaTeX string to HTML using KaTeX, with code fallback.
|
||||
*/
|
||||
function renderFormula(tex: string): string {
|
||||
if (typeof window !== 'undefined' && (window as any).katex) {
|
||||
return (window as any).katex.renderToString(tex, { throwOnError: false, displayMode: true })
|
||||
}
|
||||
return `<code class="text-sm font-mono">${tex}</code>`
|
||||
}
|
||||
|
||||
/** Main threshold formula in LaTeX. */
|
||||
const mainFormulaTeX = 'Seuil = C + B^W + \\left(M + (1-M) \\cdot \\left(1 - \\left(\\frac{T}{W}\\right)^G\\right)\\right) \\cdot \\max(0,\\, T - C)'
|
||||
|
||||
/** Smith criterion formula. */
|
||||
const smithFormulaTeX = computed(() => {
|
||||
if (props.formulaConfig.smith_exponent === null) return null
|
||||
return `Seuil_{Smith} = \\lceil W_{Smith}^{${props.formulaConfig.smith_exponent}} \\rceil`
|
||||
})
|
||||
|
||||
/** TechComm criterion formula. */
|
||||
const techcommFormulaTeX = computed(() => {
|
||||
if (props.formulaConfig.techcomm_exponent === null) return null
|
||||
return `Seuil_{TechComm} = \\lceil W_{TechComm}^{${props.formulaConfig.techcomm_exponent}} \\rceil`
|
||||
})
|
||||
|
||||
const mainFormulaHtml = computed(() => renderFormula(mainFormulaTeX))
|
||||
const smithFormulaHtml = computed(() => smithFormulaTeX.value ? renderFormula(smithFormulaTeX.value) : null)
|
||||
const techcommFormulaHtml = computed(() => techcommFormulaTeX.value ? renderFormula(techcommFormulaTeX.value) : null)
|
||||
|
||||
const parameters = computed(() => [
|
||||
{ label: 'Duree', code: 'D', value: `${props.formulaConfig.duration_days} jours`, description: 'Duree du vote en jours' },
|
||||
{ label: 'Majorite', code: 'M', value: `${props.formulaConfig.majority_pct}%`, description: 'Ratio de majorite cible a haute participation' },
|
||||
{ label: 'Base', code: 'B', value: String(props.formulaConfig.base_exponent), description: 'Exposant de base (B^W tend vers 0 si B < 1)' },
|
||||
{ label: 'Gradient', code: 'G', value: String(props.formulaConfig.gradient_exponent), description: 'Exposant du gradient d\'inertie' },
|
||||
{ label: 'Constante', code: 'C', value: String(props.formulaConfig.constant_base), description: 'Plancher fixe de votes requis' },
|
||||
...(props.formulaConfig.smith_exponent !== null ? [{
|
||||
label: 'Smith', code: 'S', value: String(props.formulaConfig.smith_exponent), description: 'Exposant du critere Smith',
|
||||
}] : []),
|
||||
...(props.formulaConfig.techcomm_exponent !== null ? [{
|
||||
label: 'TechComm', code: 'T', value: String(props.formulaConfig.techcomm_exponent), description: 'Exposant du critere TechComm',
|
||||
}] : []),
|
||||
])
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="space-y-4">
|
||||
<!-- Main formula -->
|
||||
<div class="p-4 bg-gray-50 dark:bg-gray-800 rounded-lg overflow-x-auto">
|
||||
<div v-html="mainFormulaHtml" class="text-center" />
|
||||
</div>
|
||||
|
||||
<!-- Smith criterion -->
|
||||
<div v-if="smithFormulaHtml" class="p-3 bg-blue-50 dark:bg-blue-900/20 rounded-lg overflow-x-auto">
|
||||
<p class="text-xs font-semibold text-blue-600 dark:text-blue-400 mb-2">Critere Smith</p>
|
||||
<div v-html="smithFormulaHtml" class="text-center" />
|
||||
</div>
|
||||
|
||||
<!-- TechComm criterion -->
|
||||
<div v-if="techcommFormulaHtml" class="p-3 bg-purple-50 dark:bg-purple-900/20 rounded-lg overflow-x-auto">
|
||||
<p class="text-xs font-semibold text-purple-600 dark:text-purple-400 mb-2">Critere TechComm</p>
|
||||
<div v-html="techcommFormulaHtml" class="text-center" />
|
||||
</div>
|
||||
|
||||
<!-- Parameters grid -->
|
||||
<div class="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-3">
|
||||
<div
|
||||
v-for="param in parameters"
|
||||
:key="param.code"
|
||||
class="p-3 bg-gray-50 dark:bg-gray-800 rounded-lg"
|
||||
>
|
||||
<div class="flex items-center gap-2 mb-1">
|
||||
<span class="font-mono font-bold text-primary text-sm">{{ param.code }}</span>
|
||||
<span class="text-xs font-medium text-gray-700 dark:text-gray-300">{{ param.label }}</span>
|
||||
</div>
|
||||
<div class="text-lg font-semibold text-gray-900 dark:text-white">{{ param.value }}</div>
|
||||
<p v-if="showExplain" class="text-xs text-gray-500 mt-1">{{ param.description }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Explanation toggle -->
|
||||
<div class="flex justify-end">
|
||||
<UButton
|
||||
variant="ghost"
|
||||
color="neutral"
|
||||
size="xs"
|
||||
:icon="showExplain ? 'i-lucide-eye-off' : 'i-lucide-eye'"
|
||||
@click="showExplain = !showExplain"
|
||||
>
|
||||
{{ showExplain ? 'Masquer les explications' : 'Afficher les explications' }}
|
||||
</UButton>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
Reference in New Issue
Block a user