web: parity page renders the three-scenario comparison from live data
App.vue fetches the default config, posts it to /api/comparison, and renders ComparisonTable; the Vite dev server proxies /api to the Go server so the browser sees one origin. src/lib/api.ts is the only fetch code and uses exclusively generated types: no shape is defined on the frontend. Scaffold example components removed. Engine fix surfaced by the smoke test: a nil Go slice marshals to JSON null, violating the generated 'educated: number[]' contract; RunScenario now returns an empty slice instead. Verified end to end: curl through the Vite proxy returns the golden 99/70/7. Vitest covers the table rendering; type-check, oxlint, eslint clean. This completes the milestone 2 parity check.
This commit is contained in:
parent
e9f36f509e
commit
c935071bd1
17 changed files with 198 additions and 319 deletions
63
web/src/components/ComparisonTable.vue
Normal file
63
web/src/components/ComparisonTable.vue
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
<script setup lang="ts">
|
||||
import type { ComparisonResponse } from '@/types/api'
|
||||
|
||||
defineProps<{ comparison: ComparisonResponse }>()
|
||||
|
||||
const strategyLabels: Record<string, string> = {
|
||||
none: 'No program',
|
||||
random: 'Educate at random',
|
||||
'most-connected': 'Educate the most connected',
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<table>
|
||||
<caption>
|
||||
Same education budget ({{ comparison.config.numEducated }} of
|
||||
{{ comparison.config.numStudents }} students), different targeting
|
||||
</caption>
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">Strategy</th>
|
||||
<th scope="col">Educated</th>
|
||||
<th scope="col">Reached</th>
|
||||
<th scope="col">Share of school</th>
|
||||
<th scope="col">Rounds</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="result in comparison.results" :key="result.strategy">
|
||||
<th scope="row">{{ strategyLabels[result.strategy] ?? result.strategy }}</th>
|
||||
<td>{{ result.educated.length }}</td>
|
||||
<td>{{ result.numReached }} / {{ comparison.config.numStudents }}</td>
|
||||
<td>{{ Math.round(result.reachedPct) }}%</td>
|
||||
<td>{{ result.numRounds }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
margin-top: 1.5rem;
|
||||
}
|
||||
|
||||
caption {
|
||||
caption-side: top;
|
||||
text-align: left;
|
||||
padding-bottom: 0.75rem;
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
th,
|
||||
td {
|
||||
border: 1px solid var(--color-border);
|
||||
padding: 0.5rem 0.9rem;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
thead th {
|
||||
background: var(--color-background-soft);
|
||||
}
|
||||
</style>
|
||||
Loading…
Add table
Add a link
Reference in a new issue