using TarninaHub.Application.Abstractions; using TarninaHub.Application.Dtos; using TarninaHub.Application.Matchmaking; namespace TarninaHub.Infrastructure.Assist; /// /// Middleman Innowacji (F-8). Adapts an innovation to a service form for an /// institution. Uses the configured LLM when available and a deterministic, /// template-based rationale otherwise. /// public sealed class MiddlemanAssistant : IMiddlemanService { private const string SystemPrompt = "Jesteś Middlemanem Innowacji w Małopolskim Hubie Innowacji Społecznych. " + "Dostosowujesz sprawdzoną innowację do formy usługi dla instytucji (JST/NGO). " + "Odpowiadaj prostym językiem, konkretnie (co/komu/jak) i nie wymyślaj faktów."; private readonly IInnovationService _innovations; private readonly ILlmProvider _llmProvider; private readonly ILlmRequestLimiter _llmRequestLimiter; public MiddlemanAssistant( IInnovationService innovations, ILlmProvider llmProvider, ILlmRequestLimiter llmRequestLimiter) { _innovations = innovations; _llmProvider = llmProvider; _llmRequestLimiter = llmRequestLimiter; } public async Task AdaptAsync( MiddlemanRequest request, CancellationToken cancellationToken) { var innovation = await _innovations.GetAsync(request.InnovationId, cancellationToken); if (innovation is null) { return null; } var serviceForm = BuildServiceForm(innovation, request); var rationale = BuildRationale(innovation, request); var fromLlm = false; if (_llmProvider.IsConfigured && _llmRequestLimiter.TryAcquire()) { var profile = request.InstitutionProfile; var userPrompt = $"Innowacja: {innovation.Title}\n" + $"Streszczenie: {innovation.Summary}\n" + $"Opis: {innovation.Description}\n" + $"Etap: {innovation.Stage}\n" + $"Instytucja: {profile?.Type ?? "nieokreślona"} (obszar: {profile?.Area ?? "ogólny"})\n" + $"Odbiorcy: {(profile?.Audiences is { Count: > 0 } audiences ? string.Join(", ", audiences) : "nieokreśleni")}\n" + $"Potrzeby: {request.Needs ?? "ogólne"}\n" + "Zaproponuj formę usługi (co/komu/jak) i uzasadnienie."; var completion = await _llmProvider.CompleteAsync(SystemPrompt, userPrompt, cancellationToken); if (!string.IsNullOrWhiteSpace(completion)) { serviceForm = completion.Trim(); fromLlm = true; } } return new MiddlemanResponse(serviceForm, BuildResources(innovation), rationale, fromLlm); } private static string BuildServiceForm(InnovationResponse innovation, MiddlemanRequest request) { var audiences = request.InstitutionProfile?.Audiences is { Count: > 0 } values ? string.Join(", ", values) : "odbiorcy wskazani przez instytucję"; var area = request.InstitutionProfile?.Area ?? innovation.ChallengeId; return $"Co: {innovation.Title} — {innovation.Summary}. " + $"Komu: {audiences}. " + $"Jak: wdrożyć jako usługę w obszarze '{area}', etap '{innovation.Stage}', " + $"z potrzeby: {request.Needs ?? "zdiagnozowanej lokalnie"}."; } private static string BuildRationale(InnovationResponse innovation, MiddlemanRequest request) { var institution = request.InstitutionProfile?.Type ?? "instytucja"; return $"Innowacja na etapie '{innovation.Stage}' odpowiada profilowi '{institution}' i obszarowi " + $"'{request.InstitutionProfile?.Area ?? innovation.ChallengeId}', dlatego nadaje się do " + "przekształcenia w powtarzalną usługę społeczną."; } private static IReadOnlyList BuildResources(InnovationResponse innovation) { var resources = new List { "Koordynator usługi i zespół realizacyjny", "Budżet na pilotaż oraz pomiar efektów", }; if (!string.IsNullOrWhiteSpace(innovation.Cost)) { resources.Add($"Szacowany koszt: {innovation.Cost}"); } resources.Add("Partnerzy lokalni (NGO/JST/firma) do wdrożenia"); return resources; } }