feat: add p-rec-annot line-number command preview service

This commit is contained in:
codex
2026-08-07 08:47:48 +03:00
parent 315d36a36a
commit 0a7f05989a
3 changed files with 56 additions and 0 deletions
@@ -62,6 +62,7 @@ public sealed record LineNumberCommandPlan(IReadOnlyList<LineNumberCommandOperat
public sealed record LineNumberApplyPreflight(string StableId, int MarkerNumber, string Text, AnnotationPoint Position, double Rotation, string Layer, string TextStyle);
public sealed record LineNumberApplyResult(int Created, int Updated, int Unchanged);
public sealed record LineNumberListApplyPreflight(string StableId, AnnotationPoint Position, double Rotation, IReadOnlyList<LineNumberApplyPreflight> Rows);
public sealed record LineNumberPreviewResult(bool Success, LineNumberCommandPlan? Plan, IReadOnlyList<LineNumberApplyPreflight> Apply, string? FailureCode = null);
public sealed record LineNumberTextAsset(string Layer, string TextStyle, double Size, string TextValue, Point2Dto Position);
public sealed record LineNumberLineAsset(string Layer, Point2Dto Start, Point2Dto End);
public sealed record LineNumberMarkerAsset(string Layer, double MaskScaleFactor, IReadOnlyList<LineNumberTextAsset> Texts, IReadOnlyList<LineNumberLineAsset> Lines);
@@ -0,0 +1,20 @@
using PRecAnnot.Abstractions;
namespace PRecAnnot.Core;
public sealed class LineNumberCommandPreviewService(LineNumberCommandPlanner planner, LineNumberFieldResolver fieldResolver)
{
public LineNumberPreviewResult Preview(LineNumberAnnotationDefinition definition, IEnumerable<LineNumberCandidate> candidates, IEnumerable<ExistingLineNumberAnnotation> existing)
{
var resolved = new List<LineNumberCandidate>();
foreach (var candidate in candidates.OrderBy(item => item.StableId, StringComparer.Ordinal))
{
var fields = definition.Tags.Select(tag => tag.Field).Where(field => !string.IsNullOrWhiteSpace(field));
var properties = fieldResolver.Resolve(candidate.RowId, fields);
if (!properties.Success) return new(false, null, Array.Empty<LineNumberApplyPreflight>(), properties.FailureCode);
resolved.Add(candidate with { Properties = properties.Values });
}
var plan = planner.Build(definition, resolved, existing);
return new(true, plan, LineNumberCommandPlanner.PrepareApply(plan));
}
}
@@ -0,0 +1,35 @@
using PRecAnnot.Abstractions;
using PRecAnnot.Core;
namespace PRecAnnot.Core.Tests;
public sealed class LineNumberCommandPreviewServiceTests
{
[Fact]
public void UsesTheSameResolvedPropertiesForPreviewAndApply()
{
var reader = new FakeReader(new(true, new Dictionary<string, string?> { ["EngineeringItems.LineNumber"] = "L-42" }));
var service = new LineNumberCommandPreviewService(new(new LineNumberAnnotationFormatter()), new(reader));
var definition = new LineNumberAnnotationDefinition { Nickname = "LN", Tags = [new() { Field = "EngineeringItems.LineNumber" }] };
var candidate = new LineNumberCandidate("component", 42, new(1, 2), 0, "PXP", "Standard", new Dictionary<string, string?>());
var result = service.Preview(definition, [candidate], Array.Empty<ExistingLineNumberAnnotation>());
Assert.True(result.Success);
Assert.Equal("L-42", Assert.Single(result.Apply).Text);
Assert.Equal(42, reader.LastRowId);
}
[Fact]
public void PropagatesTypedDataLinksFailureWithoutPlanning()
{
var service = new LineNumberCommandPreviewService(new(new LineNumberAnnotationFormatter()), new(new FakeReader(new(false, new Dictionary<string, string?>(), "DATALINKS_PROPERTY_READ_FAILED"))));
var definition = new LineNumberAnnotationDefinition { Nickname = "LN", Tags = [new() { Field = "EngineeringItems.LineNumber" }] };
var candidate = new LineNumberCandidate("component", 42, new(), 0, "PXP", "Standard", new Dictionary<string, string?>());
Assert.Equal("DATALINKS_PROPERTY_READ_FAILED", service.Preview(definition, [candidate], Array.Empty<ExistingLineNumberAnnotation>()).FailureCode);
}
private sealed class FakeReader(DataLinksPropertyReadResult result) : IDataLinksPropertyReader
{
public long LastRowId { get; private set; }
public DataLinksPropertyReadResult ReadProperties(long rowId) { LastRowId = rowId; return result; }
}
}