a scoring model

This commit is contained in:
Paul Schneider 2026-05-24 19:35:35 +01:00
commit 8a46341755
17 changed files with 832 additions and 3 deletions

View file

@ -0,0 +1,77 @@
// Yavsc.Controllers.Kyc/ConsultantController.cs
namespace Yavsc.Controllers.Kyc
{
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Yavsc.Models;
using Yavsc.Models.Kyc;
using Yavsc.Services.Kyc;
using Yavsc.ViewModels.Kyc;
/// <summary>
/// Accessible aux utilisateurs avec le rôle "Consultant".
/// Ne voit qu'un score agrégé — jamais de déclarations, jamais d'identité.
/// </summary>
[Authorize(Roles = "Consultant")]
[Route("kyc/score")]
public class ConsultantController : Controller
{
private readonly ITrustTokenService _tokenService;
private readonly ApplicationDbContext _db;
public ConsultantController(
ITrustTokenService tokenService,
ApplicationDbContext db)
{
_tokenService = tokenService;
_db = db;
}
/// <summary>
/// POST kyc/score
/// Body : { subjectEmail } ou { externalToken }
/// Retourne uniquement le score agrégé.
/// </summary>
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Query(ScoreQueryModel input)
{
if (!ModelState.IsValid)
return BadRequest(ModelState);
TrustToken token = string.IsNullOrEmpty(input.ExternalToken)
? await _tokenService.GetOrCreateFromEmailAsync(input.SubjectEmail)
: await _tokenService.GetOrCreateFromTrustedPartyAsync(input.ExternalToken);
// Chargement du score avec stats agrégées — aucune déclaration exposée
var stats = await _db.TrustDeclarations
.Where(d => d.TrustTokenId == token.Id
&& d.Status == ModerationStatus.Approved)
.GroupBy(d => d.TrustTokenId)
.Select(g => new {
Count = g.Count(),
LastUpdated = g.Max(d => d.SubmittedAt)
})
.FirstOrDefaultAsync();
var view = new TrustScoreView
{
Score = token.TrustScore ?? 50,
Category = ScoreToCategory(token.TrustScore ?? 50),
DeclarationCount = stats?.Count ?? 0,
LastUpdated = stats?.LastUpdated ?? token.CreatedAt
};
return View(view);
}
private static string ScoreToCategory(int score) => score switch
{
>= 75 => "Fiable",
>= 40 => "Prudence",
_ => "Risque"
};
}
}

View file

@ -0,0 +1,79 @@
// Yavsc.Controllers.Kyc/DeclarantController.cs
namespace Yavsc.Controllers.Kyc
{
using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Yavsc.Models;
using Yavsc.Models.Kyc;
using Yavsc.Services.Kyc;
using Yavsc.ViewModels.Kyc;
/// <summary>
/// Accessible à tout utilisateur authentifié.
/// Le déclarant soumet une déclaration sur un sujet pseudonymisé.
/// </summary>
[Authorize]
[Route("kyc/declare")]
public class DeclarantController : Controller
{
private readonly IModerationPipelineService _pipeline;
private readonly ITrustTokenService _tokenService;
private readonly UserManager<ApplicationUser> _userManager;
public DeclarantController(
IModerationPipelineService pipeline,
ITrustTokenService tokenService,
UserManager<ApplicationUser> userManager)
{
_pipeline = pipeline;
_tokenService = tokenService;
_userManager = userManager;
}
/// <summary>
/// GET kyc/declare — formulaire de déclaration
/// </summary>
[HttpGet]
public IActionResult Index() => View();
/// <summary>
/// POST kyc/declare
/// Body : { subjectEmail, content, sentiment }
/// Le subjectEmail est immédiatement transformé en token — jamais stocké.
/// </summary>
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Submit(DeclarationInputModel input)
{
if (!ModelState.IsValid)
return View("Index", input);
// Token du sujet — depuis email ou token tiers
TrustToken subjectToken = string.IsNullOrEmpty(input.ExternalToken)
? await _tokenService.GetOrCreateFromEmailAsync(input.SubjectEmail)
: await _tokenService.GetOrCreateFromTrustedPartyAsync(input.ExternalToken);
// Token du déclarant — depuis son propre email confirmé
var user = await _userManager.GetUserAsync(User);
if (!await _userManager.IsEmailConfirmedAsync(user))
return Forbid(); // Email non confirmé → pas de déclaration
var declarantToken = await _tokenService
.GetOrCreateFromEmailAsync(user.Email);
await _pipeline.SubmitDeclarationAsync(
subjectToken.Id,
declarantToken.Id,
input.Content,
input.Sentiment);
return RedirectToAction("Submitted");
}
[HttpGet("submitted")]
public IActionResult Submitted() => View();
}
}

View file

@ -0,0 +1,76 @@
// Yavsc.Controllers.Kyc/ModerationController.cs
namespace Yavsc.Controllers.Kyc
{
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Yavsc.Server.Helpers;
using Yavsc.Services.Kyc;
using Yavsc.ViewModels.Kyc;
/// <summary>
/// Accessible uniquement au rôle "Moderator".
/// Voit les déclarations en attente + flags, prend les décisions.
/// </summary>
[Authorize(Roles = "Moderator")]
[Route("kyc/moderation")]
public class ModerationController : Controller
{
private readonly IModerationPipelineService _pipeline;
public ModerationController(IModerationPipelineService pipeline)
{
_pipeline = pipeline;
}
/// <summary>
/// GET kyc/moderation — queue de modération
/// Flagged en tête, puis Pending par date de soumission.
/// </summary>
[HttpGet]
public async Task<IActionResult> Index()
{
var queue = await _pipeline.GetQueueAsync();
return View(queue);
}
/// <summary>
/// POST kyc/moderation/approve
/// </summary>
[HttpPost("approve")]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Approve(ModerationDecisionModel input)
{
if (!ModelState.IsValid)
return RedirectToAction("Index");
var moderatorId = User.GetUserId();
await _pipeline.ApproveAsync(input.DeclarationId, input.ScoreDelta, moderatorId);
return RedirectToAction("Index");
}
/// <summary>
/// POST kyc/moderation/reject
/// </summary>
[HttpPost("reject")]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Reject(ModerationDecisionModel input)
{
var moderatorId = User.GetUserId();
await _pipeline.RejectAsync(input.DeclarationId, moderatorId);
return RedirectToAction("Index");
}
/// <summary>
/// POST kyc/moderation/redact
/// </summary>
[HttpPost("redact")]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Redact(ModerationDecisionModel input)
{
var moderatorId = User.GetUserId();
await _pipeline.RedactAsync(input.DeclarationId, input.ScoreDelta, moderatorId);
return RedirectToAction("Index");
}
}
}