From 8fcf237722bc68eb6c8d6e8405b95a9aa1097aa4 Mon Sep 17 00:00:00 2001 From: David Bond Date: Fri, 13 Nov 2026 11:07:07 +0000 Subject: [PATCH] MS-23199 Credit h1 to h4 headings and the Actual, Desired and Definition of Done variants Current and Expected Behavior now accept h1 to h4 headings and the Actual Behavior, Actual Result, Desired Behavior, Expected Result and Expected Outcome variants. Testability counts Definition of Done and any h1 to h4 Acceptance Criteria heading. Tests cover each variant, every level and case. --- .../Rubrics/HeadingKeywordTests.cs | 79 +++++++++++++++++++ .../Rubrics/panoramicdata-ticket-quality.json | 6 +- 2 files changed, 82 insertions(+), 3 deletions(-) create mode 100644 PanoramicData.IssueAssessment.Tests/Rubrics/HeadingKeywordTests.cs diff --git a/PanoramicData.IssueAssessment.Tests/Rubrics/HeadingKeywordTests.cs b/PanoramicData.IssueAssessment.Tests/Rubrics/HeadingKeywordTests.cs new file mode 100644 index 0000000..7cba656 --- /dev/null +++ b/PanoramicData.IssueAssessment.Tests/Rubrics/HeadingKeywordTests.cs @@ -0,0 +1,79 @@ +using PanoramicData.IssueAssessment.Mapping; +using PanoramicData.IssueAssessment.Rubrics; + +namespace PanoramicData.IssueAssessment.Tests.Rubrics; + +/// +/// MS-23199 Section headings are credited whatever their level (h1 to h4), case or wording variant. +/// +public class HeadingKeywordTests : TestBase +{ + private static readonly IssueAssessor Assessor = new(BuiltInRubrics.PanoramicDataTicketQuality); + + [Theory] + [InlineData("Current Behavior", "Expected Behavior")] + [InlineData("Current Behaviour", "Expected Behaviour")] + [InlineData("Actual Behavior", "Expected Behavior")] + [InlineData("Actual Result", "Expected Result")] + [InlineData("Current Behavior", "Desired Behavior")] + [InlineData("current behavior", "EXPECTED BEHAVIOR")] + public async Task CurrentAndExpectedHeadings_ScoreFull_WhateverTheVariantOrCase(string current, string expected) + { + int score = await RawScore("CurrentVsExpected", $"h2. {current}\r\nRows with line breaks are missing.\r\n\r\nh2. {expected}\r\nEvery row is exported."); + + score.Should().Be(100); + } + + [Theory] + [InlineData("h1.")] + [InlineData("h2.")] + [InlineData("h3.")] + [InlineData("h4.")] + public async Task CurrentAndExpectedHeadings_ScoreFull_AtEveryJiraHeadingLevel(string marker) + { + int score = await RawScore("CurrentVsExpected", $"{marker} Current Behavior\r\nRows are missing.\r\n\r\n{marker} Expected Behavior\r\nEvery row is exported."); + + score.Should().Be(100); + } + + [Fact] + public async Task CurrentHeadingAlone_StillScoresHalf() + { + int score = await RawScore("CurrentVsExpected", "h2. Actual Result\r\nRows are missing."); + + score.Should().Be(50); + } + + [Theory] + [InlineData("Acceptance Criteria")] + [InlineData("Success Criteria")] + [InlineData("Definition of Done")] + [InlineData("ACCEPTANCE CRITERIA")] + public async Task CriteriaHeadings_RaiseTestabilityAboveADescriptionWithNone(string heading) + { + const string Body = "Rows with line breaks are missing from the export."; + + int without = await RawScore("Testability", Body); + int with = await RawScore("Testability", $"{Body}\r\n\r\nh2. {heading}\r\n# Every row is exported\r\n# Line breaks are preserved"); + + with.Should().BeGreaterThan(without); + } + + private static async Task RawScore(string name, string description) + { + NormalizedIssue issue = new() + { + Key = "OPS-1", + Project = "OPS", + Title = "Export to CSV drops rows that contain line breaks", + Description = description, + IssueType = "Bug", + Status = "In Progress", + Platform = "Jira", + Labels = [], + }; + using JsonDocument document = issue.ToJsonDocument(); + AssessmentReport report = AssessmentReport.From(await Assessor.AssessAsync(document, CancellationToken)); + return (int)report.Categories.SelectMany(category => category.Assessments).Single(item => item.Name == name).RawScore!.Value; + } +} diff --git a/PanoramicData.IssueAssessment/Rubrics/panoramicdata-ticket-quality.json b/PanoramicData.IssueAssessment/Rubrics/panoramicdata-ticket-quality.json index caea93f..02ec636 100644 --- a/PanoramicData.IssueAssessment/Rubrics/panoramicdata-ticket-quality.json +++ b/PanoramicData.IssueAssessment/Rubrics/panoramicdata-ticket-quality.json @@ -165,7 +165,7 @@ "measures": { "scope": { "type": "patternMatchCount", "source": { "paths": [ "$.title", "$.description" ] }, "patterns": [ "scope:", "h[1-6]\\.[^\\r\\n]*scope", "\\*\\*[^\\r\\n]*scope", "questions?\\s+to\\s+(answer|investigate)", "investigate\\s+(whether|if|how|why)", "determine\\s+(whether|if|how|why)", "find\\s+out", "understand\\s+(how|why|what)" ] }, "deliverables": { "type": "patternMatchCount", "source": { "paths": [ "$.title", "$.description" ] }, "patterns": [ "deliverable", "findings", "recommendation", "report", "document", "conclusion", "outcome", "result\\s+of\\s+(investigation|research|analysis)" ] }, - "criteria": { "type": "patternMatchCount", "source": { "paths": [ "$.title", "$.description" ] }, "patterns": [ "should\\s+(be|display|show|return|have|not)", "must\\s+(be|display|show|return|have|not)", "verify\\s+that", "acceptance\\s+criteria", "h[23]\\.\\s*acceptance\\s+criteria", "expected\\s+(result|behavior|behaviour|outcome)", "pass\\s+criteria", "success\\s+criteria" ] }, + "criteria": { "type": "patternMatchCount", "source": { "paths": [ "$.title", "$.description" ] }, "patterns": [ "should\\s+(be|display|show|return|have|not)", "must\\s+(be|display|show|return|have|not)", "verify\\s+that", "acceptance\\s+criteria", "h[1-4]\\.\\s*acceptance\\s+criteria", "expected\\s+(result|behavior|behaviour|outcome)", "pass\\s+criteria", "success\\s+criteria", "definition\\s+of\\s+done" ] }, "criteriaHeadings": { "type": "criteriaHeadingCount", "source": { "paths": [ "$.description" ] } } }, "tiers": [ @@ -215,8 +215,8 @@ "type": "tiered", "measures": { "context": { "type": "patternMatchCount", "source": { "paths": [ "$.title", "$.description" ] }, "patterns": [ "background:", "h[1-6]\\.[^\\r\\n]*background", "context:", "h[1-6]\\.[^\\r\\n]*context", "\\*\\*[^\\r\\n]*(background|context)", "motivation:", "why\\s+it\\s+matters", "why\\s+(we|this)", "reason\\s+for", "because" ] }, - "current": { "type": "patternMatchCount", "source": { "paths": [ "$.title", "$.description" ] }, "patterns": [ "current(ly)?(\\s+behavior|\\s+behaviour)?:", "h[23]\\.\\s*current(\\s*/\\s*actual)?\\s*(behavior|behaviour)", "\\*\\*current\\s*(behavior|behaviour)\\*\\*", "\\*current(\\s*/\\s*actual)?\\s*(behavior|behaviour)?\\*", "\\*actual\\s*(behavior|behaviour)?\\*", "actual(\\s+behavior|\\s+behaviour)?:", "what\\s+happens:", "observed:", "bug:", "issue:", "problem:" ] }, - "expected": { "type": "patternMatchCount", "source": { "paths": [ "$.title", "$.description" ] }, "patterns": [ "expected(\\s+behavior|\\s+behaviour)?:", "h[23]\\.\\s*expected\\s*(behavior|behaviour)", "\\*\\*expected\\s*(behavior|behaviour)\\*\\*", "\\*expected\\s*(behavior|behaviour)?\\*", "should\\s+be:", "desired:", "correct\\s+behavior:", "fix:", "solution:", "h2\\.\\s*acceptance\\s+criteria", "h3\\.\\s*acceptance\\s+criteria", "acceptance\\s+criteria:", "\\*\\*acceptance\\s+criteria\\*\\*" ] } + "current": { "type": "patternMatchCount", "source": { "paths": [ "$.title", "$.description" ] }, "patterns": [ "current(ly)?(\\s+behavior|\\s+behaviour)?:", "h[1-4]\\.\\s*(current|actual)(\\s*/\\s*(actual|current))?\\s*(behavior|behaviour|result)", "\\*\\*current\\s*(behavior|behaviour)\\*\\*", "\\*current(\\s*/\\s*actual)?\\s*(behavior|behaviour)?\\*", "\\*actual\\s*(behavior|behaviour)?\\*", "actual(\\s+behavior|\\s+behaviour)?:", "what\\s+happens:", "observed:", "bug:", "issue:", "problem:" ] }, + "expected": { "type": "patternMatchCount", "source": { "paths": [ "$.title", "$.description" ] }, "patterns": [ "expected(\\s+behavior|\\s+behaviour)?:", "h[1-4]\\.\\s*(expected|desired)\\s*(behavior|behaviour|result|outcome)", "\\*\\*expected\\s*(behavior|behaviour)\\*\\*", "\\*expected\\s*(behavior|behaviour)?\\*", "should\\s+be:", "desired:", "correct\\s+behavior:", "fix:", "solution:", "h2\\.\\s*acceptance\\s+criteria", "h3\\.\\s*acceptance\\s+criteria", "acceptance\\s+criteria:", "\\*\\*acceptance\\s+criteria\\*\\*" ] } }, "tiers": [ { "when": { "type": "all", "conditions": [ { "type": "ref", "name": "isInvestigation" }, { "type": "measure", "measure": "context", "atLeast": 2 } ] }, "score": 100, "finding": "✓ Investigation Context: Well documented" },