2026-03-30 16:37:58 +02:00
|
|
|
// API-Base: In Produktion relativ, in Dev mit Port
|
|
|
|
|
const BASE = typeof window !== 'undefined'
|
|
|
|
|
? (window.location.port === '5173'
|
|
|
|
|
? `http://${window.location.hostname}:8099/api` // Dev
|
|
|
|
|
: '/api') // Produktion
|
|
|
|
|
: '/api';
|
|
|
|
|
|
|
|
|
|
async function get<T>(path: string): Promise<T> {
|
|
|
|
|
const res = await fetch(`${BASE}${path}`);
|
|
|
|
|
if (!res.ok) throw new Error(`API error: ${res.status}`);
|
|
|
|
|
return res.json();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export interface VorlageKurz {
|
|
|
|
|
id: number;
|
|
|
|
|
aktenzeichen: string | null;
|
|
|
|
|
typ: string | null;
|
|
|
|
|
betreff: string | null;
|
|
|
|
|
datum_eingang: string | null;
|
|
|
|
|
ist_verwaltungsvorlage: boolean;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export interface ParteiOut {
|
|
|
|
|
id: number;
|
|
|
|
|
kuerzel: string;
|
|
|
|
|
name: string | null;
|
|
|
|
|
farbe: string | null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export interface GremiumOut {
|
|
|
|
|
id: number;
|
|
|
|
|
name: string;
|
|
|
|
|
kuerzel: string | null;
|
|
|
|
|
typ: string | null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export interface BeratungOut {
|
|
|
|
|
id: number;
|
|
|
|
|
gremium: GremiumOut | null;
|
|
|
|
|
sitzung_datum: string | null;
|
|
|
|
|
rolle: string | null;
|
|
|
|
|
ergebnis: string | null;
|
|
|
|
|
ergebnis_text: string | null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export interface ReferenzOut {
|
|
|
|
|
vorlage_id: number;
|
|
|
|
|
aktenzeichen: string | null;
|
|
|
|
|
betreff: string | null;
|
|
|
|
|
vorlage_typ: string | null;
|
|
|
|
|
datum_eingang: string | null;
|
|
|
|
|
ref_typ: string | null;
|
|
|
|
|
konfidenz: number | null;
|
|
|
|
|
kontext: string | null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export interface VorlageDetail extends VorlageKurz {
|
|
|
|
|
aktenzeichen_basis: string | null;
|
|
|
|
|
aktenzeichen_suffix: string | null;
|
|
|
|
|
volltext_clean: string | null;
|
|
|
|
|
pdf_url: string | null;
|
|
|
|
|
web_url: string | null;
|
|
|
|
|
thema_kurz: string | null;
|
|
|
|
|
antragsteller: ParteiOut[];
|
|
|
|
|
beratungen: BeratungOut[];
|
|
|
|
|
referenzen_ausgehend: ReferenzOut[];
|
|
|
|
|
referenzen_eingehend: ReferenzOut[];
|
|
|
|
|
kette_id: number | null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export interface KetteKurz {
|
|
|
|
|
id: number;
|
|
|
|
|
ursprung: VorlageKurz | null;
|
|
|
|
|
typ: string | null;
|
|
|
|
|
thema: string | null;
|
|
|
|
|
status: string | null;
|
|
|
|
|
status_seit: string | null;
|
|
|
|
|
letzte_aktivitaet: string | null;
|
|
|
|
|
vertagungen_count: number;
|
|
|
|
|
glieder_count: number;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export interface KettenGliedOut {
|
|
|
|
|
vorlage: VorlageKurz;
|
|
|
|
|
position: number;
|
|
|
|
|
rolle: string | null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export interface KetteDetail {
|
|
|
|
|
id: number;
|
|
|
|
|
ursprung: VorlageKurz | null;
|
|
|
|
|
typ: string | null;
|
|
|
|
|
thema: string | null;
|
|
|
|
|
status: string | null;
|
|
|
|
|
status_seit: string | null;
|
|
|
|
|
letzte_aktivitaet: string | null;
|
|
|
|
|
vertagungen_count: number;
|
2026-04-01 10:36:22 +02:00
|
|
|
begruendung: string | null;
|
2026-03-30 16:37:58 +02:00
|
|
|
glieder: KettenGliedOut[];
|
|
|
|
|
antragsteller: ParteiOut[];
|
|
|
|
|
graph: {
|
|
|
|
|
nodes: GraphNode[];
|
|
|
|
|
edges: GraphEdge[];
|
|
|
|
|
} | null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export interface GraphNode {
|
|
|
|
|
id: number;
|
|
|
|
|
aktenzeichen: string | null;
|
|
|
|
|
typ: string | null;
|
|
|
|
|
betreff: string | null;
|
|
|
|
|
datum_eingang: string | null;
|
|
|
|
|
position?: number;
|
|
|
|
|
rolle?: string;
|
|
|
|
|
ist_verwaltungsvorlage?: boolean;
|
|
|
|
|
extern?: boolean;
|
|
|
|
|
beratungen?: { sitzung_datum: string; rolle: string; ergebnis: string; gremium_name: string }[];
|
|
|
|
|
antragsteller?: { kuerzel: string; name: string; farbe: string }[];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export interface GraphEdge {
|
|
|
|
|
quelle_id: number;
|
|
|
|
|
ziel_id: number;
|
|
|
|
|
typ: string;
|
|
|
|
|
konfidenz: number;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export interface Paginated<T> {
|
|
|
|
|
items: T[];
|
|
|
|
|
total: number;
|
|
|
|
|
page: number;
|
|
|
|
|
page_size: number;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export interface Stats {
|
|
|
|
|
vorlagen_total: number;
|
|
|
|
|
beratungen_total: number;
|
|
|
|
|
ketten_total: number;
|
|
|
|
|
vorlagen_nach_typ: { typ: string; anzahl: number }[];
|
|
|
|
|
ketten_nach_status: { status: string; anzahl: number }[];
|
|
|
|
|
ketten_nach_typ: { typ: string; anzahl: number }[];
|
|
|
|
|
letzte_vorlagen: VorlageKurz[];
|
|
|
|
|
parteien: { kuerzel: string; name: string; farbe: string | null; anzahl: number }[];
|
|
|
|
|
gremien: { name: string; kuerzel: string | null; typ: string | null; anzahl: number }[];
|
|
|
|
|
timeline: { monat: string; anzahl: number }[];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export interface KettenStats {
|
|
|
|
|
nach_typ: Record<string, { status: string; anzahl: number }[]>;
|
|
|
|
|
status_detail: { status: string; anzahl: number; avg_tage: number; avg_vertagungen: number }[];
|
|
|
|
|
versandungs_fruehwarnung: number;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// API functions
|
|
|
|
|
export const fetchStats = () => get<Stats>('/stats');
|
|
|
|
|
export const fetchKettenStats = () => get<KettenStats>('/stats/ketten-stats');
|
|
|
|
|
|
|
|
|
|
export const fetchVorlagen = (params: Record<string, string>) => {
|
|
|
|
|
const qs = new URLSearchParams(params).toString();
|
|
|
|
|
return get<Paginated<VorlageKurz>>(`/vorlagen?${qs}`);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export const fetchVorlage = (id: number) => get<VorlageDetail>(`/vorlagen/${id}`);
|
|
|
|
|
|
|
|
|
|
export const fetchKetten = (params: Record<string, string>) => {
|
|
|
|
|
const qs = new URLSearchParams(params).toString();
|
|
|
|
|
return get<Paginated<KetteKurz>>(`/ketten?${qs}`);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export const fetchKette = (id: number) => get<KetteDetail>(`/ketten/${id}`);
|
2026-04-01 10:36:22 +02:00
|
|
|
|
|
|
|
|
// Fraktionen
|
|
|
|
|
export interface FraktionDashboard {
|
|
|
|
|
partei: { id: number; kuerzel: string; name: string; farbe: string | null };
|
|
|
|
|
total_antraege: number;
|
|
|
|
|
bewertet: number;
|
|
|
|
|
umsetzung: { bewertung: string; anzahl: number; avg_score: number }[];
|
|
|
|
|
antraege: {
|
|
|
|
|
id: number; aktenzeichen: string | null; betreff: string | null;
|
|
|
|
|
typ: string | null; datum_eingang: string | null;
|
|
|
|
|
umsetzung_score: number | null; umsetzung_bewertung: string | null;
|
|
|
|
|
umsetzung_begruendung: string | null;
|
|
|
|
|
}[];
|
|
|
|
|
jahre: string[];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Re-evaluation
|
|
|
|
|
async function post<T>(path: string, body: object): Promise<T> {
|
|
|
|
|
const res = await fetch(`${BASE}${path}`, {
|
|
|
|
|
method: 'POST',
|
|
|
|
|
headers: { 'Content-Type': 'application/json' },
|
|
|
|
|
body: JSON.stringify(body),
|
|
|
|
|
});
|
|
|
|
|
if (!res.ok) throw new Error(`API error: ${res.status}`);
|
|
|
|
|
return res.json();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export const reevalVorlage = (id: number, anmerkung: string) =>
|
|
|
|
|
post<{ job_id: string; status: string }>(`/bewertung/vorlagen/${id}`, { anmerkung });
|
|
|
|
|
|
|
|
|
|
export const reevalKette = (id: number, anmerkung: string) =>
|
|
|
|
|
post<{ job_id: string; status: string }>(`/bewertung/ketten/${id}`, { anmerkung });
|
|
|
|
|
|
|
|
|
|
export const fetchJobStatus = (jobId: string) =>
|
|
|
|
|
get<{ status: string; result?: object; error?: string }>(`/bewertung/status/${jobId}`);
|
|
|
|
|
|
|
|
|
|
export const fetchFraktionen = () => get<{ id: number; kuerzel: string; name: string; farbe: string | null; anzahl: number }[]>('/fraktionen');
|
|
|
|
|
export const fetchFraktionDashboard = (kuerzel: string, jahr?: string) => {
|
|
|
|
|
const params = jahr ? `?jahr=${jahr}` : '';
|
|
|
|
|
return get<FraktionDashboard>(`/fraktionen/${kuerzel}/dashboard${params}`);
|
|
|
|
|
};
|