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:
@@ -13,6 +13,7 @@ import {
|
||||
type CorpusGraph,
|
||||
type GraphNode,
|
||||
useCorpusGraph,
|
||||
useGraphFacets,
|
||||
useNodeNeighborhood,
|
||||
} from "@/lib/api/graph";
|
||||
import {
|
||||
@@ -39,10 +40,17 @@ export function GraphView() {
|
||||
source: "",
|
||||
minCitations: 0,
|
||||
q: "",
|
||||
court: "",
|
||||
precedentLevel: "",
|
||||
chair: "",
|
||||
district: "",
|
||||
yearFrom: 0,
|
||||
yearTo: 0,
|
||||
showTopics: true,
|
||||
showPracticeAreas: true,
|
||||
showHalachot: false,
|
||||
});
|
||||
const facets = useGraphFacets().data;
|
||||
const [selectedNode, setSelectedNode] = useState<GraphNode | null>(null);
|
||||
const [focusNodeId, setFocusNodeId] = useState<string | null>(null);
|
||||
|
||||
@@ -67,8 +75,26 @@ export function GraphView() {
|
||||
node_types: nodeTypes,
|
||||
limit: NODE_LIMIT,
|
||||
q: debouncedQ,
|
||||
court: controls.court,
|
||||
precedent_level: controls.precedentLevel,
|
||||
chair: controls.chair,
|
||||
district: controls.district,
|
||||
year_from: controls.yearFrom,
|
||||
year_to: controls.yearTo,
|
||||
}),
|
||||
[controls.practiceArea, controls.source, controls.minCitations, nodeTypes, debouncedQ],
|
||||
[
|
||||
controls.practiceArea,
|
||||
controls.source,
|
||||
controls.minCitations,
|
||||
controls.court,
|
||||
controls.precedentLevel,
|
||||
controls.chair,
|
||||
controls.district,
|
||||
controls.yearFrom,
|
||||
controls.yearTo,
|
||||
nodeTypes,
|
||||
debouncedQ,
|
||||
],
|
||||
);
|
||||
|
||||
const isFocused = !!focusNodeId;
|
||||
@@ -106,7 +132,7 @@ export function GraphView() {
|
||||
</div>
|
||||
|
||||
<div className="flex gap-4 h-[calc(100vh-320px)] min-h-[560px]">
|
||||
<GraphFilterPanel controls={controls} onChange={onChange} />
|
||||
<GraphFilterPanel controls={controls} onChange={onChange} facets={facets} />
|
||||
|
||||
<div className="relative flex-1 rounded-lg border border-rule bg-surface overflow-hidden">
|
||||
{error ? (
|
||||
|
||||
Reference in New Issue
Block a user