Implement step 17 modules: threads/messages, notifications with automation (submission.created, idea.created, grant_call.opened), innovation test feedback, admin panel (submissions, verify, metrics, grant call status), Middleman adaptation and partnerships. Split multi-type files into one type per file and replace object-valued dictionaries with string-valued maps in data contracts (migrations 0006/0007).
114 lines
4.4 KiB
C#
114 lines
4.4 KiB
C#
using TarninaHub.Application.Abstractions;
|
|
using TarninaHub.Application.Dtos;
|
|
using TarninaHub.Application.Matchmaking;
|
|
|
|
namespace TarninaHub.Infrastructure.Assist;
|
|
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
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<MiddlemanResponse?> 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<string> BuildResources(InnovationResponse innovation)
|
|
{
|
|
var resources = new List<string>
|
|
{
|
|
"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;
|
|
}
|
|
}
|