Add EP amendment and part votes, hidden from general lists by default

They come from the same responses as final votes. A sitting has over a
hundred, so they are flagged part_vote and only appear on request: the vote
lists have an option for them, feeds, counts and search chunks leave them out.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
simonandClaude Opus 5.5 committed 2026-11-09 13:17:10 +01:00
1 parent bf19e503f9
commit b06f17e587
20 files changed
+176 -47

No files matched your search

+4 -1
View File
@@ -157,7 +157,9 @@
"travel": "Reisen & Treffen",
"social": "Social Media"
},
"factionVoteNote": "Im Nationalrat wird meist per Aufstehen abgestimmt; offiziell protokolliert ist dann nur das Stimmverhalten der Fraktion. Bei namentlichen Abstimmungen zeigen wir die eigene Stimme – und ob sie von der Fraktion abweicht.",
"showPartVotes": "Auch Änderungsanträge und Teilabstimmungen anzeigen",
"hidePartVotes": "Nur Abstimmungen über ganze Texte anzeigen",
"factionVoteNote":"Im Nationalrat wird meist per Aufstehen abgestimmt; offiziell protokolliert ist dann nur das Stimmverhalten der Fraktion. Bei namentlichen Abstimmungen zeigen wir die eigene Stimme – und ob sie von der Fraktion abweicht.",
"roleCorroborated": "mehrfach belegt",
"unverified": "laut Wikidata – nicht amtlich bestätigt",
"accountUnverified": "Laut Wikidata – nicht amtlich bestätigt",
@@ -223,6 +225,7 @@
},
"Votes": {
"title": "Abstimmungen",
"includePartVotes": "Auch Änderungsanträge und Teilabstimmungen (EU-Parlament)",
"accepted": "angenommen",
"rejected": "abgelehnt",
"for": "dafür",
+4 -1
View File
@@ -122,7 +122,9 @@
"travel": "Travel & meetings",
"social": "Social media"
},
"factionVoteNote": "The National Council mostly votes by standing up; then only each faction's vote is recorded. For roll-call votes we show the person's own vote – and whether it differs from their faction.",
"showPartVotes": "Also show amendments and part votes",
"hidePartVotes": "Show votes on whole texts only",
"factionVoteNote":"The National Council mostly votes by standing up; then only each faction's vote is recorded. For roll-call votes we show the person's own vote – and whether it differs from their faction.",
"roleCorroborated": "corroborated",
"unverified": "per Wikidata – not officially confirmed",
"accountUnverified": "According to Wikidata – not officially confirmed",
@@ -188,6 +190,7 @@
},
"Votes": {
"title": "Votes",
"includePartVotes": "Include amendments and part votes (European Parliament)",
"accepted": "accepted",
"rejected": "rejected",
"for": "for",
+7 -3
View File
@@ -10,7 +10,7 @@ const PAGE = 50;
type Props = {
params: Promise<{locale: string}>;
searchParams: Promise<{party?: string; page?: string; roll_call?: string}>;
searchParams: Promise<{party?: string; page?: string; roll_call?: string; parts?: string}>;
};
export async function generateMetadata({params}: Props): Promise<Metadata> {
@@ -25,16 +25,20 @@ export default async function VotesPage({params, searchParams}: Props) {
const sp = await searchParams;
const t = await getTranslations('Votes');
const page = Math.max(1, Number(sp.page) || 1);
const votes = await api.votes({party: sp.party, roll_call: sp.roll_call ? 'true' : undefined, limit: PAGE, offset: (page - 1) * PAGE});
const votes = await api.votes({party: sp.party, roll_call: sp.roll_call ? 'true' : undefined,
include_parts: sp.parts ? 'true' : undefined, limit: PAGE, offset: (page - 1) * PAGE});
return (
<>
<PageHeader title={t('title')} lede={t('intro')} meta={sp.party ? t('filteredParty', {party: sp.party}) : undefined} />
<FilterBar id="votes" active={countActive(sp.roll_call)} resetHref={sp.party ? `/abstimmungen?party=${sp.party}` : '/abstimmungen'}
<FilterBar id="votes" active={countActive(sp.roll_call, sp.parts)} resetHref={sp.party ? `/abstimmungen?party=${sp.party}` : '/abstimmungen'}
hidden={{party: sp.party}}>
<label className="field field--check">
<input type="checkbox" name="roll_call" value="1" defaultChecked={Boolean(sp.roll_call)} /> {t('onlyRollCall')}
</label>
<label className="field field--check">
<input type="checkbox" name="parts" value="1" defaultChecked={Boolean(sp.parts)} /> {t('includePartVotes')}
</label>
</FilterBar>
<VoteLegend />
<VoteList votes={votes} />
@@ -48,7 +48,7 @@ const GLANCE: [string, ClaimType[], Tab][] = [
type Props = {
params: Promise<{locale: string; slug: string}>;
searchParams: Promise<{tab?: string; since?: string; until?: string}>;
searchParams: Promise<{tab?: string; since?: string; until?: string; parts?: string}>;
};
async function loadPerson(slug: string): Promise<Person> {
@@ -70,7 +70,7 @@ export async function generateMetadata({params}: Props): Promise<Metadata> {
export default async function PersonPage({params, searchParams}: Props) {
const {locale, slug} = await params;
setRequestLocale(locale);
const {tab: rawTab, since, until} = await searchParams;
const {tab: rawTab, since, until, parts} = await searchParams;
const tab: Tab = rawTab && rawTab in TABS ? (rawTab as Tab) : 'overview';
const t = await getTranslations('Person');
const tp = await getTranslations('Persons');
@@ -217,7 +217,10 @@ export default async function PersonPage({params, searchParams}: Props) {
{tab === 'votes' && (
<section>
<Notice tone="info">{t('factionVoteNote')}</Notice>
<VoteList votes={await api.personVotes(slug)} />
<p className="small">
<Link href={parts ? href('votes') : `${href('votes')}&parts=1`}>{t(parts ? 'hidePartVotes' : 'showPartVotes')}</Link>
</p>
<VoteList votes={await api.personVotes(slug, Boolean(parts))} />
</section>
)}
+2 -2
View File
@@ -74,9 +74,9 @@ export const api = {
persons: (params: Params) => get<T.PersonListItem[]>('/api/v1/persons', params),
person: (slug: string) => get<T.PersonDetail>(`/api/v1/persons/${enc(slug)}`),
personNetwork: (slug: string) => get<T.Network>(`/api/v1/persons/${enc(slug)}/network`),
personVotes: async (slug: string) => {
personVotes: async (slug: string, includeParts = false) => {
const prefs = await getPrefs();
return get<T.PersonVote[]>(`/api/v1/persons/${enc(slug)}/votes`, {topics: prefs.topics});
return get<T.PersonVote[]>(`/api/v1/persons/${enc(slug)}/votes`, {topics: prefs.topics, include_parts: includeParts || undefined});
},
former: (params: Params) => get<T.FormerPerson[]>('/api/v1/former', params),