fix(research): apply description via reactive state, add name correction

This commit is contained in:
2026-04-25 11:15:33 +02:00
7 changed files with 36 additions and 5 deletions

View File

@@ -100,6 +100,7 @@ type llmOutput struct {
}
type llmFelder struct {
MarktName llmFieldStr `json:"markt_name"`
Beschreibung llmFieldStr `json:"beschreibung"`
Website llmFieldStr `json:"website"`
Strasse llmFieldStr `json:"strasse"`
@@ -171,6 +172,7 @@ func toLLMResearchResult(raw llmOutput, m Market) ResearchResult {
}
entries := []entry{
{"name", strPtr(raw.Felder.MarktName.Wert), str(m.Name), raw.Felder.MarktName.Extraktion, raw.Felder.MarktName.Hinweis},
{"description", strPtr(raw.Felder.Beschreibung.Wert), str(m.Description), raw.Felder.Beschreibung.Extraktion, raw.Felder.Beschreibung.Hinweis},
{"website", strPtr(raw.Felder.Website.Wert), str(m.Website), raw.Felder.Website.Extraktion, raw.Felder.Website.Hinweis},
{"street", strPtr(raw.Felder.Strasse.Wert), str(m.Street), raw.Felder.Strasse.Extraktion, raw.Felder.Strasse.Hinweis},
@@ -195,6 +197,10 @@ func toLLMResearchResult(raw llmOutput, m Market) ResearchResult {
if fmt.Sprintf("%v", e.suggested) == fmt.Sprintf("%v", e.current) {
continue
}
// Name corrections only shown when the LLM is certain (direkt).
if e.field == "name" && e.extraktion != "direkt" {
continue
}
confidence := "medium"
if e.extraktion == "direkt" {
confidence = "high"

View File

@@ -24,6 +24,10 @@ Extraktion aus Quellen.
## Felder
- **markt_name**: Offizieller Name des Markts laut Veranstalter-Website. Nur
setzen wenn der Name klar und mit sehr hoher Sicherheit vom Input-Namen
abweicht (z.B. Jahrgang/Edition fehlt, offensichtlicher Tippfehler im Input).
Wenn der Input-Name korrekt oder nur minimal abweichend ist: `wert: null`.
- **beschreibung**: Kurzbeschreibung des Markts direkt aus der Quelle (z.B.
"Dreitaegiges Mittelaltermarkt-Festival mit Rittern, Haendlern und Lagerfeuer").
Nur Text der auf der Quelle steht - kein selbst verfasster Text. Typischerweise

View File

@@ -28,8 +28,9 @@
"properties": {
"felder": {
"type": "object",
"required": ["beschreibung", "website", "strasse", "plz", "stadt", "bundesland", "land", "veranstalter", "start_datum", "end_datum", "oeffnungszeiten", "eintrittspreise", "bild_url"],
"required": ["markt_name", "beschreibung", "website", "strasse", "plz", "stadt", "bundesland", "land", "veranstalter", "start_datum", "end_datum", "oeffnungszeiten", "eintrittspreise", "bild_url"],
"properties": {
"markt_name": {"$ref": "#/$defs/stringFeld"},
"beschreibung": {"$ref": "#/$defs/stringFeld"},
"plz": {"$ref": "#/$defs/stringFeld"},
"land": {"$ref": "#/$defs/stringFeld"},

View File

@@ -8,8 +8,9 @@
"quellen_gesamt": {"type": "array", "items": {"type": "string"}},
"felder": {
"type": "object",
"required": ["beschreibung", "website", "strasse", "plz", "stadt", "bundesland", "land", "veranstalter", "start_datum", "end_datum", "oeffnungszeiten", "eintrittspreise", "bild_url"],
"required": ["markt_name", "beschreibung", "website", "strasse", "plz", "stadt", "bundesland", "land", "veranstalter", "start_datum", "end_datum", "oeffnungszeiten", "eintrittspreise", "bild_url"],
"properties": {
"markt_name": {"type": "object", "required": ["wert", "quellen", "extraktion", "hinweis"], "properties": {"wert": {"anyOf": [{"type": "string"}, {"type": "null"}]}, "hinweis": {"anyOf": [{"type": "string"}, {"type": "null"}]}, "quellen": {"type": "array", "items": {"type": "string"}}, "extraktion": {"type": "string", "enum": ["direkt", "kombiniert"]}}},
"beschreibung": {"type": "object", "required": ["wert", "quellen", "extraktion", "hinweis"], "properties": {"wert": {"anyOf": [{"type": "string"}, {"type": "null"}]}, "hinweis": {"anyOf": [{"type": "string"}, {"type": "null"}]}, "quellen": {"type": "array", "items": {"type": "string"}}, "extraktion": {"type": "string", "enum": ["direkt", "kombiniert"]}}},
"website": {"type": "object", "required": ["wert", "quellen", "extraktion", "hinweis"], "properties": {"wert": {"anyOf": [{"type": "string"}, {"type": "null"}]}, "hinweis": {"anyOf": [{"type": "string"}, {"type": "null"}]}, "quellen": {"type": "array", "items": {"type": "string"}}, "extraktion": {"type": "string", "enum": ["direkt", "kombiniert"]}}},
"strasse": {"type": "object", "required": ["wert", "quellen", "extraktion", "hinweis"], "properties": {"wert": {"anyOf": [{"type": "string"}, {"type": "null"}]}, "hinweis": {"anyOf": [{"type": "string"}, {"type": "null"}]}, "quellen": {"type": "array", "items": {"type": "string"}}, "extraktion": {"type": "string", "enum": ["direkt", "kombiniert"]}}},

View File

@@ -40,6 +40,8 @@
UA: 'UAH'
};
let marktName = $state(untrack(() => market?.name ?? ''));
let description = $state(untrack(() => market?.description ?? ''));
let selectedCountry = $state(untrack(() => market?.country ?? 'DE'));
const currency = $derived(currencyByCountry[selectedCountry] ?? 'EUR');
@@ -190,6 +192,14 @@
export function setAdmission(newAdmission: AdmissionInfo) {
admission = newAdmission;
}
export function setDescription(val: string) {
description = val;
}
export function setName(val: string) {
marktName = val;
}
</script>
{#if error}
@@ -211,7 +221,7 @@
name="name"
type="text"
required
value={market?.name ?? ''}
value={marktName}
placeholder={mode === 'public' ? 'z.B. Ritterturnier zu München' : ''}
/>
@@ -227,8 +237,8 @@
text-sm shadow-sm focus:ring-2 focus:outline-none
dark:border-stone-600 dark:bg-stone-800"
placeholder={mode === 'public' ? 'Beschreibe den Markt kurz...' : ''}
>{market?.description ?? ''}</textarea
>
bind:value={description}
></textarea>
</div>
</fieldset>

View File

@@ -14,6 +14,7 @@
let selected: boolean[] = $state(untrack(() => result.suggestions.map(() => true)));
const fieldLabels: Record<string, string> = {
name: 'Name',
description: 'Beschreibung',
street: 'Straße',
city: 'Stadt',

View File

@@ -84,6 +84,14 @@
}
continue;
}
if (s.field === 'description' && typeof s.suggested_value === 'string') {
marketForm.setDescription(s.suggested_value);
continue;
}
if (s.field === 'name' && typeof s.suggested_value === 'string') {
marketForm.setName(s.suggested_value);
continue;
}
const el = document.querySelector<HTMLInputElement | HTMLTextAreaElement>(
`[name="${s.field}"]`
);