@@ -13,9 +13,11 @@ import {
1313 OcpfMemberMappingFlagsEntry
1414} from "./types"
1515
16- // TODO: Convert to onCall once tested. Use checkAuth(context) + checkAdmin(context)
17- // from ../common.ts — both utilities handle admin role checking automatically.
1816export const matchOcpfMembers = functions . https . onRequest ( async ( req , res ) => {
17+ if ( req . method !== "POST" ) {
18+ res . status ( 405 ) . send ( "Method Not Allowed. Use POST." )
19+ return
20+ }
1921 if ( process . env . FUNCTIONS_EMULATOR !== "true" ) {
2022 const authHeader = req . headers . authorization
2123 if ( ! authHeader ?. startsWith ( "Bearer " ) ) {
@@ -50,39 +52,51 @@ export const matchOcpfMembers = functions.https.onRequest(async (req, res) => {
5052
5153 if ( ! branch || ( branch !== "Senate" && branch !== "House" ) ) continue
5254
53- const lastNameMatches = filers . filter (
55+ const lastNameAndBranchMatches = filers . filter (
5456 f =>
5557 f . lastName . toLowerCase ( ) === lastName . toLowerCase ( ) &&
5658 f . officeSought === branch
5759 )
5860
5961 // Narrow by first name: compare first word of each (e.g. "Daniel" from "Daniel J. Ryan"
60- // vs "Daniel" from "Daniel Joseph"). Falls back to all last- name matches if none align .
62+ // vs "Daniel" from "Daniel Joseph"). If none align, falls back to matches by last name and branch .
6163 const mapleFirstName = member . Name . trim ( ) . split ( / \s + / ) [ 0 ] . toLowerCase ( )
62- const firstNameMatches = lastNameMatches . filter (
64+ const firstNameMatches = lastNameAndBranchMatches . filter (
6365 f => f . firstName . trim ( ) . split ( / \s + / ) [ 0 ] . toLowerCase ( ) === mapleFirstName
6466 )
6567 const candidates =
66- firstNameMatches . length > 0 ? firstNameMatches : lastNameMatches
68+ firstNameMatches . length > 0 ? firstNameMatches : lastNameAndBranchMatches
6769
68- if ( candidates . length === 1 ) {
69- if ( firstNameMatches . length === 0 ) {
70- functions . logger . warn ( "Last name matched but first name did not align" , {
71- memberCode : member . MemberCode ,
72- name : member . Name ,
73- district : member . District ,
74- branch,
75- ocpfFirstName : candidates [ 0 ] . firstName ,
76- ocpfLastName : candidates [ 0 ] . lastName
77- } )
78- }
70+ if ( firstNameMatches . length === 1 ) {
7971 const entry : OcpfMemberMappingEntry = {
8072 cpfId : candidates [ 0 ] . cpfId ,
8173 name : member . Name
8274 }
8375 mapping [ member . MemberCode ] = entry
76+
77+ // Matching was likley fixed manually
78+ } else if ( member . MemberCode in existingMapping ) {
79+ continue
80+
81+ // Single last name match but first name didn't align. Flag rather than auto-match,
82+ // since the OCPF filer may be a different person (e.g. original member changed office sought,
83+ // and another person with same last name is running for original office).
84+ } else if ( candidates . length === 1 && firstNameMatches . length === 0 ) {
85+ ambiguous . push ( { memberCode : member . MemberCode , name : member . Name } )
86+ functions . logger . warn (
87+ "Single last-name match but first name did not align" ,
88+ {
89+ memberCode : member . MemberCode ,
90+ name : member . Name ,
91+ district : member . District ,
92+ branch,
93+ ocpfFirstName : candidates [ 0 ] . firstName ,
94+ ocpfLastName : candidates [ 0 ] . lastName ,
95+ ocpfDistrict : candidates [ 0 ] . district ,
96+ ocpfOfficeSought : candidates [ 0 ] . officeSought
97+ }
98+ )
8499 } else if ( candidates . length === 0 ) {
85- if ( member . MemberCode in existingMapping ) continue
86100 unmatched . push ( { memberCode : member . MemberCode , name : member . Name } )
87101 functions . logger . warn ( "No OCPF match" , {
88102 memberCode : member . MemberCode ,
@@ -91,7 +105,6 @@ export const matchOcpfMembers = functions.https.onRequest(async (req, res) => {
91105 branch
92106 } )
93107 } else {
94- if ( member . MemberCode in existingMapping ) continue
95108 ambiguous . push ( { memberCode : member . MemberCode , name : member . Name } )
96109 functions . logger . warn ( "Ambiguous OCPF match" , {
97110 memberCode : member . MemberCode ,
@@ -102,7 +115,8 @@ export const matchOcpfMembers = functions.https.onRequest(async (req, res) => {
102115 cpfId : c . cpfId ,
103116 firstName : c . firstName ,
104117 lastName : c . lastName ,
105- district : c . district
118+ district : c . district ,
119+ officeSought : c . officeSought
106120 } ) )
107121 } )
108122 }
@@ -165,7 +179,6 @@ async function downloadAndParseFilers(): Promise<OcpfFilerRow[]> {
165179 "closedDate"
166180 ] )
167181
168-
169182 // Values in the file are wrapped in double quotes — strip them after splitting
170183 const col = ( cols : string [ ] , idx : number ) =>
171184 ( cols [ idx ] ?? "" ) . trim ( ) . replace ( / ^ " | " $ / g, "" )
0 commit comments