backend phase 14 & frontend phase 7

This commit is contained in:
hamid
2026-07-09 15:30:03 +03:30
parent de53f9d8a6
commit 93cc5ecb98
101 changed files with 12930 additions and 39 deletions
@@ -0,0 +1,31 @@
#nullable enable
using Baya.Application.Contracts.Reviews;
using Microsoft.Extensions.Options;
namespace Baya.Infrastructure.CrossCutting.Seams;
/// <summary>
/// Deterministic mock <see cref="IReviewModerationService"/> (b14) — a keyword filter / pass-through with no
/// external call. A banned-word hit → <see cref="ModerationDecision.Reject"/>; otherwise clean text → a
/// human-review <see cref="ModerationDecision.Flag"/> by default (so the publish gate holds), or
/// <see cref="ModerationDecision.Approve"/> when <see cref="ReviewModerationOptions.AutoApproveClean"/> is set.
/// The real text classifier / LLM endpoint swaps in by a registration change only — the moderation command
/// keeps decision authority and the human override, so it never touches the handler.
/// </summary>
public sealed class MockReviewModerationService(IOptions<SeamOptions> options) : IReviewModerationService
{
private readonly ReviewModerationOptions _options = options.Value.ReviewModeration;
public ValueTask<ModerationVerdict> ScreenAsync(string? reviewText, CancellationToken cancellationToken = default)
{
var text = reviewText?.Trim() ?? string.Empty;
var hit = _options.BannedWords.FirstOrDefault(
w => !string.IsNullOrWhiteSpace(w) && text.Contains(w, StringComparison.OrdinalIgnoreCase));
if (hit is not null)
return ValueTask.FromResult(new ModerationVerdict(ModerationDecision.Reject, $"banned_word:{hit}"));
var decision = _options.AutoApproveClean ? ModerationDecision.Approve : ModerationDecision.Flag;
return ValueTask.FromResult(new ModerationVerdict(decision, "clean"));
}
}
@@ -20,6 +20,22 @@ public sealed class SeamOptions
public BnplOptions Bnpl { get; set; } = new();
public CurrencyOptions Currency { get; set; } = new();
public BankTransferOptions BankTransfer { get; set; } = new();
public ReviewModerationOptions ReviewModeration { get; set; } = new();
}
/// <summary>
/// Tunes the mock <c>IReviewModerationService</c> (b14 AI review pre-screen). By default clean text returns a
/// human-review <c>Flag</c> (keeping the publish gate on); a banned-word hit returns <c>Reject</c>. Set
/// <see cref="AutoApproveClean"/> to have clean text auto-<c>Approve</c> (auto-publish). The real text
/// classifier / LLM endpoint ignores these knobs.
/// </summary>
public sealed class ReviewModerationOptions
{
/// <summary>When true, clean text is auto-approved (auto-published) instead of flagged for human review.</summary>
public bool AutoApproveClean { get; set; }
/// <summary>Case-insensitive substrings that mark a review for rejection.</summary>
public List<string> BannedWords { get; set; } = ["scam", "fraud", "کلاهبردار"];
}
/// <summary>
@@ -1,6 +1,7 @@
using Baya.Application.Contracts.Common;
using Baya.Application.Contracts.Invoices;
using Baya.Application.Contracts.Payments;
using Baya.Application.Contracts.Reviews;
using Baya.Infrastructure.CrossCutting.Seams;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
@@ -80,6 +81,12 @@ public static class ServiceCollectionExtension
// the payout status machine + the nurse_payout_booking_links UNIQUE remain the irreversible-transfer backstop.
services.AddSingleton<IBankTransferProvider, MockBankTransferProvider>();
// AI review moderation (backend-phase-14). The mock is a keyword filter / pass-through (clean → human
// flag by default so the publish gate holds; banned word → reject; config toggle auto-approves clean).
// A real text classifier / LLM endpoint swaps in by a registration change only — ModerateReviewCommand
// keeps decision authority + the human override, so the real impl never touches the handler.
services.AddSingleton<IReviewModerationService, MockReviewModerationService>();
return services;
}
}