Diagnose-Feinschliff: Art-Abgleich, nur Fließtext, konsequentes Du

Die angegebene Art ist jetzt eine unbestätigte Angabe: Claude prüft
selbst, welche Pflanze zu sehen ist, beurteilt bei Abweichung die
Pflanze im Bild und meldet matchesSpecies=false — die App zeigt dann
einen Warnhinweis im Ergebnis-Sheet. Prompt verbietet HTML/Markdown
(kein <br>) und schreibt durchgängiges Duzen vor.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
cschlaefke 2026-07-23 22:39:45 +02:00
parent 18b060413b
commit b665bae95f
6 changed files with 68 additions and 4 deletions

View file

@ -131,6 +131,7 @@ async function askClaude<T>(
*/
interface DiagnosisResult {
healthy: boolean;
matchesSpecies: boolean;
summary: string;
details: string;
treatment: string;
@ -168,8 +169,15 @@ export const diagnosePlant = onCall(
request.data.speciesHint.slice(0, 200) :
"";
// Die Art ist nur eine UNBESTÄTIGTE Angabe des Besitzers — Claude soll
// selbst prüfen, ob das Foto wirklich diese Pflanze zeigt (der Nutzer
// kann versehentlich das Foto einer anderen Pflanze hochladen).
const speciesPart = speciesHint ?
`Es handelt sich vermutlich um "${speciesHint}". ` :
`Laut Besitzer soll es sich um "${speciesHint}" handeln — verlasse ` +
"dich NICHT darauf, sondern prüfe selbst, welche Pflanze auf dem " +
"Foto zu sehen ist. Zeigt das Foto eindeutig eine andere Art, " +
"beurteile die Pflanze, die tatsächlich zu sehen ist, und nenne " +
"ihre Art im Kurzbefund. " :
"";
const diagnosis = await askClaude<DiagnosisResult>(
imageBase64,
@ -177,10 +185,14 @@ export const diagnosePlant = onCall(
`der Pflanze auf dem Foto. ${speciesPart}` +
"Achte auf Blattverfärbungen, Flecken, Schädlinge, Schimmel, " +
"Trockenheit, Überwässerung und ähnliche Anzeichen. " +
"Antworte auf Deutsch, in einfacher, freundlicher Sprache " +
"(auch für ältere Menschen gut verständlich, keine Fachbegriffe ohne " +
"Erklärung). Antworte NUR mit JSON im Format " +
"Sprache: Deutsch, einfach und freundlich (auch für ältere Menschen " +
"gut verständlich, keine Fachbegriffe ohne Erklärung). Sprich die " +
"Person immer mit „du“ an, niemals mit „Sie“. Schreibe reinen " +
"Fließtext ohne HTML-Tags (kein <br> o. Ä.) und ohne " +
"Markdown-Formatierung. Antworte NUR mit JSON im Format " +
'{"healthy": <true wenn die Pflanze gesund wirkt, sonst false>, ' +
'"matchesSpecies": <false NUR wenn eine Art angegeben wurde und das ' +
"Foto eindeutig eine andere Pflanzenart zeigt, sonst true>, " +
'"summary": "<Kurzbefund in 1 Satz>", ' +
'"details": "<2-3 Sätze: was ist zu sehen und was ist die ' +
'wahrscheinliche Ursache>", ' +

View file

@ -8,6 +8,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
class DiagnosisResult {
const DiagnosisResult({
required this.healthy,
required this.matchesSpecies,
required this.summary,
required this.details,
required this.treatment,
@ -15,6 +16,11 @@ class DiagnosisResult {
});
final bool healthy;
/// false, wenn das Foto laut KI eindeutig eine andere Pflanzenart zeigt
/// als die im Profil hinterlegte (z. B. versehentlich falsches Foto).
final bool matchesSpecies;
final String summary;
final String details;
final String treatment;
@ -43,6 +49,7 @@ class PlantDiagnosisService {
final data = response.data;
return DiagnosisResult(
healthy: data['healthy'] as bool? ?? false,
matchesSpecies: data['matchesSpecies'] as bool? ?? true,
summary: data['summary'] as String? ?? '',
details: data['details'] as String? ?? '',
treatment: data['treatment'] as String? ?? '',

View file

@ -248,6 +248,34 @@ class _DiagnosisSectionState extends ConsumerState<_DiagnosisSection> {
controller: scrollController,
padding: const EdgeInsets.all(24),
children: [
if (!result.matchesSpecies) ...[
Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: theme.colorScheme.errorContainer,
borderRadius: BorderRadius.circular(12),
),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Icon(
Icons.swap_horiz,
color: theme.colorScheme.onErrorContainer,
),
const SizedBox(width: 12),
Expanded(
child: Text(
l10n.diagnosisSpeciesMismatch(widget.plant.nickname),
style: theme.textTheme.bodyMedium?.copyWith(
color: theme.colorScheme.onErrorContainer,
),
),
),
],
),
),
const SizedBox(height: 16),
],
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [

View file

@ -233,6 +233,12 @@
"diagnosisTreatmentTitle": "Was du tun kannst",
"diagnosisPreventionTitle": "So beugst du vor",
"diagnosisDisclaimer": "Die Einschätzung kommt von einer KI und kann falsch liegen. Bei wertvollen Pflanzen im Zweifel eine Gärtnerei fragen.",
"diagnosisSpeciesMismatch": "Das Foto zeigt offenbar eine andere Pflanze als „{name}“. Die Einschätzung unten bezieht sich auf die Pflanze im Bild.",
"@diagnosisSpeciesMismatch": {
"placeholders": {
"name": { "type": "String" }
}
},
"diagnosisFailed": "Die Untersuchung hat nicht geklappt. Bitte versuche es erneut.",
"saveError": "Speichern fehlgeschlagen. Bitte versuche es erneut.",
"saveErrorDetail": "Speichern fehlgeschlagen ({detail}). Bitte versuche es erneut.",

View file

@ -862,6 +862,12 @@ abstract class AppLocalizations {
/// **'Die Einschätzung kommt von einer KI und kann falsch liegen. Bei wertvollen Pflanzen im Zweifel eine Gärtnerei fragen.'**
String get diagnosisDisclaimer;
/// No description provided for @diagnosisSpeciesMismatch.
///
/// In de, this message translates to:
/// **'Das Foto zeigt offenbar eine andere Pflanze als „{name}“. Die Einschätzung unten bezieht sich auf die Pflanze im Bild.'**
String diagnosisSpeciesMismatch(String name);
/// No description provided for @diagnosisFailed.
///
/// In de, this message translates to:

View file

@ -469,6 +469,11 @@ class AppLocalizationsDe extends AppLocalizations {
String get diagnosisDisclaimer =>
'Die Einschätzung kommt von einer KI und kann falsch liegen. Bei wertvollen Pflanzen im Zweifel eine Gärtnerei fragen.';
@override
String diagnosisSpeciesMismatch(String name) {
return 'Das Foto zeigt offenbar eine andere Pflanze als „$name“. Die Einschätzung unten bezieht sich auf die Pflanze im Bild.';
}
@override
String get diagnosisFailed =>
'Die Untersuchung hat nicht geklappt. Bitte versuche es erneut.';