This chapter examines the impact of political and environmental events on legislative output. By evaluating the “honeymoon period” of the First 100 Days, identifying duplicated bill filings, tracking disaster response cycles, charting alignment with executive priority agendas, and auditing PDF availability, we trace the rhythmic patterns of the legislature.
Source
# --- COLLAPSIBLE DATA PREPARATION BLOCK ---
# Load numerical manipulation utilities, datetime formatters, and API queries.
import pandas as pd
import numpy as np
import plotly.express as px
import sys
from pathlib import Path
cwd = Path.cwd().resolve()
sys.path[:0] = [str(cwd), str(cwd.parent)]
from book_utils import api_get, fetch_allSource
# --- COLLAPSIBLE DATA INGESTION BLOCK ---
# Retrieve the latest congress information. We load 5 pages (500 records) to keep calculations
# responsive and avoid hitting hosted API gateway timeouts while running the static builder.
stats, _ = api_get("stats")
latest_congress = max(row["congress"] for row in stats["bills_by_congress"])
documents = pd.DataFrame(fetch_all("documents", congress=latest_congress, max_pages=5))
documents["date_filed"] = pd.to_datetime(documents["date_filed"], errors="coerce")
documents.shape(500, 17)1. The First 100 Days of Congress (Topic 14)¶
Narrative Context: When a new congress session opens, there is a flurry of activity. Lawmakers rush to file priority bills, which represents the “honeymoon period” of the session.
We identify the start of this congress and calculate what percentage of the total workload is filed within the first 100 days.
Source
# --- COLLAPSIBLE TIMELINE FILTER BLOCK ---
# Calculate the start date of the session and filter bills filed in the first 100 days.
start_date = documents["date_filed"].dropna().min()
cutoff_date = start_date + pd.Timedelta(days=100)
early_bills = documents[(documents["date_filed"] >= start_date) & (documents["date_filed"] <= cutoff_date)]
pct_early = len(early_bills) / len(documents) if len(documents) > 0 else 0
# Count topics for early bills
topics_dict = {
"Education": ["school", "education", "teacher", "student", "university"],
"Health": ["health", "hospital", "medical", "patient", "disease"],
"Labor": ["labor", "worker", "employment", "wage", "job"],
"Economy/Tax": ["tax", "business", "trade", "investment", "finance"],
"Infrastructure": ["road", "bridge", "transport", "infrastructure", "highway"]
}
early_titles = early_bills["title"].fillna("").str.lower()
early_counts = {}
for topic, keywords in topics_dict.items():
mask = early_titles.map(lambda x: any(kw in str(x) for kw in keywords))
early_counts[topic] = int(mask.sum())
early_topics_df = pd.DataFrame(list(early_counts.items()), columns=["Topic", "Count"]).sort_values("Count", ascending=False)
# Count topics for the full term (all documents) for comparison
full_titles = documents["title"].fillna("").str.lower()
full_counts = {}
for topic, keywords in topics_dict.items():
mask = full_titles.map(lambda x: any(kw in str(x) for kw in keywords))
full_counts[topic] = int(mask.sum())
full_topics_df = pd.DataFrame(list(full_counts.items()), columns=["Topic", "Count"]).sort_values("Count", ascending=False)
print(f"Congress start date: {start_date.strftime('%Y-%m-%d') if pd.notnull(start_date) else 'N/A'}")
print(f"First 100 days cutoff: {cutoff_date.strftime('%Y-%m-%d') if pd.notnull(cutoff_date) else 'N/A'}")
print(f"Bills filed in first 100 days: {len(early_bills)} ({pct_early:.1%})")Congress start date: 2025-09-01
First 100 days cutoff: 2025-12-10
Bills filed in first 100 days: 499 (99.8%)
Interactive Visualization: Toggle the dropdown menu below to compare the priority topic distributions between the First 100 Days and the Full Term dataset to see how legislative focus shifts over time.
Source
# --- COLLAPSIBLE CHART BUILDING BLOCK ---
# Create an interactive comparison bar chart between early vs full topics.
fig = px.bar(
early_topics_df,
x="Count",
y="Topic",
orientation="h",
title=f"Early Priority Topics in the First 100 Days ({latest_congress}th Congress)",
labels={"Count": "Bills Filed", "Topic": "Thematic Category"}
)
fig.update_layout(
updatemenus=[
dict(
type="dropdown",
direction="down",
showactive=True,
buttons=[
dict(
label="First 100 Days",
method="update",
args=[{"x": [early_topics_df["Count"].tolist()], "y": [early_topics_df["Topic"].tolist()]},
{"title": f"Early Priority Topics in the First 100 Days ({latest_congress}th Congress)"}]
),
dict(
label="Full Term (All Filings)",
method="update",
args=[{"x": [full_topics_df["Count"].tolist()], "y": [full_topics_df["Topic"].tolist()]},
{"title": f"Cumulative Topics Across Full Term ({latest_congress}th Congress)"}]
)
],
x=1.02,
y=1.15
)
]
)
fig2. Refiled and Duplicate Bills (Topic 17)¶
Narrative Context: Why do we find identical bill titles filed multiple times within the same congress? Lawmakers often reintroduce bills that stalled in previous sessions, or multiple representatives file identical bills concurrently to claim authorship credit.
We clean punctuation and compare exact lowercased titles to identify duplicate bill submissions.
Source
# --- COLLAPSIBLE DUPLICATE DETECTION LOGIC ---
# Standardize titles by converting to lowercase and stripping punctuation.
# We then count duplicates to identify identical bill titles.
def clean_title(title):
if not isinstance(title, str):
return ""
clean = "".join(c for c in title.lower() if c.isalnum() or c.isspace())
return " ".join(clean.split())
documents["clean_title"] = documents["title"].map(clean_title)
# Filter out empty clean titles and identify duplicates
duplicate_mask = documents["clean_title"].duplicated(keep=False) & (documents["clean_title"] != "")
duplicates_df = documents[duplicate_mask].sort_values("clean_title")
dup_counts = duplicates_df.groupby("title").size().reset_index(name="Filing Count").sort_values("Filing Count", ascending=False)
dup_counts.head(10)3. Disaster and Calamity Response Cycles (Topic 19)¶
Narrative Context: The Philippines is hit by multiple typhoons every year. Does the timing of natural disasters trigger immediate legislative action?
We search for keywords like typhoon, calamity, disaster, flood, relief, and rehabilitation to plot the monthly pace of disaster-related bill filings.
Source
# --- COLLAPSIBLE TIMELINE PARSING BLOCK ---
# Isolate disaster-related bills and aggregate counts by month.
# We also compute the cumulative sum to show the growth of disaster bills.
disaster_keywords = ["typhoon", "calamity", "disaster", "flood", "relief", "rehabilitation", "emergency"]
def is_disaster_related(title):
t_lower = str(title).lower()
return any(kw in t_lower for kw in disaster_keywords)
documents["is_disaster"] = documents["title"].map(is_disaster_related)
disaster_bills = documents[documents["is_disaster"]].copy()
disaster_bills["month"] = disaster_bills["date_filed"].dt.to_period("M").dt.to_timestamp()
disaster_monthly = disaster_bills.groupby("month").size().reset_index(name="Disaster Bills")
disaster_monthly = disaster_monthly.sort_values("month")
disaster_monthly["Cumulative Disaster Bills"] = disaster_monthly["Disaster Bills"].cumsum()
disaster_monthlyInteractive Visualization: Switch between Monthly Counts to spot seasonal spikes, and Cumulative Totals to see how disaster-related bills accumulate over the course of the session.
Source
# --- COLLAPSIBLE CHART BUILDING BLOCK ---
# Timeline line chart with interactive y-axis mapping toggles.
fig = px.line(
disaster_monthly,
x="month",
y="Disaster Bills",
markers=True,
title=f"Disaster and Calamity Legislation Monthly Timeline ({latest_congress}th Congress)",
labels={"month": "Month filed", "Disaster Bills": "Disaster-related bills"}
)
fig.update_layout(
updatemenus=[
dict(
type="buttons",
direction="right",
buttons=[
dict(
label="Show Monthly Filings",
method="update",
args=[{"y": [disaster_monthly["Disaster Bills"].tolist()]},
{"title": f"Monthly Disaster Filings ({latest_congress}th Congress)",
"yaxis": {"title": "Monthly bills filed"}}]
),
dict(
label="Show Cumulative Total",
method="update",
args=[{"y": [disaster_monthly["Cumulative Disaster Bills"].tolist()]},
{"title": f"Cumulative Disaster Filings Over Term ({latest_congress}th Congress)",
"yaxis": {"title": "Cumulative bills filed"}}]
)
],
x=0.5,
xanchor="center",
y=1.15
)
]
)
fig4. Executive Priority Alignment (Topic 18)¶
Narrative Context: The President outlines key priority programs during the annual State of the Nation Address (SONA). We track how many bills mention these priority keywords to analyze alignment between the executive and legislative branches.
Tax Reform:
tax reform,train law,create act.Constitutional Reform:
charter change,cha-cha.Sovereign Wealth Fund:
maharlika.Universal Healthcare:
universal health care.Disaster Management:
department of disaster,disaster resilience.
Source
# --- COLLAPSIBLE KEYWORD MATCHING BLOCK ---
# Map titles to administration priority sectors.
admin_keywords = {
"Tax Reform": ["tax reform", "train law", "corporate recovery", "create act"],
"Constitutional Reform": ["charter change", "cha-cha", "constituent assembly", "constitutional convention"],
"Sovereign Fund / Investments": ["maharlika", "sovereign wealth fund", "investment fund"],
"Universal Healthcare": ["universal health care", "healthcare", "philhealth"],
"Disaster Management": ["department of disaster", "disaster resilience", "ddm"]
}
priority_rows = []
for label, keywords in admin_keywords.items():
mask = documents["title"].fillna("").str.lower().map(lambda x: any(kw in str(x) for kw in keywords))
count = int(mask.sum())
priority_rows.append({"Priority Program": label, "Bills Filed": count})
priority_df = pd.DataFrame(priority_rows).sort_values("Bills Filed", ascending=False)Source
# --- COLLAPSIBLE CHART BUILDING BLOCK ---
# Horizontal bar chart of priority keyword matches.
px.bar(
priority_df,
x="Bills Filed",
y="Priority Program",
orientation="h",
title=f"Administration Priority Keyword Matches ({latest_congress}th Congress)",
labels={"Bills Filed": "Number of matching bills", "Priority Program": "Priority Area"}
)5. Urgency / Document Processing Audit (Topic 24)¶
Narrative Context: Although explicit “Certified Urgent” flags are not present in the default database schema, we can look at the availability of scanned PDF sources (download_url_sources) to understand archive priority.
We compare PDF availability rates between National and Local bills to see if higher-priority national bills are scanned and archived faster.
Source
# --- COLLAPSIBLE PROCESSING AUDIT LOGIC ---
# Compute PDF link presence and group by bill scope.
# We also compute the total volume scanned to offer a double view.
documents["has_download_url"] = documents["download_url_sources"].map(lambda x: len(x) > 0 if isinstance(x, list) else False)
documents["scope_label"] = documents["scope"].fillna("Unknown").str.title()
download_audit = documents.groupby("scope_label").agg(
rate=("has_download_url", "mean"),
total_docs=("has_download_url", "count")
).reset_index()
download_audit.columns = ["Scope", "Availability Rate", "Total Bills"]
download_auditInteractive Visualization: Toggle the dropdown menu to switch the y-axis between PDF Ingestion Rate (availability percentage) and Total Bill Volume to evaluate work priority against raw volume.
Source
# --- COLLAPSIBLE CHART BUILDING BLOCK ---
# Interactive chart comparing scanned PDF counts vs rates by bill scope.
fig = px.bar(
download_audit,
x="Scope",
y="Availability Rate",
title="Scanned PDF Source Code Availability by Bill Scope",
labels={"Availability Rate": "Availability Rate", "Scope": "Bill Scope"}
)
fig.update_layout(yaxis_tickformat=".0%")
fig.update_layout(
updatemenus=[
dict(
type="dropdown",
direction="down",
showactive=True,
buttons=[
dict(
label="Show Availability Percentage",
method="update",
args=[{"y": [download_audit["Availability Rate"].tolist()]},
{"title": "Scanned PDF Availability Rate by Bill Scope",
"yaxis": {"title": "Availability Rate (Percentage)", "tickformat": ".0%"}}]
),
dict(
label="Show Total Bill Volume",
method="update",
args=[{"y": [download_audit["Total Bills"].tolist()]},
{"title": "Total Bills Registered by Scope",
"yaxis": {"title": "Number of Bills", "tickformat": ""}}]
)
],
x=1.02,
y=1.15
)
]
)
figQuestions to Investigate¶
Does the volume of duplicate/refiled bills increase in the months leading up to congressional recess?
Do spikes in disaster legislation result in structural department reforms or short-term emergency funding?
Does the scanned PDF availability gap between national and local bills narrow over time?