feat(graph): metadata filters + facets (corpus graph PR A)
Adds legal-metadata filtering and the payload to color by it (foundation for the color-by selector in the analytics PR). Backend (web/graph_api.py, web/app.py) — read-only, G2: - GraphNode += court, date (ISO) — precedents carry them for filter/color-by. - build_corpus_graph += server-side WHERE filters (G5): court, precedent_level, chair, district, year_from, year_to (EXTRACT(YEAR FROM date)). Neighborhood query also selects court/date. - New GET /api/graph/facets (response_model GraphFacets, UI2) → distinct courts/levels/chairs/districts so the UI doesn't hardcode Hebrew strings. Frontend: - graph.ts: GraphNode += court/date; GraphFilters += the six params; buildParams; useGraphFacets() hook. - graph-filter-panel: an "advanced" Accordion with court/precedent_level/chair/ district Selects (from facets) + year-from/year-to Selects. - graph-view: new controls wired into filters; facets fetched and passed down. Verified read-only against the live DB (precedent_level=עליון&year_from=2015 filters correctly; facets populated: 36 courts / 3 levels / 19 chairs / 4 districts). web-ui build + lint pass. Invariants: G2 (SELECT-only via db.get_pool), G5 (filters server-side), UI2 (explicit response_models). api:types to be regenerated post-deploy. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -32,9 +32,18 @@ export type GraphNode = {
|
||||
practice_area: string | null;
|
||||
source_kind: string | null;
|
||||
precedent_level: string | null;
|
||||
court: string | null;
|
||||
date: string | null; // ISO date
|
||||
case_law_id: string | null;
|
||||
};
|
||||
|
||||
export type GraphFacets = {
|
||||
courts: string[];
|
||||
precedent_levels: string[];
|
||||
chairs: string[];
|
||||
districts: string[];
|
||||
};
|
||||
|
||||
export type GraphEdge = {
|
||||
source: string;
|
||||
target: string;
|
||||
@@ -57,6 +66,12 @@ export type GraphFilters = {
|
||||
min_citations?: number;
|
||||
limit?: number;
|
||||
q?: string;
|
||||
court?: string;
|
||||
precedent_level?: string;
|
||||
chair?: string;
|
||||
district?: string;
|
||||
year_from?: number;
|
||||
year_to?: number;
|
||||
};
|
||||
|
||||
export const graphKeys = {
|
||||
@@ -74,6 +89,12 @@ function buildParams(f: GraphFilters): string {
|
||||
if (f.min_citations != null) p.set("min_citations", String(f.min_citations));
|
||||
if (f.limit != null) p.set("limit", String(f.limit));
|
||||
if (f.q) p.set("q", f.q.trim());
|
||||
if (f.court) p.set("court", f.court);
|
||||
if (f.precedent_level) p.set("precedent_level", f.precedent_level);
|
||||
if (f.chair) p.set("chair", f.chair);
|
||||
if (f.district) p.set("district", f.district);
|
||||
if (f.year_from) p.set("year_from", String(f.year_from));
|
||||
if (f.year_to) p.set("year_to", String(f.year_to));
|
||||
return p.toString();
|
||||
}
|
||||
|
||||
@@ -109,3 +130,12 @@ export function useNodeNeighborhood(
|
||||
staleTime: 30_000,
|
||||
});
|
||||
}
|
||||
|
||||
/** Distinct filter values (courts / levels / chairs / districts) for dropdowns. */
|
||||
export function useGraphFacets() {
|
||||
return useQuery({
|
||||
queryKey: [...graphKeys.all, "facets"] as const,
|
||||
queryFn: ({ signal }) => apiRequest<GraphFacets>("/api/graph/facets", { signal }),
|
||||
staleTime: 300_000,
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user