fix(research): convert LLM schema shapes to form-compatible types on apply
Researcher emits {datum_von,von,bis} for opening hours and [{name,betrag,waehrung}]
for admission info — both incompatible with the form's {day,open,close} and
AdmissionInfo shapes. Normalize on apply; extend normalizeDayName to handle
ISO YYYY-MM-DD dates the LLM produces. ResearchPanel renders both LLM and
form-native formats with dedicated table/list views.
This commit is contained in:
@@ -41,10 +41,21 @@
|
||||
return JSON.stringify(val, null, 2);
|
||||
}
|
||||
|
||||
type LLMOeffnungszeit = { datum_von: string; datum_bis: string; von: string; bis: string };
|
||||
type LLMEintrittspreis = { name: string; betrag: number; waehrung: string };
|
||||
|
||||
function isLLMOeffnungszeiten(val: unknown): val is LLMOeffnungszeit[] {
|
||||
return Array.isArray(val) && val.length > 0 && 'datum_von' in (val[0] as object);
|
||||
}
|
||||
|
||||
function isLLMEintrittspreise(val: unknown): val is LLMEintrittspreis[] {
|
||||
return Array.isArray(val) && val.length > 0 && 'betrag' in (val[0] as object);
|
||||
}
|
||||
|
||||
function isOpeningHours(
|
||||
val: unknown
|
||||
): val is Array<{ day: string; open: string; close: string }> {
|
||||
return Array.isArray(val) && val.length > 0 && 'day' in val[0];
|
||||
return Array.isArray(val) && val.length > 0 && 'day' in (val[0] as object);
|
||||
}
|
||||
|
||||
function isAdmissionInfo(val: unknown): val is {
|
||||
@@ -111,7 +122,26 @@
|
||||
</div>
|
||||
{/if}
|
||||
<div class="text-sm text-stone-900 dark:text-stone-100">
|
||||
{#if suggestion.field === 'opening_hours' && isOpeningHours(suggestion.suggested_value)}
|
||||
{#if suggestion.field === 'opening_hours' && isLLMOeffnungszeiten(suggestion.suggested_value)}
|
||||
<table class="w-full text-sm">
|
||||
<thead>
|
||||
<tr class="text-left text-stone-500 dark:text-stone-400">
|
||||
<th class="pr-4 pb-1 font-medium">Datum</th>
|
||||
<th class="pr-4 pb-1 font-medium">Von</th>
|
||||
<th class="pb-1 font-medium">Bis</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{#each suggestion.suggested_value as entry}
|
||||
<tr>
|
||||
<td class="py-0.5 pr-4">{entry.datum_von}</td>
|
||||
<td class="py-0.5 pr-4">{entry.von}</td>
|
||||
<td class="py-0.5">{entry.bis}</td>
|
||||
</tr>
|
||||
{/each}
|
||||
</tbody>
|
||||
</table>
|
||||
{:else if suggestion.field === 'opening_hours' && isOpeningHours(suggestion.suggested_value)}
|
||||
<table class="w-full text-sm">
|
||||
<thead>
|
||||
<tr class="text-left text-stone-500 dark:text-stone-400">
|
||||
@@ -130,6 +160,12 @@
|
||||
{/each}
|
||||
</tbody>
|
||||
</table>
|
||||
{:else if suggestion.field === 'admission_info' && isLLMEintrittspreise(suggestion.suggested_value)}
|
||||
<ul class="space-y-0.5 text-sm">
|
||||
{#each suggestion.suggested_value as ticket}
|
||||
<li>{ticket.name}: {ticket.betrag} {ticket.waehrung}</li>
|
||||
{/each}
|
||||
</ul>
|
||||
{:else if suggestion.field === 'admission_info' && isAdmissionInfo(suggestion.suggested_value)}
|
||||
<dl class="grid grid-cols-2 gap-x-4 gap-y-1 text-sm">
|
||||
<dt class="text-stone-500 dark:text-stone-400">Erwachsene</dt>
|
||||
|
||||
@@ -33,7 +33,18 @@
|
||||
'Samstag'
|
||||
];
|
||||
|
||||
// LLM opening hours entry shape (researcher_schema_simple.json)
|
||||
type LLMOeffnungszeit = { datum_von: string; datum_bis: string; von: string; bis: string };
|
||||
// LLM admission entry shape
|
||||
type LLMEintrittspreis = { name: string; betrag: number; waehrung: string };
|
||||
|
||||
function normalizeDayName(day: string): string {
|
||||
// ISO date YYYY-MM-DD
|
||||
if (/^\d{4}-\d{2}-\d{2}$/.test(day)) {
|
||||
const [y, m, d] = day.split('-').map(Number);
|
||||
return validDays[new Date(y, m - 1, d).getDay()];
|
||||
}
|
||||
// German date DD.MM.YYYY
|
||||
const dateMatch = day.match(/(\d{2})\.(\d{2})\.(\d{4})/);
|
||||
if (dateMatch) {
|
||||
const d = new Date(+dateMatch[3], +dateMatch[2] - 1, +dateMatch[1]);
|
||||
@@ -46,19 +57,31 @@
|
||||
function applyResearch(suggestions: FieldSuggestion[]) {
|
||||
for (const s of suggestions) {
|
||||
if (s.field === 'opening_hours' && Array.isArray(s.suggested_value)) {
|
||||
const normalized = (s.suggested_value as OpeningHoursEntry[]).map((entry) => ({
|
||||
...entry,
|
||||
day: normalizeDayName(entry.day)
|
||||
}));
|
||||
const entries = s.suggested_value as Array<LLMOeffnungszeit | OpeningHoursEntry>;
|
||||
const normalized: OpeningHoursEntry[] = entries.map((entry) => {
|
||||
if ('datum_von' in entry) {
|
||||
return { day: normalizeDayName(entry.datum_von), open: entry.von, close: entry.bis };
|
||||
}
|
||||
return { ...entry, day: normalizeDayName(entry.day) };
|
||||
});
|
||||
marketForm.setHours(normalized);
|
||||
continue;
|
||||
}
|
||||
if (
|
||||
s.field === 'admission_info' &&
|
||||
typeof s.suggested_value === 'object' &&
|
||||
s.suggested_value !== null
|
||||
) {
|
||||
marketForm.setAdmission(s.suggested_value as AdmissionInfo);
|
||||
if (s.field === 'admission_info') {
|
||||
if (Array.isArray(s.suggested_value)) {
|
||||
// LLM format: [{name, betrag, waehrung}] — put in notes, leave cents at 0
|
||||
const tickets = s.suggested_value as LLMEintrittspreis[];
|
||||
const notes = tickets.map((t) => `${t.name}: ${t.betrag} ${t.waehrung}`).join('\n');
|
||||
marketForm.setAdmission({
|
||||
adult_cents: 0,
|
||||
child_cents: 0,
|
||||
reduced_cents: 0,
|
||||
free_under_age: 0,
|
||||
notes
|
||||
});
|
||||
} else if (typeof s.suggested_value === 'object' && s.suggested_value !== null) {
|
||||
marketForm.setAdmission(s.suggested_value as AdmissionInfo);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
const el = document.querySelector<HTMLInputElement | HTMLTextAreaElement>(
|
||||
|
||||
Reference in New Issue
Block a user