v2 : conception ultracode + moteur de formules porté en TS
- Blueprint définitif (docs/dev/BLUEPRINT-V2.md + JSON) : panel 4 visions + 3 juges, corpus doctrinal (Mon nom est personne, Une économie du don, SejeteralO), 4 critiques d'intégration, 3 vérificateurs, réparations - Corpus d'intention + lectures brutes archivés - Moteur porté Python→TS pur : threshold (inertie WoT, Smith, ComTech), nuancé 6 niveaux, DSL modeParams — 45 tests vitest, parité croisée vérifiée (Forgeron W=7224 T=120 ⇒ 94) - Contrat de domaine v2 (types/domain.ts) + lexique UI (lexicon.ts) - vitest + idb-keyval en dépendances Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,109 @@
|
||||
/**
|
||||
* Six-level nuanced vote evaluation.
|
||||
*
|
||||
* Exact TypeScript port of backend/app/engine/nuanced_vote.py
|
||||
* (Python is the reference implementation).
|
||||
*
|
||||
* Levels:
|
||||
* 0 - CONTRE
|
||||
* 1 - PAS DU TOUT
|
||||
* 2 - PAS D'ACCORD
|
||||
* 3 - NEUTRE
|
||||
* 4 - D'ACCORD
|
||||
* 5 - TOUT A FAIT
|
||||
*
|
||||
* Adoption rule:
|
||||
* The sum of votes at levels 3 + 4 + 5 must be >= thresholdPct% of total votes.
|
||||
* A minimum number of participants is also required.
|
||||
*/
|
||||
|
||||
export const LEVEL_LABELS: Record<number, string> = {
|
||||
0: 'CONTRE',
|
||||
1: 'PAS DU TOUT',
|
||||
2: "PAS D'ACCORD",
|
||||
3: 'NEUTRE',
|
||||
4: "D'ACCORD",
|
||||
5: 'TOUT A FAIT',
|
||||
}
|
||||
|
||||
export const NUM_LEVELS = 6
|
||||
|
||||
/** Result shape mirrors the Python dict returned by evaluate_nuanced(). */
|
||||
export interface NuancedResult {
|
||||
total: number
|
||||
per_level_counts: Record<number, number>
|
||||
positive_count: number
|
||||
positive_pct: number
|
||||
threshold_met: boolean
|
||||
min_participants_met: boolean
|
||||
adopted: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* Round to 2 decimals with ties-to-even, mirroring Python's round(x, 2).
|
||||
*/
|
||||
function round2(value: number): number {
|
||||
const scaled = value * 100
|
||||
const floor = Math.floor(scaled)
|
||||
const diff = scaled - floor
|
||||
let rounded: number
|
||||
if (diff > 0.5) {
|
||||
rounded = floor + 1
|
||||
} else if (diff < 0.5) {
|
||||
rounded = floor
|
||||
} else {
|
||||
rounded = floor % 2 === 0 ? floor : floor + 1
|
||||
}
|
||||
return rounded / 100
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluate a nuanced vote from a list of individual vote levels.
|
||||
*
|
||||
* @param votes - List of vote levels (each 0-5), one entry per voter
|
||||
* @param thresholdPct - Minimum percentage of positive votes (levels 3-5) for adoption
|
||||
* @param minParticipants - Minimum number of participants required for validity
|
||||
* @returns Aggregated result (counts, percentage, adoption flags)
|
||||
* @throws Error if any vote value is outside the 0-5 integer range
|
||||
*/
|
||||
export function nuancedResult(
|
||||
votes: number[],
|
||||
thresholdPct: number = 80,
|
||||
minParticipants: number = 59,
|
||||
): NuancedResult {
|
||||
// Validate vote levels (integer check added: JS has no KeyError safety net)
|
||||
for (const v of votes) {
|
||||
if (!Number.isInteger(v) || v < 0 || v > 5) {
|
||||
throw new Error(`Niveau de vote invalide : ${v}. Les niveaux valides sont 0-5.`)
|
||||
}
|
||||
}
|
||||
|
||||
const total = votes.length
|
||||
|
||||
const perLevelCounts: Record<number, number> = {}
|
||||
for (let level = 0; level < NUM_LEVELS; level++) {
|
||||
perLevelCounts[level] = 0
|
||||
}
|
||||
for (const v of votes) {
|
||||
perLevelCounts[v] = (perLevelCounts[v] ?? 0) + 1
|
||||
}
|
||||
|
||||
// Positive = levels 3 (NEUTRE), 4 (D'ACCORD), 5 (TOUT A FAIT)
|
||||
const positiveCount = (perLevelCounts[3] ?? 0) + (perLevelCounts[4] ?? 0) + (perLevelCounts[5] ?? 0)
|
||||
|
||||
const positivePct = total > 0 ? (positiveCount / total) * 100.0 : 0.0
|
||||
|
||||
const thresholdMet = positivePct >= thresholdPct
|
||||
const minParticipantsMet = total >= minParticipants
|
||||
const adopted = thresholdMet && minParticipantsMet
|
||||
|
||||
return {
|
||||
total,
|
||||
per_level_counts: perLevelCounts,
|
||||
positive_count: positiveCount,
|
||||
positive_pct: round2(positivePct),
|
||||
threshold_met: thresholdMet,
|
||||
min_participants_met: minParticipantsMet,
|
||||
adopted,
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user