feat: Chemikalien-Recherche über lokale RiSU-Stoffliste (kein KI-Aufruf mehr)
Ersetzt den ursprünglichen KI-Websuche-Ansatz (GESTIS scheiterte sowohl über Websuche als auch über direkten API-Zugriff an fehlenden schulspezifischen Tätigkeitsbeschränkungen) durch einen reinen lokalen Datenbank-Lookup gegen die offizielle RiSU-Stoffliste (1764 Stoffe inkl. Tätigkeitsbeschränkungs-Codes, Legende zur Abfragezeit übersetzt statt in die Datenbank gebacken). Keine KI-Kosten, keine Drittanbieter-Abhängigkeit mehr für diese Funktion. HazardSubstance.AgeRestriction zu ActivityRestriction umbenannt, da die Tätigkeitsbeschränkung mehr abdeckt als reine Altersgrenzen. Details und verworfene Zwischenstände in TODO.md. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -80,32 +80,81 @@ public partial class GhsPictogramOption(GhsPictogram value, string label) : Obse
|
||||
public partial class HazardSubstanceEditItem : ObservableObject
|
||||
{
|
||||
[ObservableProperty] private string _name = "";
|
||||
[ObservableProperty] private string _cas = "";
|
||||
[ObservableProperty] private string _amount = "";
|
||||
[ObservableProperty] private string _signalWord = "";
|
||||
[ObservableProperty] private string _hStatements = "";
|
||||
[ObservableProperty] private string _pStatements = "";
|
||||
[ObservableProperty] private string _activityRestriction = "";
|
||||
[ObservableProperty] private bool _isResearching;
|
||||
[ObservableProperty] private string _researchError = "";
|
||||
public ObservableCollection<GhsPictogramOption> PictogramOptions { get; }
|
||||
public Action<HazardSubstanceEditItem>? OnRemove { get; set; }
|
||||
|
||||
/// Vom Wizard gesetzt (nur wenn eine KI-Backend-Anmeldung vorliegt, siehe CanUseAi — der
|
||||
/// Lookup selbst ruft aber keine KI auf, sondern nur die lokale RiSU-Stoffliste auf dem
|
||||
/// Server, siehe ai-backend/substance.php): führt die eigentliche Chemikalien-Recherche aus.
|
||||
/// Wird vor der Anzeige in der UI (Objektinitialisierer in NewSubstanceItem) endgültig
|
||||
/// gesetzt, daher genügt eine einmalig berechnete Eigenschaft ohne Änderungsbenachrichtigung.
|
||||
public Func<string, Task<AiSubstanceResearchResponse>>? OnResearch { get; set; }
|
||||
public bool CanResearch => OnResearch is not null;
|
||||
|
||||
public HazardSubstanceEditItem(HazardSubstance? source = null)
|
||||
{
|
||||
PictogramOptions = new ObservableCollection<GhsPictogramOption>(
|
||||
GhsPictogramDisplay.BuildOptions(source?.GhsPictograms));
|
||||
if (source is null) return;
|
||||
Name = source.Name;
|
||||
Cas = source.Cas;
|
||||
Amount = source.Amount;
|
||||
SignalWord = source.SignalWord;
|
||||
HStatements = source.HStatements;
|
||||
PStatements = source.PStatements;
|
||||
ActivityRestriction = source.ActivityRestriction;
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void Remove() => OnRemove?.Invoke(this);
|
||||
|
||||
[RelayCommand]
|
||||
private async Task Research()
|
||||
{
|
||||
if (OnResearch is null) return;
|
||||
if (string.IsNullOrWhiteSpace(Name)) { ResearchError = "Stoffname eingeben."; return; }
|
||||
|
||||
ResearchError = ""; IsResearching = true;
|
||||
try
|
||||
{
|
||||
var result = await OnResearch(Name.Trim());
|
||||
Name = result.Name;
|
||||
Cas = result.Cas;
|
||||
SignalWord = result.SignalWord;
|
||||
HStatements = result.HStatements;
|
||||
PStatements = result.PStatements;
|
||||
ActivityRestriction = result.ActivityRestriction;
|
||||
var pictograms = result.GhsPictograms
|
||||
.Select(GhsPictogramDisplay.FromCode).Where(p => p.HasValue).Select(p => p!.Value).ToHashSet();
|
||||
foreach (var option in PictogramOptions) option.IsSelected = pictograms.Contains(option.Value);
|
||||
}
|
||||
catch (AiBackendException ex)
|
||||
{
|
||||
ResearchError = ex.Message;
|
||||
}
|
||||
finally
|
||||
{
|
||||
IsResearching = false;
|
||||
}
|
||||
}
|
||||
|
||||
public HazardSubstance ToModel() => new()
|
||||
{
|
||||
Name = Name.Trim(),
|
||||
Cas = Cas.Trim(),
|
||||
Amount = Amount.Trim(),
|
||||
SignalWord = SignalWord.Trim(),
|
||||
HStatements = HStatements.Trim(),
|
||||
PStatements = PStatements.Trim(),
|
||||
ActivityRestriction = ActivityRestriction.Trim(),
|
||||
GhsPictograms = PictogramOptions.Where(o => o.IsSelected).Select(o => o.Value).ToList(),
|
||||
};
|
||||
}
|
||||
@@ -217,10 +266,25 @@ public partial class HazardAssessmentWizardViewModel : ObservableObject
|
||||
|
||||
private HazardSubstanceEditItem NewSubstanceItem(HazardSubstance? source = null)
|
||||
{
|
||||
var item = new HazardSubstanceEditItem(source) { OnRemove = RemoveSubstance };
|
||||
var item = new HazardSubstanceEditItem(source)
|
||||
{
|
||||
OnRemove = RemoveSubstance,
|
||||
OnResearch = CanUseAi ? ResearchSubstanceAsync : null,
|
||||
};
|
||||
return item;
|
||||
}
|
||||
|
||||
/// Führt die eigentliche Chemikalien-Recherche aus — ein reiner Datenbank-Lookup gegen die
|
||||
/// lokale RiSU-Stoffliste auf dem Server (ai-backend/stoffliste.php), keine KI beteiligt und
|
||||
/// damit auch kein IsAiAssisted-Hinweis nötig. Die Anmeldung läuft trotzdem über dieselbe
|
||||
/// KI-Backend-Sitzung wie die echten KI-Funktionen (gemeinsamer Host/Token).
|
||||
private async Task<AiSubstanceResearchResponse> ResearchSubstanceAsync(string substanceName)
|
||||
{
|
||||
var token = _aiSettings!.GetToken()
|
||||
?? throw new AiBackendException("Nicht angemeldet. Bitte in den Einstellungen bei der KI-Unterstützung anmelden.");
|
||||
return await _ai!.RequestSubstanceResearchAsync(substanceName, token);
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void AddSubstance() => Substances.Add(NewSubstanceItem());
|
||||
|
||||
|
||||
Reference in New Issue
Block a user