Skip to content

Commit d82b840

Browse files
authored
Bill to hearing mapping (#1995)
* fix translation string typo * refactor(HearingSearch): update sorting options and improve hearing date filters * refactor(HearingSearch): streamline sorting options and update translation strings * Bill-to-Hearings mapping
1 parent 8ebc9f1 commit d82b840

3 files changed

Lines changed: 62 additions & 41 deletions

File tree

components/db/bills.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ export type Bill = {
5151
opposeCount: number
5252
neutralCount: number
5353
nextHearingAt?: Timestamp
54+
hearingIds?: string[]
5455
latestTestimonyAt?: Timestamp
5556
latestTestimonyId?: string
5657
fetchedAt: Timestamp

functions/src/bills/types.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -317,6 +317,7 @@ export const Bill = withDefaults(
317317
opposeCount: Number,
318318
nextHearingAt: Optional(InstanceOf(Timestamp)),
319319
nextHearingId: Optional(Id),
320+
hearingIds: Optional(Array(String)),
320321
latestTestimonyAt: Optional(InstanceOf(Timestamp)),
321322
latestTestimonyId: Optional(Id),
322323
fetchedAt: InstanceOf(Timestamp),
@@ -339,6 +340,7 @@ export const Bill = withDefaults(
339340
fetchedAt: MISSING_TIMESTAMP,
340341
history: [],
341342
similar: [],
342-
topics: []
343+
topics: [],
344+
hearingIds: []
343345
}
344346
)

functions/src/bills/updateBillReferences.ts

Lines changed: 58 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
1-
import { difference, flatten, flattenDeep } from "lodash"
1+
import { difference, flatten } from "lodash"
22
import { Hearing } from "../events/types"
33
import { db, FieldValue, Timestamp } from "../firebase"
4-
import { parseApiDateTime } from "../malegislature"
54
import { Member, MemberReference } from "../members/types"
65
import BillProcessor, { BillUpdates } from "./BillProcessor"
76

@@ -33,7 +32,7 @@ class UpdateBillReferences extends BillProcessor {
3332
}
3433

3534
override get billFields() {
36-
return ["id"]
35+
return ["id", "nextHearingId"]
3736
}
3837

3938
getCityUpdates(): BillUpdates {
@@ -92,53 +91,72 @@ class UpdateBillReferences extends BillProcessor {
9291
async getEventUpdates(): Promise<BillUpdates> {
9392
const hearings = await db
9493
.collection(`/events`)
95-
.where("startsAt", ">=", new Date())
9694
.where("type", "==", "hearing")
9795
.get()
9896
.then(this.load(Hearing))
9997
const updates: BillUpdates = new Map()
10098

101-
// Set the next hearing on every bill referenced by upcoming hearings.
102-
const billEvents = flattenDeep<{
103-
startsAt: Timestamp
104-
billId: string
105-
hearingId: string
106-
court: number
107-
}>(
108-
hearings.map(hearing =>
109-
hearing.content.HearingAgendas.map(agenda =>
110-
agenda.DocumentsInAgenda.map(doc => ({
111-
startsAt: parseApiDateTime(agenda.StartTime),
112-
billId: doc.BillNumber,
113-
court: doc.GeneralCourtNumber,
114-
hearingId: hearing.id
115-
}))
116-
)
117-
)
118-
)
119-
billEvents.forEach(event => {
120-
const existing = updates.get(event.billId)
121-
if (!existing || event.startsAt < (existing.nextHearingAt as Timestamp)) {
122-
updates.set(event.billId, {
123-
nextHearingAt: event.startsAt,
124-
nextHearingId: event.hearingId
99+
// Build mapping from billId -> hearingIds and compute earliest upcoming hearing
100+
const hearingIdsByBill = new Map<string, Set<string>>()
101+
102+
const now = Timestamp.fromMillis(Date.now())
103+
104+
hearings.forEach(hearing => {
105+
const hearingId = hearing.id
106+
const startsAt = hearing.startsAt
107+
108+
hearing.content.HearingAgendas.forEach(agenda => {
109+
agenda.DocumentsInAgenda.forEach(doc => {
110+
const billId = doc.BillNumber
111+
112+
if (!hearingIdsByBill.has(billId))
113+
hearingIdsByBill.set(billId, new Set())
114+
hearingIdsByBill.get(billId)!.add(hearingId)
115+
116+
// Track next upcoming hearing per bill (startsAt in the future)
117+
if (startsAt.toMillis() >= now.toMillis()) {
118+
const existing = updates.get(billId)
119+
if (
120+
!existing ||
121+
(existing.nextHearingAt as Timestamp | undefined)?.toMillis?.()! >
122+
startsAt.toMillis()
123+
) {
124+
updates.set(billId, {
125+
nextHearingAt: startsAt,
126+
nextHearingId: hearingId
127+
})
128+
}
129+
}
125130
})
126-
}
131+
})
132+
})
133+
134+
hearingIdsByBill.forEach((ids, billId) => {
135+
const existing = updates.get(billId) ?? {}
136+
updates.set(billId, {
137+
...existing,
138+
hearingIds: Array.from(ids)
139+
})
127140
})
128141

129-
// Remove the next hearing on any bills that reference upcoming hearings but
130-
// aren't on the agenda.
131-
const hearingIds = new Set(billEvents.map(e => e.hearingId)),
132-
billsWithEvents = billEvents.map(e => e.billId),
133-
existingBillsWithEvents = this.bills
134-
.filter(b => hearingIds.has(b.nextHearingId))
135-
.map(b => b.id as string),
136-
billsWithRemovedEvents = difference(
137-
existingBillsWithEvents,
138-
billsWithEvents
139-
)
142+
// Remove the next hearing on any bills that previously had an upcoming hearing
143+
// but are no longer on any upcoming hearing agendas.
144+
const upcomingHearingBillIds = new Set<string>()
145+
updates.forEach((u, id) => {
146+
if ((u.nextHearingAt as Timestamp | undefined)?.toMillis?.())
147+
upcomingHearingBillIds.add(id)
148+
})
149+
const existingBillsWithEvents = this.bills
150+
.filter(b => !!b.nextHearingId)
151+
.map(b => b.id as string)
152+
const billsWithRemovedEvents = difference(
153+
existingBillsWithEvents,
154+
Array.from(upcomingHearingBillIds)
155+
)
140156
billsWithRemovedEvents.forEach(id => {
157+
const existing = updates.get(id) ?? {}
141158
updates.set(id, {
159+
...existing,
142160
nextHearingAt: FieldValue.delete(),
143161
nextHearingId: FieldValue.delete()
144162
})

0 commit comments

Comments
 (0)