/* global React */
/* ============================= MP PROFILE ============================= */
function MpPage({ id, nav }) {
const D = window.PT_DATA;
const mp = D.MPS.find(m => m.id === Number(id)) || D.TOP_MPS[0];
const speeches = D.SPEECHES.filter(s => s.mp.id === mp.id).slice(0, 12);
// Rank among party and overall
const sortedAll = D.TOP_MPS;
const rankAll = sortedAll.findIndex(m => m.id === mp.id) + 1;
const rankParty = D.MPS.filter(m => m.party.id === mp.party.id).sort((a,b)=>b.speechCount-a.speechCount).findIndex(m => m.id === mp.id) + 1;
return (
Member of Parliament · {mp.isActive ? 'Active' : 'Inactive'}
{mp.name}
{mp.party.name}
({mp.party.abbr})
·
{mp.district} District
·
10th Parliament
{mp.speechCount}Speeches
#{rankAll}Overall rank
#{rankParty}In {mp.party.abbr}
{/* Activity + topic breakdown */}
Topic focus
({ label: tb.topic.name, value: tb.count, slug: tb.topic.slug }))}
onClick={(it) => nav('topic', { slug: it.slug })}
/>
AI-assigned topic tags across {mp.speechCount} speeches. A speech can hold up to three topics.
{/* Speeches */}
Recent speeches
{speeches.length} of {mp.speechCount}
{speeches.map(s => (
))}
{/* Related MPs */}
);
}
function ActivityChart({ mp, sittings, nav }) {
const max = Math.max(...mp.activity, 1);
const W = 420, H = 140, PAD = 24;
const step = (W - PAD * 2) / (sittings.length - 1 || 1);
return (
);
}
/* ============================= TOPIC ============================= */
function TopicPage({ slug, nav }) {
const D = window.PT_DATA;
const topic = D.TOPICS.find(t => t.slug === slug) || D.TOPICS[0];
const speeches = D.SPEECHES.filter(s => s.topics.some(t => t.slug === topic.slug)).slice(0, 10);
// top MPs for topic
const mpCount = {};
D.SPEECHES.filter(s => s.topics.some(t => t.slug === topic.slug)).forEach(s => {
mpCount[s.mp.id] = (mpCount[s.mp.id] || 0) + 1;
});
const topMps = Object.entries(mpCount).sort((a,b) => b[1]-a[1]).slice(0, 6).map(([id, c]) => ({ mp: D.MPS.find(m => m.id === Number(id)), count: c }));
// party breakdown from matrix
const partyRow = D.TOPIC_PARTY_MATRIX.find(r => r.topic === topic.slug);
const partyBreakdown = D.PARTIES.map(p => ({
label: p.abbr, value: Math.round((partyRow.values[p.abbr] || 0) * topic.count),
color: p.color
})).filter(x => x.value > 0).sort((a, b) => b.value - a.value);
const idx = D.TOPICS.findIndex(t => t.slug === topic.slug);
const prev = D.TOPICS[(idx - 1 + D.TOPICS.length) % D.TOPICS.length];
const next = D.TOPICS[(idx + 1) % D.TOPICS.length];
return (
Topic · {String(idx + 1).padStart(2,'0')} of 18
{topic.name}
{topic.count} speeches tagged
{topMps.length}+ members speaking
{partyBreakdown.length} parties engaged
{/* Two-col: top MPs + party breakdown */}
Where it's coming from · by party
Share of speeches tagged {topic.short} by the MP's party. Includes multi-tagged speeches.
Recent speeches
{speeches.length} of {topic.count}
{speeches.map(s => )}
);
}
Object.assign(window, { MpPage, TopicPage, ActivityChart });