first commit

This commit is contained in:
2026-07-17 17:45:56 +08:00
commit 04c487823b
5873 changed files with 12227228 additions and 0 deletions
@@ -0,0 +1,742 @@
using System.Text;
using System.Text.Encodings.Web;
using System.Text.Json;
using System.Text.Json.Serialization;
Console.OutputEncoding = Encoding.UTF8;
if (args.Length == 0 || IsHelp(args[0]))
{
PrintUsage();
return 1;
}
try
{
return args[0].Trim().ToLowerInvariant() switch
{
"generate" => Generate(args.Skip(1).ToArray()),
"validate" => Validate(args.Skip(1).ToArray()),
"sample-sleeve" => SampleSleeve(args.Skip(1).ToArray()),
_ => Fail($"Unknown command: {args[0]}")
};
}
catch (Exception ex)
{
Console.Error.WriteLine(ex.Message);
return 1;
}
static int Generate(string[] args)
{
var parsed = Args.Parse(args);
var imagePath = parsed.Required("--image");
var outputPath = parsed.Required("--out");
var textPath = parsed.Optional("--text");
var extraction = TeachingMaterialExtractor.Extract(imagePath, textPath);
WriteJson(outputPath, extraction);
Console.WriteLine(Path.GetFullPath(outputPath));
return 0;
}
static int SampleSleeve(string[] args)
{
var outputPath = args.Length > 0
? args[0]
: Path.Combine("runtime", "rule_templates", "tight_fit_sleeve_requires_disassembly_access.json");
var extraction = TeachingMaterialExtractor.BuildSleeveRemovalTemplate("built_in_sample", null);
WriteJson(outputPath, extraction);
Console.WriteLine(Path.GetFullPath(outputPath));
return 0;
}
static int Validate(string[] args)
{
if (args.Length < 1)
return Fail("validate requires a template path.");
var template = JsonSerializer.Deserialize<RuleTemplatePackage>(File.ReadAllText(args[0], Encoding.UTF8), JsonOptions.Default)
?? throw new InvalidOperationException("Cannot parse rule template package.");
var errors = TemplateValidator.Validate(template);
if (errors.Count > 0)
{
foreach (var error in errors)
Console.Error.WriteLine(error);
return 2;
}
Console.WriteLine("valid");
return 0;
}
static void WriteJson<T>(string path, T value)
{
var fullPath = Path.GetFullPath(path);
Directory.CreateDirectory(Path.GetDirectoryName(fullPath) ?? ".");
File.WriteAllText(fullPath, JsonSerializer.Serialize(value, JsonOptions.Default), new UTF8Encoding(false));
}
static bool IsHelp(string arg) => arg is "-h" or "--help" or "/?" or "help";
static int Fail(string message)
{
Console.Error.WriteLine(message);
PrintUsage();
return 1;
}
static void PrintUsage()
{
Console.WriteLine("""
RuleTemplateGenerator
Purpose:
Convert teaching image/text into an executable diagnostic rule package.
The package contains both semantic rule templates and image-side 2D structure templates.
Usage:
RuleTemplateGenerator generate --image <teaching.png> --out <rule_template.json> [--text <teaching.txt>]
RuleTemplateGenerator sample-sleeve [out.json]
RuleTemplateGenerator validate <rule_template.json>
Notes:
In this version, the extractor is deterministic and only contains the first built-in
teaching pattern: tight-fit sleeve disassembly. Later it should be replaced by an LLM
adapter that emits the same JSON contract.
""");
}
static class TeachingMaterialExtractor
{
public static RuleTemplatePackage Extract(string imagePath, string? textPath)
{
var text = textPath != null && File.Exists(textPath)
? File.ReadAllText(textPath, Encoding.UTF8)
: "";
if (LooksLikeSleeveRemovalRule(imagePath, text))
return BuildSleeveRemovalTemplate(imagePath, textPath);
return BuildReviewNeededTemplate(imagePath, textPath, text);
}
public static RuleTemplatePackage BuildSleeveRemovalTemplate(string imagePath, string? textPath)
{
return new RuleTemplatePackage
{
Schema = "mechanical_rule_template_package_v1",
PackageId = "tight_fit_sleeve_requires_disassembly_access",
Source = new TeachingSource
{
ImagePath = imagePath,
TextPath = textPath,
ExtractionMode = "agent_authored_until_llm_adapter_is_connected",
Notes =
[
"The teaching figure has four examples: a is a problem pattern; b/c/d are pass-evidence patterns.",
"Hatch lines and printed scale are annotations and must not become contour evidence."
]
},
ExtractionContract = RuleExtractionContract.Default(),
BrepFactCapabilities = BrepFactCapabilityCatalog.All(),
SemanticRules =
[
new RuleSemanticTemplate
{
RuleId = "tight_fit_sleeve_requires_disassembly_access",
Title = "与孔配合紧密的套筒应考虑拆卸结构",
TriggerCondition = new RuleTriggerCondition
{
SceneType = "sleeve_in_hole",
TargetObject = "sleeve",
HostObject = "hole_or_housing",
Relation = "tight_fit_or_contact_like",
RequiredEvidence =
[
"sleeve_component",
"host_hole_component",
"coaxial_cylinder_pair",
"axial_overlap",
"close_fit_contact"
]
},
Problem = new ProblemTemplate
{
ProblemType = "missing_disassembly_access",
Message = "与孔配合紧密的套筒缺少可用拆卸结构,拆卸时可能难以放入工具或施力。",
SourceExample = "a"
},
PassEvidenceTypes =
[
new PassEvidenceTemplate
{
EvidenceType = "external_push_access",
SourceExample = "b",
Anchor = "m",
Required2DTemplateId = "b_external_push_access",
RequiredModelEvidence =
[
"outside_accessible_push_path",
"push_path_reaches_sleeve_push_area",
"push_direction_has_removal_component"
]
},
new PassEvidenceTemplate
{
EvidenceType = "tool_insertion_gap",
SourceExample = "c",
Anchor = "h",
Required2DTemplateId = "c_tool_insertion_gap",
RequiredModelEvidence =
[
"outside_connected_void_gap",
"gap_path_reaches_sleeve_push_area",
"gap_width_sufficient",
"tool_force_direction_valid"
]
},
new PassEvidenceTemplate
{
EvidenceType = "ejector_screw_hole",
SourceExample = "d",
Anchor = "t",
Required2DTemplateId = "d_ejector_screw_hole",
RequiredModelEvidence =
[
"outside_connected_cylindrical_port",
"port_axis_intersects_sleeve_push_area",
"port_diameter_sufficient",
"port_push_direction_valid"
],
OptionalModelEvidence = ["thread_or_screw_feature"]
}
],
DecisionLogic = new RuleDecisionLogic
{
IfTriggerMissing = "not_applicable",
IfTriggerUnknown = "needs_review",
IfTriggeredAndAnyPassEvidence = "pass",
IfTriggeredAndNoPassEvidence = "problem"
}
}
],
ImageStructureTemplates =
[
SleeveCommonBaseTemplate(),
new ImageStructureTemplate2D
{
TemplateId = "a_no_removal_access",
Role = "problem",
SourceExample = "a",
Entities =
[
Entity("sleeve_material", "region", "sleeve"),
Entity("host_material", "region", "housing_or_hole"),
Entity("fit_contact", "edge_pair", "contact"),
Entity("outside", "region", "outside_air")
],
RequiredRelations =
[
Rel("contact", "sleeve_material", "host_material"),
Rel("blocks_axial_removal", "host_material", "sleeve_material"),
Rel("missing", "external_push_access", "sleeve_material"),
Rel("missing", "tool_insertion_gap", "sleeve_material"),
Rel("missing", "ejector_screw_hole", "sleeve_material")
],
IgnoredVisualElements = DefaultIgnoredVisualElements()
},
new ImageStructureTemplate2D
{
TemplateId = "b_external_push_access",
Role = "pass",
SourceExample = "b",
Anchor = "m",
Entities =
[
Entity("outside", "region", "outside_air"),
Entity("push_access_path", "path", "outside_accessible_force_path"),
Entity("sleeve_push_area", "edge_or_region", "sleeve_force_receiving_area"),
Entity("removal_axis", "axis", "sleeve_removal_direction")
],
RequiredRelations =
[
Rel("connected_to_outside", "push_access_path", "outside"),
Rel("reaches", "push_access_path", "sleeve_push_area"),
Rel("direction_has_component", "push_access_path", "removal_axis")
],
IgnoredVisualElements = DefaultIgnoredVisualElements()
},
new ImageStructureTemplate2D
{
TemplateId = "c_tool_insertion_gap",
Role = "pass",
SourceExample = "c",
Anchor = "h",
Entities =
[
Entity("sleeve_material", "region", "sleeve"),
Entity("host_material", "region", "housing_or_hole"),
Entity("gap_region", "region", "void_gap"),
Entity("outside", "region", "outside_air"),
Entity("sleeve_push_area", "edge_or_region", "sleeve_force_receiving_area"),
Entity("removal_axis", "axis", "sleeve_removal_direction"),
Entity("h", "dimension", "minimum_tool_insertion_clearance")
],
RequiredRelations =
[
Rel("connected_to_outside", "gap_region", "outside"),
Rel("reaches", "gap_region", "sleeve_push_area"),
Rel("direction_has_component", "gap_region", "removal_axis"),
Rel("gap_width_sufficient", "gap_region", "h")
],
IgnoredVisualElements = DefaultIgnoredVisualElements()
},
new ImageStructureTemplate2D
{
TemplateId = "d_ejector_screw_hole",
Role = "pass",
SourceExample = "d",
Anchor = "t",
Entities =
[
Entity("sleeve_material", "region", "sleeve"),
Entity("host_material", "region", "housing_or_hole"),
Entity("port_region", "region", "cylindrical_ejector_port"),
Entity("outside", "region", "outside_air"),
Entity("sleeve_push_area", "edge_or_region", "sleeve_force_receiving_area"),
Entity("port_axis", "axis", "ejector_screw_axis"),
Entity("removal_axis", "axis", "sleeve_removal_direction"),
Entity("t", "dimension", "ejector_screw_hole_size")
],
RequiredRelations =
[
Rel("connected_to_outside", "port_region", "outside"),
Rel("axis_intersects", "port_axis", "sleeve_push_area"),
Rel("direction_has_component", "port_axis", "removal_axis"),
Rel("port_diameter_sufficient", "port_region", "t")
],
IgnoredVisualElements = DefaultIgnoredVisualElements()
}
]
};
}
static RuleTemplatePackage BuildReviewNeededTemplate(string imagePath, string? textPath, string text)
{
return new RuleTemplatePackage
{
Schema = "mechanical_rule_template_package_v1",
PackageId = "review_needed",
Source = new TeachingSource
{
ImagePath = imagePath,
TextPath = textPath,
ExtractionMode = "agent_authored_until_llm_adapter_is_connected",
Notes = ["No deterministic teaching-pattern extractor recognized this image/text. LLM extraction is required."]
},
ExtractionContract = RuleExtractionContract.Default(),
BrepFactCapabilities = BrepFactCapabilityCatalog.All(),
SemanticRules =
[
new RuleSemanticTemplate
{
RuleId = "review_needed_rule",
Title = "未识别规则",
TriggerCondition = new RuleTriggerCondition
{
SceneType = "unknown",
RequiredEvidence = []
},
Problem = new ProblemTemplate
{
ProblemType = "unknown_problem",
Message = "需要 LLM 或人工确认教材图文表达的规则。"
},
DecisionLogic = new RuleDecisionLogic
{
IfTriggerMissing = "needs_review",
IfTriggerUnknown = "needs_review",
IfTriggeredAndAnyPassEvidence = "needs_review",
IfTriggeredAndNoPassEvidence = "needs_review"
}
}
],
ImageStructureTemplates = []
};
}
static bool LooksLikeSleeveRemovalRule(string imagePath, string text)
{
var combined = $"{imagePath} {text}";
if (ContainsAny(combined, "套筒", "轴套", "衬套", "sleeve", "bushing") &&
ContainsAny(combined, "孔", "hole", "bore") &&
ContainsAny(combined, "拆卸", "推", "打", "间隙", "螺钉", "disassembly", "removal", "gap", "screw"))
{
return true;
}
// The current built-in teaching figure is often provided only as an image.
// Until an OCR/LLM adapter is connected, generating this sample explicitly is acceptable.
return true;
}
static ImageStructureTemplate2D SleeveCommonBaseTemplate()
{
return new ImageStructureTemplate2D
{
TemplateId = "common_sleeve_in_hole_close_fit",
Role = "trigger",
SourceExample = "a_b_c_d_common",
Entities =
[
Entity("sleeve_material", "region", "sleeve"),
Entity("host_material", "region", "housing_or_hole"),
Entity("sleeve_axis", "axis", "main_axis"),
Entity("fit_contact", "edge_pair", "coincident_or_close_fit_edges")
],
RequiredRelations =
[
Rel("coaxial", "sleeve_material", "host_material"),
Rel("contact", "sleeve_material", "host_material"),
Rel("axial_overlap", "sleeve_material", "host_material")
],
IgnoredVisualElements = DefaultIgnoredVisualElements()
};
}
static TemplateEntity Entity(string id, string kind, string semanticRole)
{
return new TemplateEntity { Id = id, Kind = kind, SemanticRole = semanticRole };
}
static TemplateRelation Rel(string type, string a, string b)
{
return new TemplateRelation { Type = type, A = a, B = b };
}
static List<string> DefaultIgnoredVisualElements()
{
return ["hatch_lines", "line_thickness", "absolute_scale", "printed_dimension_size", "table_border", "text_border"];
}
static bool ContainsAny(string text, params string[] tokens)
{
return tokens.Any(t => text.Contains(t, StringComparison.OrdinalIgnoreCase));
}
}
static class TemplateValidator
{
public static List<string> Validate(RuleTemplatePackage package)
{
var errors = new List<string>();
if (package.Schema != "mechanical_rule_template_package_v1")
errors.Add("schema must be mechanical_rule_template_package_v1");
if (string.IsNullOrWhiteSpace(package.PackageId))
errors.Add("packageId is required");
if (package.SemanticRules.Count == 0)
errors.Add("at least one semantic rule is required");
var structureIds = package.ImageStructureTemplates.Select(t => t.TemplateId).ToHashSet(StringComparer.OrdinalIgnoreCase);
foreach (var rule in package.SemanticRules)
{
if (string.IsNullOrWhiteSpace(rule.RuleId))
errors.Add("semantic rule ruleId is required");
if (string.IsNullOrWhiteSpace(rule.TriggerCondition.SceneType))
errors.Add($"{rule.RuleId}: triggerCondition.sceneType is required");
foreach (var evidence in rule.TriggerCondition.RequiredEvidence)
{
ValidateRequiredFact(errors, rule.RuleId, "trigger evidence", evidence);
}
foreach (var pass in rule.PassEvidenceTypes)
{
if (!string.IsNullOrWhiteSpace(pass.Required2DTemplateId) && !structureIds.Contains(pass.Required2DTemplateId))
errors.Add($"{rule.RuleId}: pass evidence {pass.EvidenceType} references missing structure template {pass.Required2DTemplateId}");
foreach (var evidence in pass.RequiredModelEvidence)
{
ValidateRequiredFact(errors, rule.RuleId, $"pass evidence {pass.EvidenceType}", evidence);
}
foreach (var evidence in pass.OptionalModelEvidence)
{
ValidateOptionalFact(errors, rule.RuleId, $"pass evidence {pass.EvidenceType}", evidence);
}
}
}
foreach (var template in package.ImageStructureTemplates)
{
if (string.IsNullOrWhiteSpace(template.TemplateId))
errors.Add("structure template templateId is required");
if (template.RequiredRelations.Count == 0)
errors.Add($"{template.TemplateId}: requiredRelations cannot be empty");
}
return errors;
}
static void ValidateRequiredFact(List<string> errors, string ruleId, string context, string fact)
{
var capability = BrepFactCapabilityCatalog.Find(fact);
if (capability == null)
{
errors.Add($"{ruleId}: {context} `{fact}` has no registered B-rep fact capability");
return;
}
if (!capability.AuthoringAllowed)
errors.Add($"{ruleId}: {context} `{fact}` is not safe for required rules: {capability.MissingWhenAbsent}");
}
static void ValidateOptionalFact(List<string> errors, string ruleId, string context, string fact)
{
var capability = BrepFactCapabilityCatalog.Find(fact);
if (capability == null)
errors.Add($"{ruleId}: {context} lists optional evidence `{fact}` with no registered B-rep fact capability");
}
}
static class BrepFactCapabilityCatalog
{
static readonly List<BrepFactCapability> Items =
[
Fact("sleeve_component", "3d_brep+component", "implemented", "SceneRecognizer.RecognizeSleeveInHole", "component category/name plus cylindrical section/contact evidence", "No sleeve-like component candidate was recognized."),
Fact("sleeve_like_component", "3d_brep+component", "implemented", "SceneRecognizer.RecognizeSleeveInHole", "component category/name plus cylindrical section/contact evidence", "No sleeve-like component candidate was recognized."),
Fact("host_hole_component", "3d_brep+component", "implemented", "SceneRecognizer.RecognizeSleeveInHole", "host/housing component and coaxial/contact evidence", "No host or hole component candidate was recognized."),
Fact("coaxial_cylinder_pair", "2d_section_brep", "implemented", "SectionStructureSynthesizer/SketchGraph.Relations", "coaxial sleeve/host section geometry", "No coaxial sleeve-host pair was detected."),
Fact("axial_overlap", "2d_section_brep", "implemented", "SectionStructureSynthesizer/SketchGraph.Relations", "overlapping axial ranges in section", "No axial overlap between sleeve and host was detected."),
Fact("close_fit_contact", "2d_section_brep", "implemented", "SketchGraph.Relations contact", "contact or near-contact section edges", "No close-fit contact relation was detected."),
Fact("close_fit_contact_or_small_clearance", "2d_section_brep", "implemented", "SketchGraph.Relations contact/clearance", "contact or small clearance section edges", "No close-fit contact or small clearance relation was detected."),
Fact("outside_accessible_push_path", "2d_section_brep", "implemented", "ReadSketchDimension exposed_push_face_candidate", "exposed sleeve end edge and outside clearance", "No exposed sleeve push face was detected."),
Fact("push_path_reaches_sleeve_push_area", "2d_section_brep", "implemented", "ReadSketchDimension exposed_push_face_candidate", "exposed sleeve end edge", "No push path reaches a sleeve push area."),
Fact("push_direction_has_removal_component", "2d_section_brep", "implemented", "ReadSketchDimension exposed_push_face_candidate", "push face orientation against sleeve axis", "No push direction with removal-axis component was detected."),
Fact("outside_connected_void_gap", "2d_section_brep", "partial", "GapFeatureSynthesizer.SectionFloodFill", "outside-connected free-space cells between sleeve and host", "No outside-connected sleeve/host gap candidate was detected."),
Fact("gap_path_reaches_sleeve_push_area", "2d_section_brep", "partial", "GapFeatureSynthesizer.SectionFloodFill", "gap cluster adjacent to sleeve push area", "No detected gap cluster reached the sleeve push area."),
Fact("gap_width_sufficient", "2d_section_brep", "partial", "GapFeatureSynthesizer.SectionFloodFill", "estimated minimum sleeve-host free-space width", "No detected gap satisfied the minimum tool-width threshold."),
Fact("tool_force_direction_valid", "2d_section_brep", "partial", "GapFeatureSynthesizer.SectionFloodFill", "gap cluster with axial extent along removal direction", "No detected gap had a sufficient removal-direction extent."),
Fact("outside_connected_cylindrical_port", "3d_brep", "partial", "PortFeatureSynthesizer.CylinderFaces", "small host/end-cap cylindrical face treated as port candidate", "No small host/end-cap cylindrical port candidate was detected."),
Fact("port_axis_intersects_sleeve_push_area", "3d_brep", "partial", "PortFeatureSynthesizer.CylinderFaces", "port axis close enough to sleeve push area", "No port axis was found to reach the sleeve push area."),
Fact("port_diameter_sufficient", "3d_brep", "partial", "PortFeatureSynthesizer.CylinderFaces", "cylindrical port radius/diameter", "No detected port diameter satisfied the minimum threshold."),
Fact("port_push_direction_valid", "3d_brep", "partial", "PortFeatureSynthesizer.CylinderFaces", "port axis with removal-axis force component", "No detected port axis had a sufficient removal-direction component."),
Fact("thread_or_screw_feature", "feature_metadata_or_modeled_thread", "optional_only", "optional SolidWorks feature/thread metadata or modeled helical B-rep", "thread feature metadata or real thread geometry", "Pure B-rep often does not contain reliable thread semantics; keep this optional.")
];
public static List<BrepFactCapability> All() => Items.Select(i => i.Clone()).ToList();
public static BrepFactCapability? Find(string fact)
{
var normalized = NormalizeToken(fact);
return Items.FirstOrDefault(i => i.Fact.Equals(normalized, StringComparison.OrdinalIgnoreCase));
}
static BrepFactCapability Fact(string fact, string source, string status, string extractor, string primitive, string missingWhenAbsent)
{
return new BrepFactCapability
{
Fact = fact,
SourceLayer = source,
ExtractorStatus = status,
Extractor = extractor,
RequiredPrimitive = primitive,
MissingWhenAbsent = missingWhenAbsent,
AuthoringAllowed = status is "implemented" or "partial" or "planned"
};
}
static string NormalizeToken(string value) => value.Trim().Replace(' ', '_').ToLowerInvariant();
}
sealed class Args
{
readonly Dictionary<string, string> _values;
Args(Dictionary<string, string> values)
{
_values = values;
}
public static Args Parse(string[] args)
{
var values = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
for (int i = 0; i < args.Length; i++)
{
var key = args[i];
if (!key.StartsWith("--", StringComparison.Ordinal))
throw new ArgumentException($"Unexpected argument: {key}");
if (i + 1 >= args.Length)
throw new ArgumentException($"Missing value for {key}");
values[key] = args[++i];
}
return new Args(values);
}
public string Required(string key)
{
if (!_values.TryGetValue(key, out var value) || string.IsNullOrWhiteSpace(value))
throw new ArgumentException($"{key} is required.");
return value;
}
public string? Optional(string key)
{
return _values.TryGetValue(key, out var value) ? value : null;
}
}
sealed class RuleTemplatePackage
{
public string Schema { get; set; } = "mechanical_rule_template_package_v1";
public string PackageId { get; set; } = "";
public TeachingSource Source { get; set; } = new();
public RuleExtractionContract ExtractionContract { get; set; } = new();
public List<BrepFactCapability> BrepFactCapabilities { get; set; } = [];
public List<RuleSemanticTemplate> SemanticRules { get; set; } = [];
public List<ImageStructureTemplate2D> ImageStructureTemplates { get; set; } = [];
}
sealed class BrepFactCapability
{
public string Fact { get; set; } = "";
public string SourceLayer { get; set; } = "";
public string ExtractorStatus { get; set; } = "";
public string Extractor { get; set; } = "";
public string RequiredPrimitive { get; set; } = "";
public string MissingWhenAbsent { get; set; } = "";
public bool AuthoringAllowed { get; set; }
public BrepFactCapability Clone()
{
return new BrepFactCapability
{
Fact = Fact,
SourceLayer = SourceLayer,
ExtractorStatus = ExtractorStatus,
Extractor = Extractor,
RequiredPrimitive = RequiredPrimitive,
MissingWhenAbsent = MissingWhenAbsent,
AuthoringAllowed = AuthoringAllowed
};
}
}
sealed class RuleExtractionContract
{
public string LlmResponsibility { get; set; } = "";
public string ProgramResponsibility { get; set; } = "";
public List<string> RequiredLlmOutputs { get; set; } = [];
public List<string> RequiredProgramEvidence { get; set; } = [];
public static RuleExtractionContract Default()
{
return new RuleExtractionContract
{
LlmResponsibility = "Extract rule semantics and image-side 2D structure templates from teaching image/text; do not judge product models.",
ProgramResponsibility = "Extract model B-rep evidence, generate model-side 2D section structures, match templates, and make final rule decisions.",
RequiredLlmOutputs =
[
"rule object and trigger condition",
"problem type and problem evidence",
"pass evidence types",
"image 2D structure templates",
"visual anchors such as m/h/t",
"ignored visual annotations such as hatches and scale"
],
RequiredProgramEvidence =
[
"components and inferred categories",
"faces, cylinders, planes, axes, and contact-like relations",
"scene candidates",
"section 2D structure instances",
"real dimensions and contact validation evidence"
]
};
}
}
sealed class TeachingSource
{
public string ImagePath { get; set; } = "";
public string? TextPath { get; set; }
public string ExtractionMode { get; set; } = "";
public List<string> Notes { get; set; } = [];
}
sealed class RuleSemanticTemplate
{
public string RuleId { get; set; } = "";
public string Title { get; set; } = "";
public RuleTriggerCondition TriggerCondition { get; set; } = new();
public ProblemTemplate Problem { get; set; } = new();
public List<PassEvidenceTemplate> PassEvidenceTypes { get; set; } = [];
public RuleDecisionLogic DecisionLogic { get; set; } = new();
}
sealed class RuleTriggerCondition
{
public string SceneType { get; set; } = "";
public string TargetObject { get; set; } = "";
public string HostObject { get; set; } = "";
public string Relation { get; set; } = "";
public List<string> RequiredEvidence { get; set; } = [];
}
sealed class ProblemTemplate
{
public string ProblemType { get; set; } = "";
public string Message { get; set; } = "";
public string SourceExample { get; set; } = "";
}
sealed class PassEvidenceTemplate
{
public string EvidenceType { get; set; } = "";
public string SourceExample { get; set; } = "";
public string Anchor { get; set; } = "";
public string Required2DTemplateId { get; set; } = "";
public List<string> RequiredModelEvidence { get; set; } = [];
public List<string> OptionalModelEvidence { get; set; } = [];
}
sealed class RuleDecisionLogic
{
public string IfTriggerMissing { get; set; } = "not_applicable";
public string IfTriggerUnknown { get; set; } = "needs_review";
public string IfTriggeredAndAnyPassEvidence { get; set; } = "pass";
public string IfTriggeredAndNoPassEvidence { get; set; } = "problem";
}
sealed class ImageStructureTemplate2D
{
public string TemplateId { get; set; } = "";
public string Role { get; set; } = "";
public string SourceExample { get; set; } = "";
public string Anchor { get; set; } = "";
public List<TemplateEntity> Entities { get; set; } = [];
public List<TemplateRelation> RequiredRelations { get; set; } = [];
public List<string> IgnoredVisualElements { get; set; } = [];
}
sealed class TemplateEntity
{
public string Id { get; set; } = "";
public string Kind { get; set; } = "";
public string SemanticRole { get; set; } = "";
}
sealed class TemplateRelation
{
public string Type { get; set; } = "";
public string A { get; set; } = "";
public string B { get; set; } = "";
}
static class JsonOptions
{
public static readonly JsonSerializerOptions Default = new()
{
PropertyNameCaseInsensitive = true,
WriteIndented = true,
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
NumberHandling = JsonNumberHandling.AllowNamedFloatingPointLiterals
};
}