feat: port p-rec-annot line-number command planning

This commit is contained in:
codex
2026-08-07 08:22:40 +03:00
parent b6a2cd06af
commit 10af8c8f12
4 changed files with 43 additions and 0 deletions
@@ -55,6 +55,10 @@ public interface ILineNumberAnnotationFormatter
public sealed record DataLinksPropertyReadResult(bool Success, IReadOnlyDictionary<string, string?> Properties, string? FailureCode = null);
public interface IDataLinksPropertyReader { DataLinksPropertyReadResult ReadProperties(long rowId); }
public sealed record LineNumberFieldResolution(bool Success, IReadOnlyDictionary<string, string?> Values, string? FailureCode = null);
public sealed record LineNumberCandidate(string StableId, long RowId, AnnotationPoint Position, double Rotation, string Layer, string TextStyle, IReadOnlyDictionary<string, string?> Properties);
public sealed record ExistingLineNumberAnnotation(string StableId, string SourceStableId, string Text);
public sealed record LineNumberCommandOperation(string StableId, AnnotationOperationKind Kind, string? Text, AnnotationPoint Position, double Rotation, string Layer, string TextStyle, IReadOnlyList<string> Reasons);
public sealed record LineNumberCommandPlan(IReadOnlyList<LineNumberCommandOperation> Operations);
public sealed record GeometrySnapshot
{
@@ -0,0 +1,20 @@
using PRecAnnot.Abstractions;
namespace PRecAnnot.Core;
public sealed class LineNumberCommandPlanner(ILineNumberAnnotationFormatter formatter)
{
public LineNumberCommandPlan Build(LineNumberAnnotationDefinition definition, IEnumerable<LineNumberCandidate> candidates, IEnumerable<ExistingLineNumberAnnotation> existing)
{
var seen = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
var indexed = existing.GroupBy(x => x.SourceStableId, StringComparer.OrdinalIgnoreCase).ToDictionary(x => x.Key, x => x.First(), StringComparer.OrdinalIgnoreCase);
var result = new List<LineNumberCommandOperation>();
foreach (var candidate in candidates.OrderBy(x => x.StableId, StringComparer.Ordinal))
{
if (!seen.Add(candidate.StableId)) { result.Add(new(candidate.StableId, AnnotationOperationKind.Conflict, null, candidate.Position, candidate.Rotation, candidate.Layer, candidate.TextStyle, ["DUPLICATE_CANDIDATE"])); continue; }
var text = formatter.Format(definition, candidate.Properties);
if (!text.Success) { result.Add(new(candidate.StableId, AnnotationOperationKind.Skip, null, candidate.Position, candidate.Rotation, candidate.Layer, candidate.TextStyle, [text.ErrorCode ?? "FORMAT_FAILED"])); continue; }
result.Add(new(candidate.StableId, indexed.ContainsKey(candidate.StableId) ? AnnotationOperationKind.Update : AnnotationOperationKind.Create, text.Text, candidate.Position, candidate.Rotation, candidate.Layer, candidate.TextStyle, Array.Empty<string>()));
}
return new(result);
}
}
@@ -35,6 +35,12 @@ public sealed class EntryPoint : IExtensionApplication
[CommandMethod("PXP-OA")]
public void OrthoAnnotateCommand() => RunDiagnostic("PXP-OA");
[CommandMethod("PXP-LN")] public void AnnotateLineNumbersCommand() => RunDiagnostic("PXP-LN");
[CommandMethod("PXP-ULN")] public void UpdateLineNumbersCommand() => RunDiagnostic("PXP-ULN");
[CommandMethod("PXP-ESN")] public void EditLineNumberStartCommand() => RunDiagnostic("PXP-ESN");
[CommandMethod("PXP-ELN")] public void EditLineNumbersCommand() => RunDiagnostic("PXP-ELN");
[CommandMethod("PXP-RMP")] public void ResetMarkerPositionCommand() => RunDiagnostic("PXP-RMP");
[CommandMethod("PHP_REC_AUTOMATION_START", CommandFlags.Session)]
public void AutomationStartCommand() => FirstLimitedApplyAutomationBootstrap.Arm();
@@ -0,0 +1,13 @@
using PRecAnnot.Abstractions;
using PRecAnnot.Core;
namespace PRecAnnot.Core.Tests;
public sealed class LineNumberCommandPlannerTests
{
[Fact] public void ProducesDeterministicCreateUpdateAndDuplicateDecisions()
{
var def = new LineNumberAnnotationDefinition { Nickname="LN", Tags=[new() { Field="EngineeringItems.LineNumber" }] };
var c = new LineNumberCandidate("b", 1, new(0,0), 0, "PXP", "Standard", new Dictionary<string,string?> { ["EngineeringItems.LineNumber"]="L-2" });
var plan = new LineNumberCommandPlanner(new LineNumberAnnotationFormatter()).Build(def, [c, c, c with { StableId="a" }], [new("x", "b", "old")]);
Assert.Collection(plan.Operations, x => Assert.Equal(AnnotationOperationKind.Create,x.Kind), x => Assert.Equal(AnnotationOperationKind.Update,x.Kind), x => Assert.Equal(AnnotationOperationKind.Conflict,x.Kind));
}
}