feat(arguments): פופאפ פרופוזיציות גולמיות בלחיצה על "מסתמך על N"
הקישור טיעון↔פרופוזיציות כבר נשמר ב-DB (legal_argument_propositions),
אך ה-UI הציג רק את המספר. מעשיר את get_legal_arguments באותו round-trip
(JOIN ל-claims) להחזיר supporting_propositions = {id, text, source_document},
ועוטף את שורת "מסתמך על N פרופוזיציות" ב-Popover שמציג את הטענות הגולמיות
verbatim עם מקור. שקיפות ועקיבוּת מהטיעון המאוגד חזרה לטענות-המקור.
- supporting_claims נשאר id-only (תאימות לאחור: מונה, צרכני MCP)
- supporting_propositions שדה חדש אופציונלי; fallback לטקסט סטטי כשחסר
- אין מסלול מקביל (G2) — העשרה של אותו endpoint; נרמול-במקור (G1)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -335,18 +335,30 @@ async def get_legal_arguments(
|
|||||||
case_id,
|
case_id,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Pull supporting claim ids for each argument in one round-trip.
|
# Pull supporting claims (id + full text) for each argument in one
|
||||||
|
# round-trip. ``supporting_claims`` stays id-only for backwards compat
|
||||||
|
# (counts, MCP consumers); ``supporting_propositions`` carries the text
|
||||||
|
# so the UI can show the raw propositions without an extra fetch.
|
||||||
arg_ids = [r["id"] for r in rows]
|
arg_ids = [r["id"] for r in rows]
|
||||||
supporting: dict[UUID, list[str]] = {}
|
supporting: dict[UUID, list[str]] = {}
|
||||||
|
propositions: dict[UUID, list[dict]] = {}
|
||||||
if arg_ids:
|
if arg_ids:
|
||||||
joins = await conn.fetch(
|
joins = await conn.fetch(
|
||||||
"""SELECT argument_id, claim_id
|
"""SELECT lap.argument_id, lap.claim_id,
|
||||||
FROM legal_argument_propositions
|
c.claim_text, c.source_document, c.claim_index
|
||||||
WHERE argument_id = ANY($1::uuid[])""",
|
FROM legal_argument_propositions lap
|
||||||
|
JOIN claims c ON c.id = lap.claim_id
|
||||||
|
WHERE lap.argument_id = ANY($1::uuid[])
|
||||||
|
ORDER BY c.claim_index""",
|
||||||
arg_ids,
|
arg_ids,
|
||||||
)
|
)
|
||||||
for j in joins:
|
for j in joins:
|
||||||
supporting.setdefault(j["argument_id"], []).append(str(j["claim_id"]))
|
supporting.setdefault(j["argument_id"], []).append(str(j["claim_id"]))
|
||||||
|
propositions.setdefault(j["argument_id"], []).append({
|
||||||
|
"id": str(j["claim_id"]),
|
||||||
|
"text": j["claim_text"],
|
||||||
|
"source_document": j["source_document"],
|
||||||
|
})
|
||||||
|
|
||||||
out: list[dict] = []
|
out: list[dict] = []
|
||||||
for r in rows:
|
for r in rows:
|
||||||
@@ -354,5 +366,6 @@ async def get_legal_arguments(
|
|||||||
d["id"] = str(d["id"])
|
d["id"] = str(d["id"])
|
||||||
d["case_id"] = str(d["case_id"])
|
d["case_id"] = str(d["case_id"])
|
||||||
d["supporting_claims"] = supporting.get(r["id"], [])
|
d["supporting_claims"] = supporting.get(r["id"], [])
|
||||||
|
d["supporting_propositions"] = propositions.get(r["id"], [])
|
||||||
out.append(d)
|
out.append(d)
|
||||||
return out
|
return out
|
||||||
|
|||||||
@@ -10,6 +10,11 @@ import {
|
|||||||
import { Badge } from "@/components/ui/badge";
|
import { Badge } from "@/components/ui/badge";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Card, CardContent } from "@/components/ui/card";
|
import { Card, CardContent } from "@/components/ui/card";
|
||||||
|
import {
|
||||||
|
Popover,
|
||||||
|
PopoverContent,
|
||||||
|
PopoverTrigger,
|
||||||
|
} from "@/components/ui/popover";
|
||||||
import { Skeleton } from "@/components/ui/skeleton";
|
import { Skeleton } from "@/components/ui/skeleton";
|
||||||
import {
|
import {
|
||||||
PARTY_LABELS_HE,
|
PARTY_LABELS_HE,
|
||||||
@@ -22,7 +27,7 @@ import {
|
|||||||
type LegalArgumentPriority,
|
type LegalArgumentPriority,
|
||||||
} from "@/lib/api/legal-arguments";
|
} from "@/lib/api/legal-arguments";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { Loader2, RefreshCw, Sparkles } from "lucide-react";
|
import { ListTree, Loader2, RefreshCw, Sparkles } from "lucide-react";
|
||||||
|
|
||||||
const PRIORITY_BADGE_TONE: Record<LegalArgumentPriority, string> = {
|
const PRIORITY_BADGE_TONE: Record<LegalArgumentPriority, string> = {
|
||||||
threshold: "bg-danger-bg/60 text-danger-strong border-danger/40",
|
threshold: "bg-danger-bg/60 text-danger-strong border-danger/40",
|
||||||
@@ -102,12 +107,55 @@ function PartySection({ party, args }: PartySectionProps) {
|
|||||||
<p className="text-ink leading-relaxed whitespace-pre-line">
|
<p className="text-ink leading-relaxed whitespace-pre-line">
|
||||||
{arg.argument_body}
|
{arg.argument_body}
|
||||||
</p>
|
</p>
|
||||||
{arg.supporting_claims.length > 0 && (
|
{arg.supporting_claims.length > 0 &&
|
||||||
<p className="text-ink-muted text-xs">
|
(arg.supporting_propositions &&
|
||||||
מסתמך על {arg.supporting_claims.length} פרופוזיציות
|
arg.supporting_propositions.length > 0 ? (
|
||||||
גולמיות.
|
<Popover>
|
||||||
</p>
|
<PopoverTrigger asChild>
|
||||||
)}
|
<button
|
||||||
|
type="button"
|
||||||
|
className="text-gold-strong hover:text-gold inline-flex items-center gap-1 text-xs underline decoration-dotted underline-offset-2"
|
||||||
|
>
|
||||||
|
<ListTree className="size-3.5" aria-hidden />
|
||||||
|
מסתמך על {arg.supporting_claims.length}{" "}
|
||||||
|
פרופוזיציות גולמיות
|
||||||
|
</button>
|
||||||
|
</PopoverTrigger>
|
||||||
|
<PopoverContent
|
||||||
|
align="start"
|
||||||
|
className="max-h-96 w-96 overflow-y-auto"
|
||||||
|
>
|
||||||
|
<p className="text-ink-muted mb-2 text-xs font-medium">
|
||||||
|
הטענות הגולמיות שמהן אוגד הטיעון:
|
||||||
|
</p>
|
||||||
|
<ol className="space-y-2">
|
||||||
|
{arg.supporting_propositions.map((p, i) => (
|
||||||
|
<li
|
||||||
|
key={p.id}
|
||||||
|
className="border-rule border-s-2 ps-2 text-xs"
|
||||||
|
>
|
||||||
|
<span className="text-navy font-medium">
|
||||||
|
{i + 1}.
|
||||||
|
</span>{" "}
|
||||||
|
<span className="text-ink leading-relaxed">
|
||||||
|
{p.text}
|
||||||
|
</span>
|
||||||
|
{p.source_document && (
|
||||||
|
<span className="text-ink-muted mt-0.5 block">
|
||||||
|
מקור: {p.source_document}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ol>
|
||||||
|
</PopoverContent>
|
||||||
|
</Popover>
|
||||||
|
) : (
|
||||||
|
<p className="text-ink-muted text-xs">
|
||||||
|
מסתמך על {arg.supporting_claims.length} פרופוזיציות
|
||||||
|
גולמיות.
|
||||||
|
</p>
|
||||||
|
))}
|
||||||
</div>
|
</div>
|
||||||
</AccordionContent>
|
</AccordionContent>
|
||||||
</AccordionItem>
|
</AccordionItem>
|
||||||
|
|||||||
@@ -22,6 +22,12 @@ export type LegalArgumentPriority =
|
|||||||
| "procedural"
|
| "procedural"
|
||||||
| "relief";
|
| "relief";
|
||||||
|
|
||||||
|
export type SupportingProposition = {
|
||||||
|
id: string;
|
||||||
|
text: string;
|
||||||
|
source_document: string | null;
|
||||||
|
};
|
||||||
|
|
||||||
export type LegalArgument = {
|
export type LegalArgument = {
|
||||||
id: string;
|
id: string;
|
||||||
case_id: string;
|
case_id: string;
|
||||||
@@ -35,6 +41,8 @@ export type LegalArgument = {
|
|||||||
created_at?: string;
|
created_at?: string;
|
||||||
updated_at?: string;
|
updated_at?: string;
|
||||||
supporting_claims: string[];
|
supporting_claims: string[];
|
||||||
|
/** Raw extracted propositions (id + full text) backing this argument. */
|
||||||
|
supporting_propositions?: SupportingProposition[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export type LegalArgumentsResponse = {
|
export type LegalArgumentsResponse = {
|
||||||
|
|||||||
Reference in New Issue
Block a user