Skip to content

Commit db679b3

Browse files
committed
#131 get and include filing agency in case summaries; capture but don't show addresses
1 parent 773ba79 commit db679b3

7 files changed

Lines changed: 369 additions & 8 deletions

File tree

frontend/src/components/app/SearchResult.tsx

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,13 @@ const SearchResult: React.FC<SearchResultProps> = ({ searchResult: sr }) => {
8383
}
8484
return null;
8585
})()}
86+
87+
{/* Filing agency: shown at top-level if the case summary has a single filing agency for all charges */}
88+
{summary.filingAgency && (
89+
<p className="mt-1 text-sm text-gray-600">
90+
<span className="font-medium">Filing Agency:</span> {summary.filingAgency}
91+
</p>
92+
)}
8693
</div>
8794

8895
{summary.charges && summary.charges.length > 0 && (
@@ -118,6 +125,13 @@ const SearchResult: React.FC<SearchResultProps> = ({ searchResult: sr }) => {
118125
<span className="font-medium">Fine:</span> ${charge.fine.toFixed(2)}
119126
</div>
120127
)}
128+
129+
{/* Per-charge filing agency: only shown when no top-level filing agency is present */}
130+
{!summary.filingAgency && charge.filingAgency && (
131+
<div>
132+
<span className="font-medium">Filing Agency:</span> {charge.filingAgency}
133+
</div>
134+
)}
121135
</div>
122136
{charge.dispositions && charge.dispositions.length > 0 && (
123137
<div className="mt-2 text-xs text-gray-600">

frontend/src/components/app/__tests__/SearchResult.test.tsx

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,11 @@ const createTestCase = (override = {}): SearchResultType => ({
5050
description: 'Guilty',
5151
},
5252
],
53+
filingAgency: null,
54+
filingAgencyAddress: [],
5355
},
5456
],
57+
filingAgency: null,
5558
},
5659
...override,
5760
});
@@ -218,4 +221,77 @@ describe('SearchResult component', () => {
218221
// Should still render the charge description
219222
expect(screen.getByText('Weird Charge')).toBeInTheDocument();
220223
});
224+
225+
it('displays top-level filing agency when present', () => {
226+
const testCase = createTestCase({
227+
caseSummary: {
228+
caseName: 'State vs. Doe',
229+
court: 'Circuit Court',
230+
filingAgency: 'Metro PD',
231+
charges: [
232+
{
233+
offenseDate: '2022-01-01',
234+
filedDate: '2022-01-02',
235+
description: 'Theft',
236+
statute: '123.456',
237+
degree: { code: 'M', description: 'Misdemeanor' },
238+
fine: 0,
239+
dispositions: [],
240+
filingAgency: 'Metro PD',
241+
},
242+
],
243+
},
244+
});
245+
246+
render(<SearchResult searchResult={testCase} />);
247+
248+
// Top-level Filing Agency should be present
249+
expect(screen.getByText(/Filing Agency:/)).toBeInTheDocument();
250+
expect(screen.getByText('Metro PD')).toBeInTheDocument();
251+
252+
// Per-charge filing agency should not be duplicated when top-level present
253+
const chargeAgency = screen.queryAllByText(/Filing Agency:/).length;
254+
expect(chargeAgency).toBe(1); // only the top-level label
255+
});
256+
257+
it('displays per-charge filing agencies when they differ and no top-level is set', () => {
258+
const testCase = createTestCase({
259+
caseSummary: {
260+
caseName: 'State vs. Doe',
261+
court: 'Circuit Court',
262+
charges: [
263+
{
264+
offenseDate: '2022-01-01',
265+
filedDate: '2022-01-02',
266+
description: 'Charge A',
267+
statute: '111',
268+
degree: { code: 'M', description: 'M' },
269+
fine: 0,
270+
dispositions: [],
271+
filingAgency: 'Dept A',
272+
},
273+
{
274+
offenseDate: '2022-02-01',
275+
filedDate: '2022-02-02',
276+
description: 'Charge B',
277+
statute: '222',
278+
degree: { code: 'M', description: 'M' },
279+
fine: 0,
280+
dispositions: [],
281+
filingAgency: 'Dept B',
282+
},
283+
],
284+
},
285+
});
286+
287+
render(<SearchResult searchResult={testCase} />);
288+
289+
// No single top-level filing agency — expect per-charge Filing Agency labels for each charge
290+
const filingLabels = screen.queryAllByText(/Filing Agency:/);
291+
expect(filingLabels.length).toBeGreaterThanOrEqual(2);
292+
293+
// Both per-charge filing agencies should be present
294+
expect(screen.getByText('Dept A')).toBeInTheDocument();
295+
expect(screen.getByText('Dept B')).toBeInTheDocument();
296+
});
221297
});

serverless/lib/CaseProcessor.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -639,6 +639,7 @@ function buildCaseSummary(rawData: Record<string, PortalApiResponse>): CaseSumma
639639
caseName: rawData['summary']['CaseSummaryHeader']['Style'] || '',
640640
court: rawData['summary']['CaseSummaryHeader']['Heading'] || '',
641641
charges: [],
642+
filingAgency: null,
642643
};
643644

644645
const chargeMap = new Map<number, Charge>();
@@ -663,8 +664,21 @@ function buildCaseSummary(rawData: Record<string, PortalApiResponse>): CaseSumma
663664
},
664665
fine: typeof chargeOffense['FineAmount'] === 'number' ? chargeOffense['FineAmount'] : 0,
665666
dispositions: [],
667+
filingAgency: null,
668+
filingAgencyAddress: [],
666669
};
667670

671+
const filingAgencyRaw = chargeData['FilingAgencyDescription'];
672+
if (filingAgencyRaw) {
673+
charge.filingAgency = String(filingAgencyRaw).trim();
674+
}
675+
676+
// Extract filing agency address if present. It will be an array of strings.
677+
const filingAgencyAddressRaw = chargeData['FilingAgencyAddress'];
678+
if (filingAgencyAddressRaw) {
679+
charge.filingAgencyAddress.push(...(filingAgencyAddressRaw as any));
680+
}
681+
668682
// Add to charges array
669683
caseSummary.charges.push(charge);
670684

@@ -674,6 +688,22 @@ function buildCaseSummary(rawData: Record<string, PortalApiResponse>): CaseSumma
674688
}
675689
});
676690

691+
// After processing charges, derive top-level filing agency if appropriate
692+
try {
693+
const definedAgencies = caseSummary.charges.map(ch => ch.filingAgency).filter((a): a is string => a !== null && a.length > 0);
694+
695+
const uniqueAgencies = Array.from(new Set(definedAgencies));
696+
697+
// If there's at least one defined agency, and all defined agencies are identical,
698+
// set it on the case summary. Charges that lack an agency (null) are ignored for this decision.
699+
if (uniqueAgencies.length === 1 && uniqueAgencies[0]) {
700+
caseSummary.filingAgency = uniqueAgencies[0];
701+
console.log(`🔔 Set Filing Agency to ${caseSummary.filingAgency}`);
702+
}
703+
} catch (faErr) {
704+
console.error('Error computing top-level filing agency:', faErr);
705+
}
706+
677707
// Process dispositions and link them to charges
678708
const dispositionEvents = rawData['dispositionEvents']['Events'] || [];
679709
console.log(`📋 Found ${dispositionEvents.length} disposition events`);

serverless/lib/__tests__/CaseSearchProcessor.test.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ describe('CaseSearchProcessor', () => {
4040
caseName: 'Test vs State',
4141
court: 'Test Court',
4242
charges: [],
43+
filingAgency: null,
4344
};
4445

4546
const completeCase: SearchResult = {
@@ -284,6 +285,7 @@ describe('CaseSearchProcessor', () => {
284285
caseName: 'Test vs State',
285286
court: 'Test Court',
286287
charges: [],
288+
filingAgency: null,
287289
};
288290

289291
mockStorageClient.getSearchResults.mockResolvedValue({

0 commit comments

Comments
 (0)