feat(style-acq T14): שער-יו"ר לאישור הצעות-curator → הטמעה לפרופיל

סוגר את הלולאה מקצה-לקצה (INV-G10/LRN1): ה-curator מציע (status=analyzed),
היו"ר מאשרת, והלקחים נכתבים לערוצים שהכותב צורך (T15) — אין auto-commit.

- db.get_draft_final_pair(id) — שורת-פנקס מלאה כולל analysis.
- app.py: GET /api/learning/pairs/{id} (חושף רק changes מסוג style_method —
  INV-LRN5) + POST .../promote (לקחים→discussion_rules['universal'],
  ביטויים→transition_phrases['universal'] דרך merge ל-appeal_type_rules;
  status→lessons_folded). _append_methodology_override משותף.
- web-ui: usePairDetail/usePromoteLearning + ProposalReview (בחירת לקחים/
  ביטויים לאימוץ) בטאב "למידה" עבור pairs במצב analyzed.

INV-G10 (שער-יו"ר) · INV-LRN1 (אין auto-commit) · INV-LRN5 (טוהר).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-06 19:17:56 +00:00
parent ee76455a9a
commit f20a3a09fd
4 changed files with 244 additions and 5 deletions

View File

@@ -3,7 +3,7 @@
* Backs the /training "למידה" tab. Endpoints under /api/learning.
*/
import { useQuery } from "@tanstack/react-query";
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { apiRequest } from "./client";
export type DraftFinalPair = {
@@ -67,3 +67,50 @@ export function useStyleDistance(caseNumber: string | null) {
staleTime: 15_000,
});
}
// ── T14: curator distillation proposal + chair approval gate ──────
export type DistillationChange = {
type?: string;
domain?: string;
block?: string;
description?: string;
draft_text?: string;
final_text?: string;
lesson?: string;
};
export type PairDetail = {
id: string;
case_number: string;
title: string;
status: string;
diff_stats: { change_percent?: number } | null;
overall_assessment: string;
changes: DistillationChange[]; // style_method only (server-filtered)
new_expressions: string[];
};
export function usePairDetail(pairId: string | null) {
return useQuery({
queryKey: [...learningKeys.all, "pair", pairId ?? ""] as const,
queryFn: ({ signal }) =>
apiRequest<PairDetail>(`/api/learning/pairs/${pairId}`, { signal }),
enabled: Boolean(pairId),
staleTime: 15_000,
});
}
export function usePromoteLearning(pairId: string) {
const qc = useQueryClient();
return useMutation({
mutationFn: (body: { lessons: string[]; phrases: string[] }) =>
apiRequest<{ id: string; status: string; folded_lessons: number; folded_phrases: number }>(
`/api/learning/pairs/${pairId}/promote`,
{ method: "POST", body },
),
onSuccess: () => {
qc.invalidateQueries({ queryKey: learningKeys.all });
},
});
}