|
1 | | -import { difference, flatten, flattenDeep } from "lodash" |
| 1 | +import { difference, flatten } from "lodash" |
2 | 2 | import { Hearing } from "../events/types" |
3 | 3 | import { db, FieldValue, Timestamp } from "../firebase" |
4 | | -import { parseApiDateTime } from "../malegislature" |
5 | 4 | import { Member, MemberReference } from "../members/types" |
6 | 5 | import BillProcessor, { BillUpdates } from "./BillProcessor" |
7 | 6 |
|
@@ -33,7 +32,7 @@ class UpdateBillReferences extends BillProcessor { |
33 | 32 | } |
34 | 33 |
|
35 | 34 | override get billFields() { |
36 | | - return ["id"] |
| 35 | + return ["id", "nextHearingId"] |
37 | 36 | } |
38 | 37 |
|
39 | 38 | getCityUpdates(): BillUpdates { |
@@ -92,53 +91,72 @@ class UpdateBillReferences extends BillProcessor { |
92 | 91 | async getEventUpdates(): Promise<BillUpdates> { |
93 | 92 | const hearings = await db |
94 | 93 | .collection(`/events`) |
95 | | - .where("startsAt", ">=", new Date()) |
96 | 94 | .where("type", "==", "hearing") |
97 | 95 | .get() |
98 | 96 | .then(this.load(Hearing)) |
99 | 97 | const updates: BillUpdates = new Map() |
100 | 98 |
|
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 | + } |
125 | 130 | }) |
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 | + }) |
127 | 140 | }) |
128 | 141 |
|
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 | + ) |
140 | 156 | billsWithRemovedEvents.forEach(id => { |
| 157 | + const existing = updates.get(id) ?? {} |
141 | 158 | updates.set(id, { |
| 159 | + ...existing, |
142 | 160 | nextHearingAt: FieldValue.delete(), |
143 | 161 | nextHearingId: FieldValue.delete() |
144 | 162 | }) |
|
0 commit comments