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);
|
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(
|
function isOpeningHours(
|
||||||
val: unknown
|
val: unknown
|
||||||
): val is Array<{ day: string; open: string; close: string }> {
|
): 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 {
|
function isAdmissionInfo(val: unknown): val is {
|
||||||
@@ -111,7 +122,26 @@
|
|||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
<div class="text-sm text-stone-900 dark:text-stone-100">
|
<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">
|
<table class="w-full text-sm">
|
||||||
<thead>
|
<thead>
|
||||||
<tr class="text-left text-stone-500 dark:text-stone-400">
|
<tr class="text-left text-stone-500 dark:text-stone-400">
|
||||||
@@ -130,6 +160,12 @@
|
|||||||
{/each}
|
{/each}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</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)}
|
{:else if suggestion.field === 'admission_info' && isAdmissionInfo(suggestion.suggested_value)}
|
||||||
<dl class="grid grid-cols-2 gap-x-4 gap-y-1 text-sm">
|
<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>
|
<dt class="text-stone-500 dark:text-stone-400">Erwachsene</dt>
|
||||||
|
|||||||
@@ -33,7 +33,18 @@
|
|||||||
'Samstag'
|
'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 {
|
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})/);
|
const dateMatch = day.match(/(\d{2})\.(\d{2})\.(\d{4})/);
|
||||||
if (dateMatch) {
|
if (dateMatch) {
|
||||||
const d = new Date(+dateMatch[3], +dateMatch[2] - 1, +dateMatch[1]);
|
const d = new Date(+dateMatch[3], +dateMatch[2] - 1, +dateMatch[1]);
|
||||||
@@ -46,19 +57,31 @@
|
|||||||
function applyResearch(suggestions: FieldSuggestion[]) {
|
function applyResearch(suggestions: FieldSuggestion[]) {
|
||||||
for (const s of suggestions) {
|
for (const s of suggestions) {
|
||||||
if (s.field === 'opening_hours' && Array.isArray(s.suggested_value)) {
|
if (s.field === 'opening_hours' && Array.isArray(s.suggested_value)) {
|
||||||
const normalized = (s.suggested_value as OpeningHoursEntry[]).map((entry) => ({
|
const entries = s.suggested_value as Array<LLMOeffnungszeit | OpeningHoursEntry>;
|
||||||
...entry,
|
const normalized: OpeningHoursEntry[] = entries.map((entry) => {
|
||||||
day: normalizeDayName(entry.day)
|
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);
|
marketForm.setHours(normalized);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (
|
if (s.field === 'admission_info') {
|
||||||
s.field === 'admission_info' &&
|
if (Array.isArray(s.suggested_value)) {
|
||||||
typeof s.suggested_value === 'object' &&
|
// LLM format: [{name, betrag, waehrung}] — put in notes, leave cents at 0
|
||||||
s.suggested_value !== null
|
const tickets = s.suggested_value as LLMEintrittspreis[];
|
||||||
) {
|
const notes = tickets.map((t) => `${t.name}: ${t.betrag} ${t.waehrung}`).join('\n');
|
||||||
marketForm.setAdmission(s.suggested_value as AdmissionInfo);
|
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;
|
continue;
|
||||||
}
|
}
|
||||||
const el = document.querySelector<HTMLInputElement | HTMLTextAreaElement>(
|
const el = document.querySelector<HTMLInputElement | HTMLTextAreaElement>(
|
||||||
|
|||||||
Reference in New Issue
Block a user