@elisra-devops/docgen-data-provider 1.101.0 → 1.103.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -24,6 +24,7 @@ export interface MewpL2RequirementWorkItem {
24
24
  requirementId: string;
25
25
  baseKey: string;
26
26
  title: string;
27
+ owner: string;
27
28
  subSystem: string;
28
29
  responsibility: string;
29
30
  linkedTestCaseIds: number[];
@@ -35,6 +36,7 @@ export interface MewpL2RequirementFamily {
35
36
  requirementId: string;
36
37
  baseKey: string;
37
38
  title: string;
39
+ owner: string;
38
40
  subSystem: string;
39
41
  responsibility: string;
40
42
  linkedTestCaseIds: number[];
@@ -71,7 +73,10 @@ export interface MewpCoverageBugCell {
71
73
  export type MewpCoverageL3L4Cell = MewpL3L4Pair;
72
74
  export interface MewpCoverageRow {
73
75
  'L2 REQ ID': string;
76
+ 'SR #': string;
74
77
  'L2 REQ Title': string;
78
+ 'L2 REQ Full Title'?: string;
79
+ 'L2 Owner': string;
75
80
  'L2 SubSystem': string;
76
81
  'L2 Run Status': MewpRunStatus;
77
82
  'Bug ID': number | '';
@@ -26,6 +26,8 @@ export default class ResultDataProvider {
26
26
  private static readonly MEWP_INTERNAL_VALIDATION_TRACE_TAG;
27
27
  private static readonly MEWP_INTERNAL_VALIDATION_DIAGNOSTICS_TAG;
28
28
  private static readonly MEWP_INTERNAL_VALIDATION_SUMMARY_TAG;
29
+ private static readonly MEWP_INTERNAL_VALIDATION_ASSUMPTIONS_REF;
30
+ private static readonly MEWP_INTERNAL_VALIDATION_ASSUMPTIONS_HEADER_PATTERN;
29
31
  private static readonly MEWP_L2_COVERAGE_COLUMNS;
30
32
  private static readonly INTERNAL_VALIDATION_COLUMNS;
31
33
  orgUrl: string;
@@ -90,6 +92,7 @@ export default class ResultDataProvider {
90
92
  private formatLogValue;
91
93
  private buildTaggedLogMessage;
92
94
  private createMewpCoverageRow;
95
+ private deriveMewpRequirementDisplayName;
93
96
  private createEmptyMewpCoverageBugCell;
94
97
  private createEmptyMewpCoverageL3L4Cell;
95
98
  private buildMewpCoverageL3L4Rows;
@@ -104,6 +107,15 @@ export default class ResultDataProvider {
104
107
  private isExternalStateInScope;
105
108
  private invertBaseRequirementLinks;
106
109
  private buildMewpTestCaseTitleMap;
110
+ /**
111
+ * Builds a lookup of test case id -> test case description using suite payload data.
112
+ *
113
+ * Resolution order:
114
+ * 1) direct description fields on test-case payload (`testCase.description` / `workItem.description`)
115
+ * 2) `System.Description` / `Description` in work-item fields map
116
+ * 3) `System.Description` / `Description` from work-item field list (`workItemFields`)
117
+ */
118
+ private buildMewpTestCaseDescriptionMap;
107
119
  private extractMewpTestCaseId;
108
120
  private buildTestCaseStepsXmlMap;
109
121
  private extractStepsXmlFromTestCaseItem;
@@ -115,6 +127,27 @@ export default class ResultDataProvider {
115
127
  private resolveRequirementStatusForWindow;
116
128
  private extractRequirementCodesFromText;
117
129
  private extractRequirementMentionsFromExpectedSteps;
130
+ /**
131
+ * Extracts SR requirement mentions from the "assumptions" section inside a test-case description.
132
+ *
133
+ * Notes:
134
+ * - Heading detection is case-insensitive and keyed by "assumptions".
135
+ * - Parsing is scoped to that section only and stops when a likely next section heading is reached
136
+ * after at least one requirement-bearing line was collected.
137
+ * - Returned stepRef is a synthetic marker ("Assumptions") so downstream discrepancy output can
138
+ * clearly attribute source to description-level assumptions.
139
+ */
140
+ private extractRequirementMentionsFromAssumptionsDescription;
141
+ /**
142
+ * Normalizes raw HTML/plain description content into comparable text lines.
143
+ * This makes section/title heuristics resilient to formatting differences.
144
+ */
145
+ private normalizeMewpDescriptionLines;
146
+ /**
147
+ * Heuristic detector for description section headings used to stop assumptions parsing.
148
+ * A line is considered a heading when it is short/title-like and does not itself contain SR codes.
149
+ */
150
+ private isLikelyMewpDescriptionSectionHeading;
118
151
  private extractRequirementCodesFromExpectedText;
119
152
  private extractRequirementCandidatesFromToken;
120
153
  private expandRequirementTokenByComma;
@@ -138,6 +171,7 @@ export default class ResultDataProvider {
138
171
  private extractLinkedWorkItemIdsFromRelations;
139
172
  private extractMewpRequirementIdentifier;
140
173
  private deriveMewpResponsibility;
174
+ private deriveMewpRequirementOwner;
141
175
  private deriveMewpTestCaseResponsibility;
142
176
  private deriveMewpSubSystem;
143
177
  private resolveBugResponsibility;
@@ -517,6 +517,7 @@ class ResultDataProvider {
517
517
  const rows = [];
518
518
  const stepsXmlByTestCase = this.buildTestCaseStepsXmlMap(testData);
519
519
  const testCaseTitleMap = this.buildMewpTestCaseTitleMap(testData);
520
+ const testCaseDescriptionMap = this.buildMewpTestCaseDescriptionMap(testData);
520
521
  const allTestCaseIds = new Set();
521
522
  for (const suite of testData || []) {
522
523
  const testCasesItems = Array.isArray(suite === null || suite === void 0 ? void 0 : suite.testCasesItems) ? suite.testCasesItems : [];
@@ -562,7 +563,13 @@ class ResultDataProvider {
562
563
  : [];
563
564
  const executableSteps = parsedSteps.filter((step) => !(step === null || step === void 0 ? void 0 : step.isSharedStepTitle));
564
565
  diagnostics.totalParsedSteps += executableSteps.length;
565
- const mentionEntries = this.extractRequirementMentionsFromExpectedSteps(parsedSteps, true);
566
+ // Direction A/B mentions can come from two sources:
567
+ // 1) Expected Result in executable steps.
568
+ // 2) "Assumptions" section in the test-case description.
569
+ const stepMentionEntries = this.extractRequirementMentionsFromExpectedSteps(parsedSteps, true);
570
+ const descriptionText = testCaseDescriptionMap.get(testCaseId) || '';
571
+ const assumptionsMentionEntries = this.extractRequirementMentionsFromAssumptionsDescription(descriptionText, true);
572
+ const mentionEntries = [...stepMentionEntries, ...assumptionsMentionEntries];
566
573
  diagnostics.totalStepsWithMentions += mentionEntries.length;
567
574
  const mentionedL2Only = new Set();
568
575
  const mentionedCodeFirstStep = new Map();
@@ -601,19 +608,6 @@ class ResultDataProvider {
601
608
  mentionedCodesByBase.set(baseKey, new Set());
602
609
  mentionedCodesByBase.get(baseKey).add(code);
603
610
  }
604
- if (traceCurrentTestCase) {
605
- logger_1.default.debug(this.buildTaggedLogMessage(ResultDataProvider.MEWP_INTERNAL_VALIDATION_TRACE_TAG, {
606
- event: 'test-case-start',
607
- tc: testCaseId,
608
- parsedSteps: executableSteps.length,
609
- stepsWithMentions: mentionEntries.length,
610
- mentionedCodes: [...mentionedL2Only].sort((a, b) => this.compareMewpRequirementCodes(a, b)).join('; ') ||
611
- '<none>',
612
- linkedCodesInTestCase: [...linkedFullCodes]
613
- .sort((a, b) => this.compareMewpRequirementCodes(a, b))
614
- .join('; ') || '<none>',
615
- }));
616
- }
617
611
  // Direction A logic:
618
612
  // 1) Base mention ("SR0054") is parent-level and considered covered only when
619
613
  // the whole family is covered across scoped test cases:
@@ -636,81 +630,30 @@ class ResultDataProvider {
636
630
  const requiredFamilyMembers = specificFamilyMembers.length > 0 ? specificFamilyMembers : normalizedFamilyMembers;
637
631
  // Base mention ("SR0054") requires full family coverage across selected test cases.
638
632
  if (hasBaseMention) {
639
- const missingRequiredFamilyMembers = requiredFamilyMembers.filter((memberCode) => !familyLinkedCodes.has(memberCode));
640
633
  const isWholeFamilyCovered = requiredFamilyMembers.every((memberCode) => familyLinkedCodes.has(memberCode));
641
634
  if (!isWholeFamilyCovered) {
642
635
  missingBaseWhenFamilyUncovered.add(baseKey);
643
636
  }
644
- if (traceCurrentTestCase) {
645
- logger_1.default.debug(this.buildTaggedLogMessage(ResultDataProvider.MEWP_INTERNAL_VALIDATION_TRACE_TAG, {
646
- event: 'base-family-coverage',
647
- tc: testCaseId,
648
- base: baseKey,
649
- baseMention: true,
650
- requiredFamily: requiredFamilyMembers
651
- .sort((a, b) => this.compareMewpRequirementCodes(a, b))
652
- .join('; ') || '<none>',
653
- linkedAcrossScope: [...familyLinkedCodes]
654
- .sort((a, b) => this.compareMewpRequirementCodes(a, b))
655
- .join('; ') || '<none>',
656
- missingRequired: missingRequiredFamilyMembers
657
- .sort((a, b) => this.compareMewpRequirementCodes(a, b))
658
- .join('; ') || '<none>',
659
- covered: isWholeFamilyCovered,
660
- }));
661
- }
662
637
  }
663
638
  // Specific mention ("SR0054-1") validates as exact-match only across scoped test cases.
664
639
  const missingSpecificMembers = mentionedSpecificMembers.filter((code) => !familyLinkedCodes.has(code));
665
640
  for (const code of missingSpecificMembers) {
666
641
  missingSpecificMentionedNoFamily.add(code);
667
642
  }
668
- if (traceCurrentTestCase && mentionedSpecificMembers.length > 0) {
669
- logger_1.default.debug(this.buildTaggedLogMessage(ResultDataProvider.MEWP_INTERNAL_VALIDATION_TRACE_TAG, {
670
- event: 'specific-members-check',
671
- tc: testCaseId,
672
- base: baseKey,
673
- specificMentioned: mentionedSpecificMembers
674
- .sort((a, b) => this.compareMewpRequirementCodes(a, b))
675
- .join('; ') || '<none>',
676
- specificMissing: missingSpecificMembers
677
- .sort((a, b) => this.compareMewpRequirementCodes(a, b))
678
- .join('; ') || '<none>',
679
- }));
680
- }
681
643
  continue;
682
644
  }
683
645
  // Fallback path when family data is unavailable for this base key.
684
- const fallbackMissingSpecific = [];
685
- let fallbackMissingBase = false;
686
646
  for (const code of mentionedCodes) {
687
647
  const hasSpecificSuffix = /-\d+$/.test(code);
688
648
  if (hasSpecificSuffix) {
689
649
  if (!linkedFullCodesAcrossTestCases.has(code)) {
690
650
  missingSpecificMentionedNoFamily.add(code);
691
- fallbackMissingSpecific.push(code);
692
651
  }
693
652
  }
694
653
  else if (!linkedBaseKeysAcrossTestCases.has(baseKey)) {
695
654
  missingBaseWhenFamilyUncovered.add(baseKey);
696
- fallbackMissingBase = true;
697
655
  }
698
656
  }
699
- if (traceCurrentTestCase) {
700
- logger_1.default.debug(this.buildTaggedLogMessage(ResultDataProvider.MEWP_INTERNAL_VALIDATION_TRACE_TAG, {
701
- event: 'fallback-path',
702
- tc: testCaseId,
703
- base: baseKey,
704
- fallbackUsed: true,
705
- mentioned: mentionedCodesList
706
- .sort((a, b) => this.compareMewpRequirementCodes(a, b))
707
- .join('; ') || '<none>',
708
- missingSpecific: fallbackMissingSpecific
709
- .sort((a, b) => this.compareMewpRequirementCodes(a, b))
710
- .join('; ') || '<none>',
711
- missingBase: fallbackMissingBase,
712
- }));
713
- }
714
657
  }
715
658
  // Direction B is family-based: if any member of a family is mentioned in Expected Result,
716
659
  // linked members of that same family are not considered "linked but not mentioned".
@@ -750,14 +693,6 @@ class ResultDataProvider {
750
693
  const stepRef = mentionedBaseFirstStep.get(baseKey) || 'Step ?';
751
694
  appendMentionedButNotLinked(baseKey, stepRef);
752
695
  }
753
- if (traceCurrentTestCase) {
754
- logger_1.default.debug(this.buildTaggedLogMessage(ResultDataProvider.MEWP_INTERNAL_VALIDATION_TRACE_TAG, {
755
- event: 'direction-a-summary',
756
- tc: testCaseId,
757
- missingSpecific: sortedMissingSpecificMentionedNoFamily.join('; ') || '<none>',
758
- missingBase: sortedMissingBaseWhenFamilyUncovered.join('; ') || '<none>',
759
- }));
760
- }
761
696
  const sortedExtraLinked = [...new Set(extraLinked)]
762
697
  .map((code) => this.normalizeMewpRequirementCodeWithSuffix(code))
763
698
  .filter((code) => !!code)
@@ -792,18 +727,20 @@ class ResultDataProvider {
792
727
  const validationStatus = mentionedButNotLinked || linkedButNotMentioned ? 'Fail' : 'Pass';
793
728
  if (validationStatus === 'Fail')
794
729
  diagnostics.failingRows += 1;
795
- logger_1.default.debug(this.buildTaggedLogMessage(ResultDataProvider.MEWP_INTERNAL_VALIDATION_DIAGNOSTICS_TAG, {
796
- testCaseId,
797
- parsedSteps: executableSteps.length,
798
- stepsWithMentions: mentionEntries.length,
799
- customerIdsFound: mentionedL2Only.size,
800
- linkedRequirements: linkedFullCodes.size,
801
- mentionedButNotLinked: sortedMissingSpecificMentionedNoFamily.length +
802
- sortedMissingBaseWhenFamilyUncovered.length,
803
- linkedButNotMentioned: sortedExtraLinked.length,
804
- status: validationStatus,
805
- customerIdSample: [...mentionedL2Only].slice(0, 5).join(', '),
806
- }));
730
+ if (traceCurrentTestCase) {
731
+ logger_1.default.debug(this.buildTaggedLogMessage(ResultDataProvider.MEWP_INTERNAL_VALIDATION_DIAGNOSTICS_TAG, {
732
+ testCaseId,
733
+ parsedSteps: executableSteps.length,
734
+ stepsWithMentions: mentionEntries.length,
735
+ customerIdsFound: mentionedL2Only.size,
736
+ linkedRequirements: linkedFullCodes.size,
737
+ mentionedButNotLinked: sortedMissingSpecificMentionedNoFamily.length +
738
+ sortedMissingBaseWhenFamilyUncovered.length,
739
+ linkedButNotMentioned: sortedExtraLinked.length,
740
+ status: validationStatus,
741
+ customerIdSample: [...mentionedL2Only].slice(0, 5).join(', '),
742
+ }));
743
+ }
807
744
  rows.push({
808
745
  'Test Case ID': testCaseId,
809
746
  'Test Case Title': String(testCaseTitleMap.get(testCaseId) || '').trim(),
@@ -921,11 +858,17 @@ class ResultDataProvider {
921
858
  createMewpCoverageRow(requirement, runStatus, bug, linkedL3L4) {
922
859
  const l2ReqIdNumeric = Number((requirement === null || requirement === void 0 ? void 0 : requirement.workItemId) || 0);
923
860
  const l2ReqId = l2ReqIdNumeric > 0 ? String(l2ReqIdNumeric) : '';
861
+ const srNumber = this.normalizeMewpRequirementCodeWithSuffix((requirement === null || requirement === void 0 ? void 0 : requirement.requirementId) || '');
924
862
  const l2ReqTitle = this.toMewpComparableText(requirement.title);
863
+ const reqName = this.deriveMewpRequirementDisplayName(srNumber, l2ReqTitle);
864
+ const l2Owner = this.toMewpComparableText(requirement.owner);
925
865
  const l2SubSystem = this.toMewpComparableText(requirement.subSystem);
926
866
  return {
927
867
  'L2 REQ ID': l2ReqId,
928
- 'L2 REQ Title': l2ReqTitle,
868
+ 'SR #': srNumber,
869
+ 'L2 REQ Title': reqName,
870
+ 'L2 REQ Full Title': l2ReqTitle,
871
+ 'L2 Owner': l2Owner,
929
872
  'L2 SubSystem': l2SubSystem,
930
873
  'L2 Run Status': runStatus,
931
874
  'Bug ID': Number.isFinite(Number(bug === null || bug === void 0 ? void 0 : bug.id)) && Number(bug === null || bug === void 0 ? void 0 : bug.id) > 0 ? Number(bug === null || bug === void 0 ? void 0 : bug.id) : '',
@@ -937,6 +880,20 @@ class ResultDataProvider {
937
880
  'L4 REQ Title': String((linkedL3L4 === null || linkedL3L4 === void 0 ? void 0 : linkedL3L4.l4Title) || '').trim(),
938
881
  };
939
882
  }
883
+ deriveMewpRequirementDisplayName(requirementCode, title) {
884
+ const normalizedTitle = this.toMewpComparableText(title);
885
+ if (!normalizedTitle)
886
+ return '';
887
+ const normalizedCode = this.normalizeMewpRequirementCodeWithSuffix(requirementCode || '');
888
+ if (!normalizedCode)
889
+ return normalizedTitle;
890
+ const escapedCode = normalizedCode.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
891
+ const codePrefixPattern = new RegExp(`^${escapedCode}(?:\\s*[:\\-–—]\\s*|\\s+)`, 'i');
892
+ const withoutCodePrefix = normalizedTitle.replace(codePrefixPattern, '').trim();
893
+ if (!withoutCodePrefix)
894
+ return normalizedTitle;
895
+ return withoutCodePrefix;
896
+ }
940
897
  createEmptyMewpCoverageBugCell() {
941
898
  return { id: '', title: '', responsibility: '' };
942
899
  }
@@ -1192,7 +1149,9 @@ class ResultDataProvider {
1192
1149
  const baseKey = String((requirement === null || requirement === void 0 ? void 0 : requirement.baseKey) || '').trim();
1193
1150
  if (!baseKey)
1194
1151
  continue;
1195
- const normalized = this.resolveMewpResponsibility(this.toMewpComparableText(requirement === null || requirement === void 0 ? void 0 : requirement.responsibility));
1152
+ const rawOwner = this.toMewpComparableText(requirement === null || requirement === void 0 ? void 0 : requirement.owner);
1153
+ const normalized = this.resolveMewpResponsibility(rawOwner) ||
1154
+ this.resolveMewpResponsibility(this.toMewpComparableText(requirement === null || requirement === void 0 ? void 0 : requirement.responsibility));
1196
1155
  if (!normalized)
1197
1156
  continue;
1198
1157
  const existing = out.get(baseKey) || '';
@@ -1289,6 +1248,48 @@ class ResultDataProvider {
1289
1248
  }
1290
1249
  return map;
1291
1250
  }
1251
+ /**
1252
+ * Builds a lookup of test case id -> test case description using suite payload data.
1253
+ *
1254
+ * Resolution order:
1255
+ * 1) direct description fields on test-case payload (`testCase.description` / `workItem.description`)
1256
+ * 2) `System.Description` / `Description` in work-item fields map
1257
+ * 3) `System.Description` / `Description` from work-item field list (`workItemFields`)
1258
+ */
1259
+ buildMewpTestCaseDescriptionMap(testData) {
1260
+ var _a, _b, _c, _d;
1261
+ const map = new Map();
1262
+ const readDescriptionFromFields = (fields) => {
1263
+ var _a;
1264
+ const value = (_a = this.getFieldValueByName(fields, 'System.Description')) !== null && _a !== void 0 ? _a : this.getFieldValueByName(fields, 'Description');
1265
+ return this.toMewpComparableText(value);
1266
+ };
1267
+ for (const suite of testData || []) {
1268
+ const testCasesItems = Array.isArray(suite === null || suite === void 0 ? void 0 : suite.testCasesItems) ? suite.testCasesItems : [];
1269
+ for (const testCase of testCasesItems) {
1270
+ const id = Number(((_a = testCase === null || testCase === void 0 ? void 0 : testCase.workItem) === null || _a === void 0 ? void 0 : _a.id) || (testCase === null || testCase === void 0 ? void 0 : testCase.testCaseId) || (testCase === null || testCase === void 0 ? void 0 : testCase.id));
1271
+ if (!Number.isFinite(id) || id <= 0 || map.has(id))
1272
+ continue;
1273
+ const fromDirect = this.toMewpComparableText((testCase === null || testCase === void 0 ? void 0 : testCase.description) || ((_b = testCase === null || testCase === void 0 ? void 0 : testCase.workItem) === null || _b === void 0 ? void 0 : _b.description));
1274
+ if (fromDirect) {
1275
+ map.set(id, fromDirect);
1276
+ continue;
1277
+ }
1278
+ const fieldsFromMap = ((_c = testCase === null || testCase === void 0 ? void 0 : testCase.workItem) === null || _c === void 0 ? void 0 : _c.fields) || {};
1279
+ const fromFieldsMap = readDescriptionFromFields(fieldsFromMap);
1280
+ if (fromFieldsMap) {
1281
+ map.set(id, fromFieldsMap);
1282
+ continue;
1283
+ }
1284
+ const fieldsFromList = this.extractWorkItemFieldsMap((_d = testCase === null || testCase === void 0 ? void 0 : testCase.workItem) === null || _d === void 0 ? void 0 : _d.workItemFields);
1285
+ const fromFieldsList = readDescriptionFromFields(fieldsFromList);
1286
+ if (fromFieldsList) {
1287
+ map.set(id, fromFieldsList);
1288
+ }
1289
+ }
1290
+ }
1291
+ return map;
1292
+ }
1292
1293
  extractMewpTestCaseId(runResult) {
1293
1294
  var _a;
1294
1295
  const testCaseId = Number((runResult === null || runResult === void 0 ? void 0 : runResult.testCaseId) || ((_a = runResult === null || runResult === void 0 ? void 0 : runResult.testCase) === null || _a === void 0 ? void 0 : _a.id) || 0);
@@ -1479,6 +1480,102 @@ class ResultDataProvider {
1479
1480
  }
1480
1481
  return out;
1481
1482
  }
1483
+ /**
1484
+ * Extracts SR requirement mentions from the "assumptions" section inside a test-case description.
1485
+ *
1486
+ * Notes:
1487
+ * - Heading detection is case-insensitive and keyed by "assumptions".
1488
+ * - Parsing is scoped to that section only and stops when a likely next section heading is reached
1489
+ * after at least one requirement-bearing line was collected.
1490
+ * - Returned stepRef is a synthetic marker ("Assumptions") so downstream discrepancy output can
1491
+ * clearly attribute source to description-level assumptions.
1492
+ */
1493
+ extractRequirementMentionsFromAssumptionsDescription(description, includeSuffix) {
1494
+ const lines = this.normalizeMewpDescriptionLines(description);
1495
+ if (lines.length === 0)
1496
+ return [];
1497
+ const assumptionsHeaderIndex = lines.findIndex((line) => ResultDataProvider.MEWP_INTERNAL_VALIDATION_ASSUMPTIONS_HEADER_PATTERN.test(line));
1498
+ if (assumptionsHeaderIndex < 0)
1499
+ return [];
1500
+ const assumptionCodes = new Set();
1501
+ let hasCollectedRequirementCodes = false;
1502
+ for (let index = assumptionsHeaderIndex + 1; index < lines.length; index += 1) {
1503
+ const rawLine = String(lines[index] || '').trim();
1504
+ if (!rawLine)
1505
+ continue;
1506
+ const line = rawLine.replace(/^[-*•]+\s*/, '').trim();
1507
+ if (!line)
1508
+ continue;
1509
+ const lineCodes = this.extractRequirementCodesFromExpectedText(line, includeSuffix);
1510
+ if (lineCodes.size > 0) {
1511
+ for (const code of lineCodes)
1512
+ assumptionCodes.add(code);
1513
+ hasCollectedRequirementCodes = true;
1514
+ continue;
1515
+ }
1516
+ if (hasCollectedRequirementCodes && this.isLikelyMewpDescriptionSectionHeading(line)) {
1517
+ break;
1518
+ }
1519
+ }
1520
+ if (assumptionCodes.size === 0)
1521
+ return [];
1522
+ return [{ stepRef: ResultDataProvider.MEWP_INTERNAL_VALIDATION_ASSUMPTIONS_REF, codes: assumptionCodes }];
1523
+ }
1524
+ /**
1525
+ * Normalizes raw HTML/plain description content into comparable text lines.
1526
+ * This makes section/title heuristics resilient to formatting differences.
1527
+ */
1528
+ normalizeMewpDescriptionLines(description) {
1529
+ const raw = String(description || '');
1530
+ if (!raw)
1531
+ return [];
1532
+ const normalized = raw
1533
+ .replace(/\r\n?/g, '\n')
1534
+ .replace(/<\s*br\s*\/?>/gi, '\n')
1535
+ // Underlined sections are commonly used as inline HTML titles in MEWP test-case descriptions.
1536
+ // Treat underline tags as boundaries so compact forms like <u><b>Title</b></u><p>... preserve section split.
1537
+ .replace(/<\s*u\b[^>]*>/gi, '\n')
1538
+ .replace(/<\/\s*u\s*>/gi, '\n')
1539
+ .replace(/<\s*li\b[^>]*>/gi, '\n- ')
1540
+ .replace(/<\/\s*(p|div|li|ul|ol|tr|td|h[1-6])\s*>/gi, '\n')
1541
+ .replace(/&nbsp;|&#160;|&#xA0;/gi, ' ')
1542
+ .replace(/&lt;/gi, '<')
1543
+ .replace(/&gt;/gi, '>')
1544
+ .replace(/&amp;/gi, '&')
1545
+ .replace(/&quot;/gi, '"')
1546
+ .replace(/&#39;|&apos;/gi, "'")
1547
+ .replace(/<[^>]*>/g, ' ')
1548
+ .replace(/[\u200B-\u200D\uFEFF]/g, '')
1549
+ .replace(/[ \t]+/g, ' ');
1550
+ return normalized
1551
+ .split('\n')
1552
+ .map((line) => String(line || '').trim())
1553
+ .filter((line) => !!line);
1554
+ }
1555
+ /**
1556
+ * Heuristic detector for description section headings used to stop assumptions parsing.
1557
+ * A line is considered a heading when it is short/title-like and does not itself contain SR codes.
1558
+ */
1559
+ isLikelyMewpDescriptionSectionHeading(line) {
1560
+ const normalized = String(line || '')
1561
+ .replace(/^[-*•]+\s*/, '')
1562
+ .trim();
1563
+ if (!normalized)
1564
+ return false;
1565
+ if (ResultDataProvider.MEWP_INTERNAL_VALIDATION_ASSUMPTIONS_HEADER_PATTERN.test(normalized))
1566
+ return false;
1567
+ if (this.extractRequirementCodesFromExpectedText(normalized, true).size > 0)
1568
+ return false;
1569
+ const compact = normalized.replace(/[.:;,\-–—]+$/, '').trim();
1570
+ if (!compact)
1571
+ return false;
1572
+ const wordCount = compact.split(/\s+/).filter((item) => !!item).length;
1573
+ if (wordCount === 0 || wordCount > 12)
1574
+ return false;
1575
+ if (/[.;!?]/.test(compact))
1576
+ return false;
1577
+ return /^[a-z0-9\s()&/+_'-]+$/i.test(compact);
1578
+ }
1482
1579
  extractRequirementCodesFromExpectedText(text, includeSuffix) {
1483
1580
  const out = new Set();
1484
1581
  const source = this.normalizeRequirementStepText(text);
@@ -1648,6 +1745,7 @@ class ResultDataProvider {
1648
1745
  requirementId,
1649
1746
  baseKey: this.toRequirementKey(requirementId),
1650
1747
  title: this.toMewpComparableText((fields === null || fields === void 0 ? void 0 : fields['System.Title']) || (wi === null || wi === void 0 ? void 0 : wi.title)),
1748
+ owner: this.deriveMewpRequirementOwner(fields),
1651
1749
  subSystem: this.deriveMewpSubSystem(fields),
1652
1750
  responsibility: this.deriveMewpResponsibility(fields),
1653
1751
  linkedTestCaseIds: this.extractLinkedTestCaseIdsFromRequirement((wi === null || wi === void 0 ? void 0 : wi.relations) || []),
@@ -1690,6 +1788,8 @@ class ResultDataProvider {
1690
1788
  score += 2;
1691
1789
  if (String((item === null || item === void 0 ? void 0 : item.title) || '').trim())
1692
1790
  score += 1;
1791
+ if (String((item === null || item === void 0 ? void 0 : item.owner) || '').trim())
1792
+ score += 1;
1693
1793
  if (String((item === null || item === void 0 ? void 0 : item.subSystem) || '').trim())
1694
1794
  score += 1;
1695
1795
  if (String((item === null || item === void 0 ? void 0 : item.responsibility) || '').trim())
@@ -1723,14 +1823,15 @@ class ResultDataProvider {
1723
1823
  }
1724
1824
  return [...families.entries()]
1725
1825
  .map(([baseKey, family]) => {
1726
- var _a, _b, _c, _d, _e;
1826
+ var _a, _b, _c, _d, _e, _f;
1727
1827
  return ({
1728
1828
  workItemId: Number(((_a = family === null || family === void 0 ? void 0 : family.representative) === null || _a === void 0 ? void 0 : _a.workItemId) || 0),
1729
1829
  requirementId: String(((_b = family === null || family === void 0 ? void 0 : family.representative) === null || _b === void 0 ? void 0 : _b.requirementId) || baseKey),
1730
1830
  baseKey,
1731
1831
  title: String(((_c = family === null || family === void 0 ? void 0 : family.representative) === null || _c === void 0 ? void 0 : _c.title) || ''),
1732
- subSystem: String(((_d = family === null || family === void 0 ? void 0 : family.representative) === null || _d === void 0 ? void 0 : _d.subSystem) || ''),
1733
- responsibility: String(((_e = family === null || family === void 0 ? void 0 : family.representative) === null || _e === void 0 ? void 0 : _e.responsibility) || ''),
1832
+ owner: String(((_d = family === null || family === void 0 ? void 0 : family.representative) === null || _d === void 0 ? void 0 : _d.owner) || ''),
1833
+ subSystem: String(((_e = family === null || family === void 0 ? void 0 : family.representative) === null || _e === void 0 ? void 0 : _e.subSystem) || ''),
1834
+ responsibility: String(((_f = family === null || family === void 0 ? void 0 : family.representative) === null || _f === void 0 ? void 0 : _f.responsibility) || ''),
1734
1835
  linkedTestCaseIds: [...family.linkedTestCaseIds].sort((a, b) => a - b),
1735
1836
  });
1736
1837
  })
@@ -2159,6 +2260,24 @@ class ResultDataProvider {
2159
2260
  }
2160
2261
  return '';
2161
2262
  }
2263
+ // L2 owner is sourced only from requirement SAPWBS fields.
2264
+ deriveMewpRequirementOwner(fields) {
2265
+ const directCandidates = [fields === null || fields === void 0 ? void 0 : fields['Custom.SAPWBS'], fields === null || fields === void 0 ? void 0 : fields['SAPWBS']];
2266
+ for (const candidate of directCandidates) {
2267
+ const normalized = this.toMewpComparableText(candidate);
2268
+ if (normalized)
2269
+ return normalized;
2270
+ }
2271
+ for (const [key, value] of Object.entries(fields || {})) {
2272
+ const normalizedKey = String(key || '').toLowerCase();
2273
+ if (!normalizedKey.includes('sapwbs'))
2274
+ continue;
2275
+ const normalizedValue = this.toMewpComparableText(value);
2276
+ if (normalizedValue)
2277
+ return normalizedValue;
2278
+ }
2279
+ return '';
2280
+ }
2162
2281
  // Test-case responsibility must come from test-case path context (not SAPWBS).
2163
2282
  deriveMewpTestCaseResponsibility(fields) {
2164
2283
  const areaPathCandidates = [
@@ -4511,9 +4630,13 @@ class ResultDataProvider {
4511
4630
  ResultDataProvider.MEWP_INTERNAL_VALIDATION_TRACE_TAG = '[MEWP][InternalValidation][Trace]';
4512
4631
  ResultDataProvider.MEWP_INTERNAL_VALIDATION_DIAGNOSTICS_TAG = '[MEWP][InternalValidation][Diagnostics]';
4513
4632
  ResultDataProvider.MEWP_INTERNAL_VALIDATION_SUMMARY_TAG = '[MEWP][InternalValidation][Summary]';
4633
+ ResultDataProvider.MEWP_INTERNAL_VALIDATION_ASSUMPTIONS_REF = 'Assumptions';
4634
+ ResultDataProvider.MEWP_INTERNAL_VALIDATION_ASSUMPTIONS_HEADER_PATTERN = /assumptions/i;
4514
4635
  ResultDataProvider.MEWP_L2_COVERAGE_COLUMNS = [
4515
4636
  'L2 REQ ID',
4637
+ 'SR #',
4516
4638
  'L2 REQ Title',
4639
+ 'L2 Owner',
4517
4640
  'L2 SubSystem',
4518
4641
  'L2 Run Status',
4519
4642
  'Bug ID',