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 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 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 Rules { get; init; } = []; } public interface IPdfOcrService { Task RecognizePageAsync(string pdfPath, int pageNumber, CancellationToken cancellationToken); } public interface IStandardPdfService { Task<(StandardMetadata Metadata, IReadOnlyList Pages, IReadOnlyList Clauses)> ImportAsync(string pdfPath, string code, string title, CancellationToken cancellationToken); } public interface IOllamaService { Task> GetModelsAsync(CancellationToken cancellationToken); Task 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 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 }; } }