Skip to main content
Glama
northernvariables

FedMCP - Federal Parliamentary Information

billGanttUtils.ts7.68 kB
/** * Utility functions for Bill GANTT visualization */ export interface BillGanttData { number: string; session: string; title: string; title_fr: string; status: string; status_fr: string; bill_type: string; bill_type_fr: string; is_government_bill: boolean; originating_chamber: string; originating_chamber_fr: string; introduced_date: string; stage?: string; latest_event?: string; passed_house_first_reading?: string; passed_house_second_reading?: string; passed_house_third_reading?: string; passed_senate_first_reading?: string; passed_senate_second_reading?: string; passed_senate_third_reading?: string; royal_assent_date?: string; referredTo?: Array<{ code: string; name: string }>; sponsor?: { name: string; party: string }; votesAggregate?: { count: number }; hansardDebatesAggregate?: { count: number }; } export interface LegislativeStage { name: string; date?: string; chamber: 'house' | 'senate' | 'committee'; completed: boolean; } export type Swimlane = 'house' | 'committee' | 'senate'; /** * Calculate activity score based on Hansard mentions and votes * Higher score = more active bill */ export function calculateBillActivity(bill: BillGanttData): number { let score = 0; // Hansard debates (+5 points each) const hansardDebates = bill.hansardDebatesAggregate?.count || 0; score += hansardDebates * 5; // Votes (+10 points each) const votes = bill.votesAggregate?.count || 0; score += votes * 10; // Committee referral (+15 points) if (bill.referredTo && bill.referredTo.length > 0) { score += 15; } // Recency bonus (bills introduced recently get higher scores) // +1 point per day since introduction (max 90 days) if (bill.introduced_date) { const introducedDate = new Date(bill.introduced_date); const daysSince = Math.floor( (Date.now() - introducedDate.getTime()) / (1000 * 60 * 60 * 24) ); const recencyBonus = Math.max(0, 90 - daysSince); score += recencyBonus; } // Stage advancement bonus (further along = more activity) const stageBonus = getStageProgress(bill) * 2; score += stageBonus; return score; } /** * Determine which swimlane a bill currently belongs to * Based on latest stage progression */ export function determineSwimlane(bill: BillGanttData): Swimlane { // Check if in committee (check status or referredTo) const statusLower = (bill.status || '').toLowerCase(); if ( statusLower.includes('committee') || statusLower.includes('report stage') ) { return 'committee'; } // Check Senate stages if ( bill.passed_senate_third_reading || bill.passed_senate_second_reading || bill.passed_senate_first_reading || statusLower.includes('senate') ) { return 'senate'; } // Default to House return 'house'; } /** * Calculate horizontal position (0-100%) based on stage progress * within the current swimlane */ export function calculateStagePosition(bill: BillGanttData): number { const swimlane = determineSwimlane(bill); if (swimlane === 'house') { // House progression: Introduced -> 1st -> 2nd -> 3rd (0-100%) if (bill.passed_house_third_reading) return 100; if (bill.passed_house_second_reading) return 66; if (bill.passed_house_first_reading) return 33; if (bill.introduced_date) return 10; return 0; } else if (swimlane === 'senate') { // Senate progression: 1st -> 2nd -> 3rd -> Royal Assent (0-100%) if (bill.royal_assent_date) return 100; if (bill.passed_senate_third_reading) return 75; if (bill.passed_senate_second_reading) return 50; if (bill.passed_senate_first_reading) return 25; return 5; } else { // Committee: centered around 50% const statusLower = (bill.status || '').toLowerCase(); if (statusLower.includes('report stage')) return 75; return 50; } } /** * Get overall stage progress (0-100) across entire legislative process */ export function getStageProgress(bill: BillGanttData): number { let progress = 0; const stages = [ bill.introduced_date, // 0 bill.passed_house_first_reading, // 1 bill.passed_house_second_reading, // 2 bill.passed_house_third_reading, // 3 bill.passed_senate_first_reading, // 4 bill.passed_senate_second_reading, // 5 bill.passed_senate_third_reading, // 6 bill.royal_assent_date, // 7 ]; for (const stage of stages) { if (stage) progress++; } return progress; } /** * Build a timeline of legislative stages for a bill */ export function buildBillTimeline(bill: BillGanttData): LegislativeStage[] { const timeline: LegislativeStage[] = []; // House stages if (bill.introduced_date) { timeline.push({ name: 'introduced', date: bill.introduced_date, chamber: 'house', completed: true, }); } if (bill.passed_house_first_reading) { timeline.push({ name: 'house_first_reading', date: bill.passed_house_first_reading, chamber: 'house', completed: true, }); } if (bill.passed_house_second_reading) { timeline.push({ name: 'house_second_reading', date: bill.passed_house_second_reading, chamber: 'house', completed: true, }); } // Committee stages (if applicable) if (bill.referredTo && bill.referredTo.length > 0) { timeline.push({ name: 'committee_review', chamber: 'committee', completed: !!( bill.passed_house_third_reading || bill.passed_senate_first_reading ), }); } if (bill.passed_house_third_reading) { timeline.push({ name: 'house_third_reading', date: bill.passed_house_third_reading, chamber: 'house', completed: true, }); } // Senate stages if (bill.passed_senate_first_reading) { timeline.push({ name: 'senate_first_reading', date: bill.passed_senate_first_reading, chamber: 'senate', completed: true, }); } if (bill.passed_senate_second_reading) { timeline.push({ name: 'senate_second_reading', date: bill.passed_senate_second_reading, chamber: 'senate', completed: true, }); } if (bill.passed_senate_third_reading) { timeline.push({ name: 'senate_third_reading', date: bill.passed_senate_third_reading, chamber: 'senate', completed: true, }); } if (bill.royal_assent_date) { timeline.push({ name: 'royal_assent', date: bill.royal_assent_date, chamber: 'senate', completed: true, }); } return timeline; } /** * Filter and sort bills for GANTT display */ export function filterOrderPaperBills( bills: BillGanttData[], limit: number = 20 ): BillGanttData[] { // Filter out bills with royal assent (completed bills) const activeBills = bills.filter((bill) => { const statusLower = (bill.status || '').toLowerCase(); return !statusLower.includes('royal assent') && !bill.royal_assent_date; }); // Sort by activity score (descending) const sorted = activeBills.sort((a, b) => { return calculateBillActivity(b) - calculateBillActivity(a); }); // Return top N bills return sorted.slice(0, limit); } /** * Group bills by swimlane for visualization */ export function groupBillsBySwimlane(bills: BillGanttData[]): { house: BillGanttData[]; committee: BillGanttData[]; senate: BillGanttData[]; } { const grouped = { house: [] as BillGanttData[], committee: [] as BillGanttData[], senate: [] as BillGanttData[], }; for (const bill of bills) { const swimlane = determineSwimlane(bill); grouped[swimlane].push(bill); } return grouped; }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/northernvariables/FedMCP'

If you have feedback or need assistance with the MCP directory API, please join our Discord server