Module:Legislature/US
Appearance
-- local base = require('Module:Legislature')
local base
-- -- Blindly map local package layout into scope
-- local members = mw.loadData('Module:Legislature/US/lower/members')
-- local seats = mw.loadData('Module:Legislature/US/lower/seats')
-- -- Alias the base functions so formatters can see them
-- local qidToWikilink = base.qidToWikilink
-- local getArgs = base.getArgs
local lang = mw.language.getContentLanguage()
local p = {}
p.name = "Congress"
p.current_congress = 119
p.current_session = 2
-- other stuff probably - if there are any truly joint commissions (as opposed to commissions where the House and Senate appoint the members separately), i guess they might go here. not sure about functions.
-- ============================================================================
-- ROSTER BY STATE
-- Returns: Unsorted dictionary of district data for the given state code
-- ============================================================================
function p.stateToRoster(seats, state)
-- if not state or not seats[state] then return nil end
return seats[state]
end
-- make this compatible with the universal functions in Module:Legislature
function p.flattenDistricts(seats)
local dict = {}
for state_abbr, state_districts in pairs(seats) do
for district_number, data in pairs(state_districts) do
dict[state_abbr .. "-" .. tostring(district_number)] = data
end
end
return dict
end
p.getSeats = p.flattenDistricts -- standard name
-- ============================================================================
-- FORMATTING HELPERS
-- ============================================================================
-- Helper: Format a single member line (e.g., "* [[Jim Jordan]], Ohio, ''Chair''")
local function formatMemberLine(member_info, is_majority, committee_bloc_party)
-- local rep = members[member_info.rep_id]
-- if not rep then return "" end
if member_info.former then
local line = "* " .. member_info.rep_wikilink_and_state
local mdy = lang:formatDate("F j, Y", member_info.end_date)
line = line .. " (until " .. mdy .. ")"
if member_info.note ~= nil then
line = line .. member_info.note
end
local party = member_info.party
if party ~= committee_bloc_party then
line = "<span {{Party shading/" .. party .. "}}>" .. line .. "</span>"
end
return line
end
-- Extract the 2-letter state abbreviation from their seat ID (e.g., "TX" from "TX-15")
local seat_id = base.repIDToSeatID(member_info.rep_id)
-- local state_abbr = "Unknown"
local state_abbr = string.sub(seat_id, 1, 2) -- TODO: this is terrible and should be a function. LLMs should be banned from using string operations
-- local state_name = stateMap[state_abbr] or state_abbr
local line = base.qidToWikilink(member_info.rep_id) .. ", " .. "{{US state abbreviation to name|" .. state_abbr .. "}}"
local rep_data = base.getMember(member_info.rep_id)
local party = rep_data.party
if party ~= committee_bloc_party then
line = "<span {{Party shading/" .. party .. "}}>" .. line
end
line = "* " .. line
-- Append MVP leadership titles based on rank == 1 - "chair", "vice chair", etc., are in the xml file directly
if member_info.rank == 1 and not is_majority then
line = line .. ", ''Ranking Member''"
elseif member_info.leadership ~= nil then
line = line .. ", ''" .. member_info.leadership .. "''"
end
if member_info.start_date ~= nil then
-- Format the date into "Month DD, YYYY"
local mdy = lang:formatDate("F j, Y", member_info.start_date)
line = line .. " (from " .. mdy .. ")"
end
-- Append any custom note
if rep_data.note_committees ~= nil then
line = line .. rep_data.note_committees
end
if member_info.note ~= nil then
line = line .. member_info.note
end
if rep_data.party ~= committee_bloc_party then
line = line .. "</span>"
end
return line
end
-- ============================================================================
-- WIKITEXT FORMATTERS (Callable by Templates)
-- ============================================================================
function p.formatCommitteeRosterTable(frame, base_module)
base = base_module
local args = base.getArgs(frame)
-- -- Tell the base module to load the data paths passed from the template
-- base.initEnvironment(args)
local committee_name = args.committee or args[1]
-- if not committee_name then return "Error: No committee specified." end
-- 1. Call your existing, unmodified data function
local committee_id = base.committeeNameToID(committee_name)
local raw_roster = base.committeeIDToRoster(committee_id)
-- if not raw_roster then return "Error: Committee not found." end
-- local roster_copy = {}
-- for _, member_info in ipairs(raw_roster) do
-- -- Shallow copy the read-only mw.loadData table so it can be mutated
-- local m_copy = {}
-- for k, v in pairs(member_info) do m_copy[k] = v end
-- -- if m_copy.party_status == "majority" then
-- -- table.insert(majority_list, m_copy)
-- -- else
-- -- assert(m_copy.party_status == "minority")
-- -- table.insert(minority_list, m_copy)
-- -- end
-- table.insert(roster_copy, m_copy)
-- end
-- raw_roster = roster_copy
-- 2. Sort the roster array into majority and minority groups
local majority_list = {}
local minority_list = {}
for _, member_info in ipairs(raw_roster) do
local m_copy = {}
for k, v in pairs(member_info) do m_copy[k] = v end
if m_copy.party_status == "majority" then
table.insert(majority_list, m_copy)
-- if member_info.party_status == "majority" then
-- table.insert(majority_list, member_info)
-- elseif member_info.party_status == "minority" then
else
assert(m_copy.party_status == "minority")
table.insert(minority_list, m_copy)
-- assert(member_info.party_status == "minority")
-- table.insert(minority_list, member_info)
end
end
-- Parse and inject ad hoc former members
if args.former_members then
-- Split the string by semicolon to get individual members
local former_arr = mw.text.split(args.former_members, "%s*;%s*")
for _, fm_str in ipairs(former_arr) do
-- if fm_str ~= "" then
-- Split by caret to get the fields
local parts = mw.text.split(fm_str, "%s*^%s*")
local rep_wikilink_and_state = parts[1]
local end_date = parts[2]
-- local mdy = lang:formatDate("F j, Y", end_date)
local party_status = parts[3]
local party = parts[4] or nil
local rank = tonumber(parts[5]) or 9999 -- Assign an artificially high rank to force them to the bottom -- TODO: not the standard that is done but rather something the LLM decided was right
-- Build a record
local member = {
former = true,
rep_wikilink_and_state = rep_wikilink_and_state,
party_status = party_status,
rank = rank,
party = party,
end_date = end_date
-- note = "(until " .. mdy .. ")"
}
if party_status == "majority" then
table.insert(majority_list, member)
elseif party_status == "minority" then
table.insert(minority_list, member)
end
-- end
end
end
-- Sort both lists by their rank integer -- TODO: probably unnecessary since it should already be in order
table.sort(majority_list, function(a, b) return (a.rank or 999) < (b.rank or 999) end) -- note: i actually don't mind the LLM's defaults here too much
table.sort(minority_list, function(a, b) return (a.rank or 999) < (b.rank or 999) end)
-- 3. Dynamically determine column party colors based on the Chair and Ranking Member -- TODO: this could be done by just having a field populated in Legislature/US/lower and Legislature/US/upper that tells us who the majority party is - this data is available in the house clerk's xml document - but technically this is more robust because of the possibility of split control and power-sharing and such
local maj_party = majority_list[1] and base.getMember(majority_list[1].rep_id).party
local min_party = minority_list[1] and base.getMember(minority_list[1].rep_id).party
-- -- Sort both lists by their rank integer
-- table.sort(majority_list, function(a, b) return (a.rank or 9999) < (b.rank or 9999) end) -- i don't hate the LLM's defaults here
-- table.sort(minority_list, function(a, b) return (a.rank or 9999) < (b.rank or 9999) end)
-- 3.5 Process Ex Officio members if this is a subcommittee
local parent_id = base.subcommitteeIDToParentCommitteeID(committee_id)
local ex_maj_line, ex_min_line
if parent_id ~= nil then
local parent_roster = base.committeeIDToRoster(parent_id)
local p_maj_chair, p_min_chair
-- if parent_roster then
for _, m in ipairs(parent_roster) do
if m.rank == 1 then
if m.party_status == "majority" then
p_maj_chair = m
elseif m.party_status == "minority" then
p_min_chair = m
end
end
end
-- end
local function handleExOfficio(parent_top_ranked_member, subcommittee_list, is_majority, column_party)
-- if not parent_top_ranked_member then return "" end
local rep_id = parent_top_ranked_member.rep_id
-- -- Extract the last name from the wikilink target/label -- TODO
-- local label = mw.wikibase.label(rep_id) or mw.wikibase.sitelink(rep_id) or ""
-- local last_name = label:match("(%S+)%s*$") or label
local rep_name = base.getMember(rep_id).rep_name
local title = is_majority and "Chair" or "Ranking Member"
-- local formulaic_note = "<ref >As " .. title .. " of the full committee, Rep. " .. rep_name .. " is entitled to sit as an ''ex officio'' member of all subcommittees, but has been designated a full member of this subcommittee.</ref>"
local ref_name = "exofficio_" .. rep_id
local full_ref = '<ref name="' .. ref_name .. '">As ' .. title .. ' of the full committee, Rep. ' .. rep_name .. ' is entitled to sit as an \'\'ex officio\'\' member of all subcommittees, but has been designated a full member of this subcommittee.</ref>'
local short_ref = '<ref name="' .. ref_name .. '" />'
-- Check if they are already on the subcommittee
for _, m in ipairs(subcommittee_list) do
if m.rep_id == rep_id then
-- m.note = (m.note or "") .. formulaic_note
-- return "* " .. formulaic_note
m.note = (m.note or "") .. full_ref
return "* " .. short_ref
end
end
-- Build a mock member for the standalone Ex Officio line.
-- Overriding rank to 999 prevents formatMemberLine from adding subcommittee MVP titles. -- idk what the llm meant by "MVP". maybe like "vice chair" or "chair".
return formatMemberLine({rep_id = rep_id, rank = 999}, is_majority, column_party)
end
ex_maj_line = handleExOfficio(p_maj_chair, majority_list, true, maj_party)
ex_min_line = handleExOfficio(p_min_chair, minority_list, false, min_party)
end
-- 4. Format the strings for each member
local maj_lines = {}
for _, m in ipairs(majority_list) do
table.insert(maj_lines, formatMemberLine(m, true, maj_party))
end
local min_lines = {}
for _, m in ipairs(minority_list) do
table.insert(min_lines, formatMemberLine(m, false, min_party))
end
-- 5. Construct the final Wikitable string
local out = {}
table.insert(out, '{| class="wikitable"')
-- table.insert(out, '|+ Committee roster{{User:Blippy1998/sandbox/HouseClerkXMLCitation}}')
-- table.insert(out, '! Majority\n! Minority')
table.insert(out, '! Majority{{User:Blippy1998/sandbox/HouseClerkXMLCitation|name=house_clerk_member_data_xml}}\n! Minority<ref name=house_clerk_member_data_xml/>')
table.insert(out, '|-')
-- Majority Column
table.insert(out, '| valign="top" {{Party shading/' .. maj_party .. '}} |')
table.insert(out, table.concat(maj_lines, "\n"))
-- Minority Column
table.insert(out, '| valign="top" {{Party shading/' .. min_party .. '}} |')
table.insert(out, table.concat(min_lines, "\n"))
-- 6. Append Ex Officio Rows (if it's a subcommittee)
if parent_id ~= nil then
table.insert(out, '|-')
table.insert(out, '! colspan="2" | \'\'Ex officio\'\'')
table.insert(out, '|-')
table.insert(out, '| valign="top" {{Party shading/' .. maj_party .. '}} |')
table.insert(out, ex_maj_line)
table.insert(out, '| valign="top" {{Party shading/' .. min_party .. '}} |')
table.insert(out, ex_min_line)
end
table.insert(out, '|}')
local final_wikitext = table.concat(out, "\n")
if type(frame) == 'table' and type(frame.preprocess) == 'function' then
return frame:preprocess(final_wikitext)
end
return final_wikitext
end
function p.committeeMeetingPlace(committee)
local building_codes = {
["CHOB"] = "Cannon House Office Building", -- https://clerk.house.gov/member_info/MemberData_UserGuide.pdf
["LHOB"] = "Longworth House Office Building", -- https://clerk.house.gov/member_info/MemberData_UserGuide.pdf
["RHOB"] = "Rayburn House Office Building", -- https://clerk.house.gov/member_info/MemberData_UserGuide.pdf
["FHOB"] = "Ford House Office Building", -- https://clerk.house.gov/member_info/MemberData_UserGuide.pdf
["OFOB"] = "O'Neill House Office Building", -- https://clerk.house.gov/member_info/MemberData_UserGuide.pdf
["H"] = "The Capitol", -- https://www.govinfo.gov/content/pkg/CDIR-2016-02-12/pdf/CDIR-2016-02-12-HOUSECOMMITTEES.pdf
["C"] = "The Capitol", -- https://www.govinfo.gov/content/pkg/CDIR-2016-02-12/pdf/CDIR-2016-02-12-HOUSECOMMITTEES.pdf
["HB"] = "The Capitol", -- https://clerk.house.gov/member_info/TTD-119.pdf
["HT"] = "The Capitol", -- https://clerk.house.gov/member_info/TTD-119.pdf
["HVC"] = "Capitol Visitor Center", -- https://clerk.house.gov/member_info/TTD-119.pdf
["CVC"] = "Capitol Visitor Center", -- https://clerk.house.gov/member_info/TTD-119.pdf
["DSOB"] = "Dirksen Senate Office Building", -- https://www.senate.gov/visiting/common/generic/building_abbreviation_key.htm
["RSOB"] = "Russell Senate Office Building", -- https://www.senate.gov/visiting/common/generic/building_abbreviation_key.htm
["HSOB"] = "Hart Senate Office Building" -- https://www.senate.gov/visiting/common/generic/building_abbreviation_key.htm
}
return committee.room .. ", " .. building_codes[committee.building_code] .. "<br/>" .. "Washington, D.C. " .. committee.zip
end
return p