78 lines
5.2 KiB
C#
78 lines
5.2 KiB
C#
using System.Security.Cryptography;
|
|
using System.Text.Json;
|
|
using DocumentAutomation.Shared;
|
|
|
|
namespace StandardsWorkbench.Core;
|
|
|
|
public sealed record StandardMetadata
|
|
{
|
|
public required string StandardId { get; init; }
|
|
public required string Code { get; init; }
|
|
public required string Title { get; init; }
|
|
public string Revision { get; init; } = "";
|
|
public DateOnly? EffectiveFrom { get; init; }
|
|
public required string SourceFileSha256 { get; init; }
|
|
public int PageCount { get; init; }
|
|
public bool TextLayerDetected { get; init; }
|
|
public DateTime ImportedAt { get; init; } = DateTime.UtcNow;
|
|
public string Status { get; init; } = "Imported";
|
|
}
|
|
|
|
public sealed record PdfPageText(int PageNumber, string Text, bool TextLayerDetected, string Status, IReadOnlyList<string> Blocks);
|
|
public sealed record StandardClause(string StandardId, string ClauseId, string Heading, string Text, int StartPage, int EndPage, double ExtractionConfidence);
|
|
public sealed record CandidateRule
|
|
{
|
|
public required string CandidateId { get; init; }
|
|
public required string StandardId { get; init; }
|
|
public required string ClauseId { get; init; }
|
|
public int SourcePage { get; init; }
|
|
public required string SourceText { get; init; }
|
|
public required string Title { get; init; }
|
|
public string[] Scope { get; init; } = ["DOCX"];
|
|
public string RuleKind { get; init; } = "ExpertReview";
|
|
public string Target { get; init; } = "Document";
|
|
public Dictionary<string, string> Parameters { get; init; } = [];
|
|
public string Severity { get; init; } = "Warning";
|
|
public string AutomationLevel { get; init; } = "Manual";
|
|
public double Confidence { get; init; }
|
|
public bool RequiresHumanApproval { get; init; } = true;
|
|
public string Status { get; init; } = "New";
|
|
}
|
|
|
|
public sealed record ApprovedRulesProfile
|
|
{
|
|
public required string StandardId { get; init; }
|
|
public required string Code { get; init; }
|
|
public required string Revision { get; init; }
|
|
public required string SourceFileSha256 { get; init; }
|
|
public string ProfileVersion { get; init; } = "1.0";
|
|
public string MinimumRulesEngineVersion { get; init; } = "1.0";
|
|
public DateTime ExportedAt { get; init; } = DateTime.UtcNow;
|
|
public IReadOnlyList<CandidateRule> Rules { get; init; } = [];
|
|
}
|
|
|
|
public interface IPdfOcrService { Task<PdfPageText> RecognizePageAsync(string pdfPath, int pageNumber, CancellationToken cancellationToken); }
|
|
public interface IStandardPdfService { Task<(StandardMetadata Metadata, IReadOnlyList<PdfPageText> Pages, IReadOnlyList<StandardClause> Clauses)> ImportAsync(string pdfPath, string code, string title, CancellationToken cancellationToken); }
|
|
public interface IOllamaService { Task<IReadOnlyList<string>> GetModelsAsync(CancellationToken cancellationToken); Task<CandidateRule?> SuggestCandidateAsync(StandardClause clause, string model, CancellationToken cancellationToken); }
|
|
|
|
public static class ProfileStore
|
|
{
|
|
public static string Sha256(string path) { using var stream = File.OpenRead(path); return Convert.ToHexString(SHA256.HashData(stream)).ToLowerInvariant(); }
|
|
public static void Export(string directory, ApprovedRulesProfile profile)
|
|
{
|
|
Directory.CreateDirectory(directory); var options = new JsonSerializerOptions { WriteIndented = true }; File.WriteAllText(Path.Combine(directory, "manifest.json"), JsonSerializer.Serialize(profile, options)); File.WriteAllText(Path.Combine(directory, "approved-rules.json"), JsonSerializer.Serialize(profile.Rules, options)); File.WriteAllText(Path.Combine(directory, "references.json"), JsonSerializer.Serialize(profile.Rules.Select(r => new { r.ClauseId, r.SourcePage, r.StandardId }), options)); File.WriteAllText(Path.Combine(directory, "signature.sha256"), Sha256(Path.Combine(directory, "approved-rules.json")));
|
|
}
|
|
}
|
|
|
|
public sealed class RulesEngine
|
|
{
|
|
public IReadOnlyList<RuleEvaluationResult> Evaluate(DocumentInspectionResult document, ApprovedRulesProfile profile)
|
|
=> profile.Rules.Where(r => r.Status is "Approved" or "Implemented" or "Tested").Select(r => EvaluateOne(document, r)).ToArray();
|
|
|
|
private static RuleEvaluationResult EvaluateOne(DocumentInspectionResult d, CandidateRule rule)
|
|
{
|
|
var status = rule.RuleKind switch { "SectionCount" => d.Sections > 0 ? RuleEvaluationStatus.Pass : RuleEvaluationStatus.Fail, "ContentControlPresence" => d.ContentControls > 0 ? RuleEvaluationStatus.Pass : RuleEvaluationStatus.Fail, "BookmarkPresence" => d.Bookmarks > 0 ? RuleEvaluationStatus.Pass : RuleEvaluationStatus.Fail, "CommentsAbsent" => d.Comments == 0 ? RuleEvaluationStatus.Pass : RuleEvaluationStatus.Fail, "TrackedChangesAbsent" => !d.HasTrackedChanges ? RuleEvaluationStatus.Pass : RuleEvaluationStatus.Fail, _ => RuleEvaluationStatus.NeedsReview };
|
|
return new RuleEvaluationResult { RuleId = rule.CandidateId, Status = status, Message = status == RuleEvaluationStatus.Pass ? "Проверка пройдена." : status == RuleEvaluationStatus.Fail ? "Проверка не пройдена." : "Требуется экспертная проверка.", ClauseId = rule.ClauseId, SourcePage = rule.SourcePage, DocumentLocation = rule.Target, CanAutoFix = false };
|
|
}
|
|
}
|