@elisra-devops/docgen-data-provider 1.78.0 → 1.80.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.
@@ -36,6 +36,7 @@ export interface MewpL2RequirementWorkItem {
36
36
  areaPath: string;
37
37
  }
38
38
  export interface MewpL2RequirementFamily {
39
+ workItemId?: number;
39
40
  requirementId: string;
40
41
  baseKey: string;
41
42
  title: string;
@@ -86,7 +86,6 @@ export default class ResultDataProvider {
86
86
  private createEmptyMewpCoverageBugCell;
87
87
  private createEmptyMewpCoverageL3L4Cell;
88
88
  private buildMewpCoverageL3L4Rows;
89
- private formatMewpCustomerId;
90
89
  private buildMewpCoverageRows;
91
90
  private resolveCoverageBugResponsibility;
92
91
  private resolveMewpL2RunStatus;
@@ -119,6 +118,8 @@ export default class ResultDataProvider {
119
118
  private fetchMewpL2Requirements;
120
119
  private isMewpL2AreaPath;
121
120
  private collapseMewpRequirementFamilies;
121
+ private buildMewpL2ToLinkedL1BaseKeys;
122
+ private buildMewpExternalJoinKeysByL2Requirement;
122
123
  private buildRequirementFamilyMap;
123
124
  private buildLinkedRequirementsByTestCase;
124
125
  private resolveMewpRequirementScopeKeysFromQuery;
@@ -304,6 +304,7 @@ class ResultDataProvider {
304
304
  const linkedRequirementsByTestCase = await this.buildLinkedRequirementsByTestCase(allRequirements, testData, projectName);
305
305
  const scopedRequirementKeys = await this.resolveMewpRequirementScopeKeysFromQuery(linkedQueryRequest, allRequirements, linkedRequirementsByTestCase);
306
306
  const requirements = this.collapseMewpRequirementFamilies(allRequirements, (scopedRequirementKeys === null || scopedRequirementKeys === void 0 ? void 0 : scopedRequirementKeys.size) ? scopedRequirementKeys : undefined);
307
+ const l2ToLinkedL1BaseKeys = await this.buildMewpL2ToLinkedL1BaseKeys(allRequirements, projectName, testData);
307
308
  const requirementSapWbsByBaseKey = this.buildRequirementSapWbsByBaseKey(allRequirements);
308
309
  const externalBugsByTestCase = await this.loadExternalBugsByTestCase(options === null || options === void 0 ? void 0 : options.externalBugsFile);
309
310
  const externalL3L4ByBaseKey = await this.loadExternalL3L4ByBaseKey(options === null || options === void 0 ? void 0 : options.externalL3L4File, requirementSapWbsByBaseKey);
@@ -333,6 +334,7 @@ class ResultDataProvider {
333
334
  if (requirements.length === 0) {
334
335
  return Object.assign(Object.assign({}, defaultPayload), { sheetName: this.buildMewpCoverageSheetName(planName, testPlanId) });
335
336
  }
337
+ const externalJoinKeysByL2 = this.buildMewpExternalJoinKeysByL2Requirement(requirements, l2ToLinkedL1BaseKeys);
336
338
  const requirementIndex = new Map();
337
339
  const observedTestCaseIdsByRequirement = new Map();
338
340
  const requirementKeys = new Set();
@@ -382,8 +384,15 @@ class ResultDataProvider {
382
384
  this.accumulateRequirementCountsFromActionResults(fallbackActionResults, testCaseId, requirementKeys, requirementIndex, observedTestCaseIdsByRequirement);
383
385
  }
384
386
  const requirementBaseKeys = new Set(requirements.map((item) => String((item === null || item === void 0 ? void 0 : item.baseKey) || '').trim()).filter((item) => !!item));
387
+ const externalJoinKeyUniverse = new Set();
388
+ for (const keySet of externalJoinKeysByL2.values()) {
389
+ for (const key of keySet) {
390
+ if (key)
391
+ externalJoinKeyUniverse.add(key);
392
+ }
393
+ }
385
394
  const externalL3L4BaseKeys = new Set([...externalL3L4ByBaseKey.keys()]);
386
- const externalL3L4OverlapKeys = [...externalL3L4BaseKeys].filter((key) => requirementBaseKeys.has(key));
395
+ const externalL3L4OverlapKeys = [...externalL3L4BaseKeys].filter((key) => externalJoinKeyUniverse.has(key));
387
396
  const failedRequirementBaseKeys = new Set();
388
397
  const failedTestCaseIds = new Set();
389
398
  for (const [requirementBaseKey, byTestCase] of requirementIndex.entries()) {
@@ -404,9 +413,10 @@ class ResultDataProvider {
404
413
  externalBugBaseKeys.add(key);
405
414
  }
406
415
  }
407
- const externalBugRequirementOverlap = [...externalBugBaseKeys].filter((key) => requirementBaseKeys.has(key));
408
- const externalBugFailedRequirementOverlap = [...externalBugBaseKeys].filter((key) => failedRequirementBaseKeys.has(key));
416
+ const externalBugRequirementOverlap = [...externalBugBaseKeys].filter((key) => externalJoinKeyUniverse.has(key));
417
+ const externalBugFailedRequirementOverlap = [...externalBugBaseKeys].filter((key) => externalJoinKeyUniverse.has(key));
409
418
  logger_1.default.info(`MEWP coverage join diagnostics: requirementBaseKeys=${requirementBaseKeys.size} ` +
419
+ `externalJoinKeys=${externalJoinKeyUniverse.size} ` +
410
420
  `failedRequirementBaseKeys=${failedRequirementBaseKeys.size} failedTestCases=${failedTestCaseIds.size}; ` +
411
421
  `externalL3L4BaseKeys=${externalL3L4BaseKeys.size} externalL3L4Overlap=${externalL3L4OverlapKeys.length}; ` +
412
422
  `externalBugTestCases=${externalBugTestCaseIds.size} externalBugFailedTestCaseOverlap=${externalBugFailedTestCaseOverlap.length}; ` +
@@ -414,21 +424,25 @@ class ResultDataProvider {
414
424
  `externalBugFailedRequirementOverlap=${externalBugFailedRequirementOverlap.length}`);
415
425
  if (externalL3L4BaseKeys.size > 0 && externalL3L4OverlapKeys.length === 0) {
416
426
  const sampleReq = [...requirementBaseKeys].slice(0, 5);
427
+ const sampleJoin = [...externalJoinKeyUniverse].slice(0, 5);
417
428
  const sampleExt = [...externalL3L4BaseKeys].slice(0, 5);
418
429
  logger_1.default.warn(`MEWP coverage join diagnostics: no L3/L4 key overlap found. ` +
419
- `sampleRequirementKeys=${sampleReq.join(', ')} sampleExternalL3L4Keys=${sampleExt.join(', ')}`);
430
+ `sampleRequirementKeys=${sampleReq.join(', ')} sampleJoinKeys=${sampleJoin.join(', ')} ` +
431
+ `sampleExternalL3L4Keys=${sampleExt.join(', ')}`);
420
432
  }
421
433
  if (externalBugBaseKeys.size > 0 && externalBugRequirementOverlap.length === 0) {
422
434
  const sampleReq = [...requirementBaseKeys].slice(0, 5);
435
+ const sampleJoin = [...externalJoinKeyUniverse].slice(0, 5);
423
436
  const sampleExt = [...externalBugBaseKeys].slice(0, 5);
424
437
  logger_1.default.warn(`MEWP coverage join diagnostics: no bug requirement-key overlap found. ` +
425
- `sampleRequirementKeys=${sampleReq.join(', ')} sampleExternalBugKeys=${sampleExt.join(', ')}`);
438
+ `sampleRequirementKeys=${sampleReq.join(', ')} sampleJoinKeys=${sampleJoin.join(', ')} ` +
439
+ `sampleExternalBugKeys=${sampleExt.join(', ')}`);
426
440
  }
427
441
  if (externalBugTestCaseIds.size > 0 && externalBugFailedTestCaseOverlap.length === 0) {
428
442
  logger_1.default.warn(`MEWP coverage join diagnostics: no overlap between external bug test cases and failed test cases. ` +
429
443
  `Bug rows remain empty because bugs are shown only for failed L2s.`);
430
444
  }
431
- const rows = this.buildMewpCoverageRows(requirements, requirementIndex, observedTestCaseIdsByRequirement, linkedRequirementsByTestCase, externalL3L4ByBaseKey, externalBugsByTestCase);
445
+ const rows = this.buildMewpCoverageRows(requirements, requirementIndex, observedTestCaseIdsByRequirement, linkedRequirementsByTestCase, externalL3L4ByBaseKey, externalBugsByTestCase, externalJoinKeysByL2);
432
446
  const coverageRowStats = rows.reduce((acc, row) => {
433
447
  const hasBug = Number((row === null || row === void 0 ? void 0 : row['Bug ID']) || 0) > 0;
434
448
  const hasL3 = String((row === null || row === void 0 ? void 0 : row['L3 REQ ID']) || '').trim() !== '';
@@ -493,12 +507,24 @@ class ResultDataProvider {
493
507
  }
494
508
  }
495
509
  const validL2BaseKeys = new Set([...requirementFamilies.keys()]);
510
+ const diagnostics = {
511
+ totalTestCases: 0,
512
+ totalParsedSteps: 0,
513
+ totalStepsWithMentions: 0,
514
+ totalMentionedCustomerIds: 0,
515
+ testCasesWithoutMentionedCustomerIds: 0,
516
+ failingRows: 0,
517
+ };
496
518
  for (const testCaseId of [...allTestCaseIds].sort((a, b) => a - b)) {
519
+ diagnostics.totalTestCases += 1;
497
520
  const stepsXml = stepsXmlByTestCase.get(testCaseId) || '';
498
521
  const parsedSteps = stepsXml && String(stepsXml).trim() !== ''
499
522
  ? await this.testStepParserHelper.parseTestSteps(stepsXml, new Map())
500
523
  : [];
524
+ const executableSteps = parsedSteps.filter((step) => !(step === null || step === void 0 ? void 0 : step.isSharedStepTitle));
525
+ diagnostics.totalParsedSteps += executableSteps.length;
501
526
  const mentionEntries = this.extractRequirementMentionsFromExpectedSteps(parsedSteps, true);
527
+ diagnostics.totalStepsWithMentions += mentionEntries.length;
502
528
  const mentionedL2Only = new Set();
503
529
  const mentionedCodeFirstStep = new Map();
504
530
  const mentionedBaseFirstStep = new Map();
@@ -521,6 +547,10 @@ class ResultDataProvider {
521
547
  }
522
548
  }
523
549
  }
550
+ diagnostics.totalMentionedCustomerIds += mentionedL2Only.size;
551
+ if (mentionedL2Only.size === 0) {
552
+ diagnostics.testCasesWithoutMentionedCustomerIds += 1;
553
+ }
524
554
  const mentionedBaseKeys = new Set([...mentionedL2Only].map((code) => this.toRequirementKey(code)).filter((code) => !!code));
525
555
  const expectedFamilyCodes = new Set();
526
556
  for (const baseKey of mentionedBaseKeys) {
@@ -597,6 +627,14 @@ class ResultDataProvider {
597
627
  .join('; ');
598
628
  const linkedButNotMentioned = sortedExtraLinked.join('; ');
599
629
  const validationStatus = mentionedButNotLinked || linkedButNotMentioned ? 'Fail' : 'Pass';
630
+ if (validationStatus === 'Fail')
631
+ diagnostics.failingRows += 1;
632
+ logger_1.default.debug(`MEWP internal validation parse diagnostics: ` +
633
+ `testCaseId=${testCaseId} parsedSteps=${executableSteps.length} ` +
634
+ `stepsWithMentions=${mentionEntries.length} customerIdsFound=${mentionedL2Only.size} ` +
635
+ `linkedRequirements=${linkedFullCodes.size} mentionedButNotLinked=${sortedMissingMentioned.length + sortedMissingFamily.length} ` +
636
+ `linkedButNotMentioned=${sortedExtraLinked.length} status=${validationStatus} ` +
637
+ `customerIdSample='${[...mentionedL2Only].slice(0, 5).join(', ')}'`);
600
638
  rows.push({
601
639
  'Test Case ID': testCaseId,
602
640
  'Test Case Title': String(testCaseTitleMap.get(testCaseId) || '').trim(),
@@ -605,6 +643,11 @@ class ResultDataProvider {
605
643
  'Validation Status': validationStatus,
606
644
  });
607
645
  }
646
+ logger_1.default.info(`MEWP internal validation summary: testCases=${diagnostics.totalTestCases} ` +
647
+ `parsedSteps=${diagnostics.totalParsedSteps} stepsWithMentions=${diagnostics.totalStepsWithMentions} ` +
648
+ `totalCustomerIdsFound=${diagnostics.totalMentionedCustomerIds} ` +
649
+ `testCasesWithoutCustomerIds=${diagnostics.testCasesWithoutMentionedCustomerIds} ` +
650
+ `failingRows=${diagnostics.failingRows}`);
608
651
  return {
609
652
  sheetName: this.buildInternalValidationSheetName(planName, testPlanId),
610
653
  columnOrder: [...ResultDataProvider.INTERNAL_VALIDATION_COLUMNS],
@@ -694,7 +737,8 @@ class ResultDataProvider {
694
737
  return `MEWP Internal Validation - ${suffix}`;
695
738
  }
696
739
  createMewpCoverageRow(requirement, runStatus, bug, linkedL3L4) {
697
- const l2ReqId = this.formatMewpCustomerId(requirement.requirementId);
740
+ const l2ReqIdNumeric = Number((requirement === null || requirement === void 0 ? void 0 : requirement.workItemId) || 0);
741
+ const l2ReqId = l2ReqIdNumeric > 0 ? String(l2ReqIdNumeric) : '';
698
742
  const l2ReqTitle = this.toMewpComparableText(requirement.title);
699
743
  const l2SubSystem = this.toMewpComparableText(requirement.subSystem);
700
744
  return {
@@ -718,7 +762,22 @@ class ResultDataProvider {
718
762
  return { l3Id: '', l3Title: '', l4Id: '', l4Title: '' };
719
763
  }
720
764
  buildMewpCoverageL3L4Rows(links) {
721
- const sorted = [...(links || [])].sort((a, b) => {
765
+ const deduped = new Map();
766
+ for (const item of links || []) {
767
+ const level = (item === null || item === void 0 ? void 0 : item.level) === 'L4' ? 'L4' : 'L3';
768
+ const id = String((item === null || item === void 0 ? void 0 : item.id) || '').trim();
769
+ if (!id)
770
+ continue;
771
+ const key = `${level}:${id}`;
772
+ if (!deduped.has(key)) {
773
+ deduped.set(key, {
774
+ id,
775
+ level,
776
+ title: String((item === null || item === void 0 ? void 0 : item.title) || '').trim(),
777
+ });
778
+ }
779
+ }
780
+ const sorted = [...deduped.values()].sort((a, b) => {
722
781
  if (a.level !== b.level)
723
782
  return a.level === 'L3' ? -1 : 1;
724
783
  return String(a.id || '').localeCompare(String(b.id || ''));
@@ -735,21 +794,14 @@ class ResultDataProvider {
735
794
  }
736
795
  return rows;
737
796
  }
738
- formatMewpCustomerId(rawValue) {
739
- const normalized = this.normalizeMewpRequirementCode(this.toMewpComparableText(rawValue));
740
- if (normalized)
741
- return normalized;
742
- const onlyDigits = String(rawValue || '').replace(/\D/g, '');
743
- if (onlyDigits)
744
- return `SR${onlyDigits}`;
745
- return '';
746
- }
747
- buildMewpCoverageRows(requirements, requirementIndex, observedTestCaseIdsByRequirement, linkedRequirementsByTestCase, l3l4ByBaseKey, externalBugsByTestCase) {
797
+ buildMewpCoverageRows(requirements, requirementIndex, observedTestCaseIdsByRequirement, linkedRequirementsByTestCase, l3l4ByBaseKey, externalBugsByTestCase, externalJoinKeysByL2) {
748
798
  var _a;
749
799
  const rows = [];
750
800
  const linkedByRequirement = this.invertBaseRequirementLinks(linkedRequirementsByTestCase);
801
+ const joinKeysByRequirement = externalJoinKeysByL2 || new Map();
751
802
  for (const requirement of requirements) {
752
803
  const key = String((requirement === null || requirement === void 0 ? void 0 : requirement.baseKey) || this.toRequirementKey(requirement.requirementId) || '').trim();
804
+ const externalJoinKeys = joinKeysByRequirement.get(key) || new Set([key]);
753
805
  const linkedTestCaseIds = ((requirement === null || requirement === void 0 ? void 0 : requirement.linkedTestCaseIds) || []).filter((id) => Number.isFinite(id) && Number(id) > 0);
754
806
  const linkedByTestCase = key ? Array.from(linkedByRequirement.get(key) || []) : [];
755
807
  const observedTestCaseIds = key
@@ -771,7 +823,7 @@ class ResultDataProvider {
771
823
  const externalBugs = externalBugsByTestCase.get(testCaseId) || [];
772
824
  for (const bug of externalBugs) {
773
825
  const bugBaseKey = String((bug === null || bug === void 0 ? void 0 : bug.requirementBaseKey) || '').trim();
774
- if (bugBaseKey && bugBaseKey !== key)
826
+ if (bugBaseKey && !externalJoinKeys.has(bugBaseKey))
775
827
  continue;
776
828
  const bugId = Number((bug === null || bug === void 0 ? void 0 : bug.id) || 0);
777
829
  if (!Number.isFinite(bugId) || bugId <= 0)
@@ -789,7 +841,7 @@ class ResultDataProvider {
789
841
  const bugsForRows = runStatus === 'Fail'
790
842
  ? Array.from(aggregatedBugs.values()).sort((a, b) => a.id - b.id)
791
843
  : [];
792
- const l3l4ForRows = [...(l3l4ByBaseKey.get(key) || [])];
844
+ const l3l4ForRows = [...externalJoinKeys].flatMap((joinKey) => l3l4ByBaseKey.get(joinKey) || []);
793
845
  const bugRows = bugsForRows.length > 0
794
846
  ? bugsForRows
795
847
  : [];
@@ -1369,7 +1421,7 @@ class ResultDataProvider {
1369
1421
  const workItems = await this.fetchWorkItemsByIds(projectName, requirementIds, true);
1370
1422
  const requirements = workItems.map((wi) => {
1371
1423
  const fields = (wi === null || wi === void 0 ? void 0 : wi.fields) || {};
1372
- const requirementId = this.extractMewpRequirementIdentifier(fields, Number((wi === null || wi === void 0 ? void 0 : wi.id) || 0));
1424
+ const requirementId = this.extractMewpRequirementIdentifier(fields);
1373
1425
  const areaPath = this.toMewpComparableText(fields === null || fields === void 0 ? void 0 : fields['System.AreaPath']);
1374
1426
  return {
1375
1427
  workItemId: Number((wi === null || wi === void 0 ? void 0 : wi.id) || 0),
@@ -1451,18 +1503,132 @@ class ResultDataProvider {
1451
1503
  }
1452
1504
  return [...families.entries()]
1453
1505
  .map(([baseKey, family]) => {
1454
- var _a, _b, _c, _d;
1506
+ var _a, _b, _c, _d, _e;
1455
1507
  return ({
1456
- requirementId: String(((_a = family === null || family === void 0 ? void 0 : family.representative) === null || _a === void 0 ? void 0 : _a.requirementId) || baseKey),
1508
+ workItemId: Number(((_a = family === null || family === void 0 ? void 0 : family.representative) === null || _a === void 0 ? void 0 : _a.workItemId) || 0),
1509
+ requirementId: String(((_b = family === null || family === void 0 ? void 0 : family.representative) === null || _b === void 0 ? void 0 : _b.requirementId) || baseKey),
1457
1510
  baseKey,
1458
- title: String(((_b = family === null || family === void 0 ? void 0 : family.representative) === null || _b === void 0 ? void 0 : _b.title) || ''),
1459
- subSystem: String(((_c = family === null || family === void 0 ? void 0 : family.representative) === null || _c === void 0 ? void 0 : _c.subSystem) || ''),
1460
- responsibility: String(((_d = family === null || family === void 0 ? void 0 : family.representative) === null || _d === void 0 ? void 0 : _d.responsibility) || ''),
1511
+ title: String(((_c = family === null || family === void 0 ? void 0 : family.representative) === null || _c === void 0 ? void 0 : _c.title) || ''),
1512
+ subSystem: String(((_d = family === null || family === void 0 ? void 0 : family.representative) === null || _d === void 0 ? void 0 : _d.subSystem) || ''),
1513
+ responsibility: String(((_e = family === null || family === void 0 ? void 0 : family.representative) === null || _e === void 0 ? void 0 : _e.responsibility) || ''),
1461
1514
  linkedTestCaseIds: [...family.linkedTestCaseIds].sort((a, b) => a - b),
1462
1515
  });
1463
1516
  })
1464
1517
  .sort((a, b) => String(a.requirementId).localeCompare(String(b.requirementId)));
1465
1518
  }
1519
+ async buildMewpL2ToLinkedL1BaseKeys(requirements, projectName, testData) {
1520
+ const out = new Map();
1521
+ const relatedIds = new Set();
1522
+ const linkedTestCaseIdsByL2 = new Map();
1523
+ const testCaseTitleMap = this.buildMewpTestCaseTitleMap(testData);
1524
+ for (const requirement of requirements || []) {
1525
+ const l2BaseKey = String((requirement === null || requirement === void 0 ? void 0 : requirement.baseKey) || '').trim();
1526
+ if (!l2BaseKey)
1527
+ continue;
1528
+ if (!out.has(l2BaseKey))
1529
+ out.set(l2BaseKey, new Set());
1530
+ if (!linkedTestCaseIdsByL2.has(l2BaseKey))
1531
+ linkedTestCaseIdsByL2.set(l2BaseKey, new Set());
1532
+ for (const testCaseId of (requirement === null || requirement === void 0 ? void 0 : requirement.linkedTestCaseIds) || []) {
1533
+ const numeric = Number(testCaseId);
1534
+ if (Number.isFinite(numeric) && numeric > 0) {
1535
+ linkedTestCaseIdsByL2.get(l2BaseKey).add(numeric);
1536
+ }
1537
+ }
1538
+ for (const relatedId of (requirement === null || requirement === void 0 ? void 0 : requirement.relatedWorkItemIds) || []) {
1539
+ const id = Number(relatedId);
1540
+ if (Number.isFinite(id) && id > 0)
1541
+ relatedIds.add(id);
1542
+ }
1543
+ }
1544
+ let titleDerivedCount = 0;
1545
+ for (const [l2BaseKey, testCaseIds] of linkedTestCaseIdsByL2.entries()) {
1546
+ const targetSet = out.get(l2BaseKey) || new Set();
1547
+ for (const testCaseId of testCaseIds) {
1548
+ const title = String(testCaseTitleMap.get(testCaseId) || '').trim();
1549
+ if (!title)
1550
+ continue;
1551
+ const fromTitleCodes = this.extractRequirementCodesFromExpectedText(title, false);
1552
+ if (fromTitleCodes.size > 0) {
1553
+ for (const code of fromTitleCodes) {
1554
+ const normalized = this.toRequirementKey(code);
1555
+ if (normalized)
1556
+ targetSet.add(normalized);
1557
+ }
1558
+ }
1559
+ else {
1560
+ const normalized = this.toRequirementKey(title);
1561
+ if (normalized)
1562
+ targetSet.add(normalized);
1563
+ }
1564
+ }
1565
+ if (targetSet.size > 0) {
1566
+ titleDerivedCount += 1;
1567
+ }
1568
+ out.set(l2BaseKey, targetSet);
1569
+ }
1570
+ if (relatedIds.size === 0) {
1571
+ const linkedL1Count = [...out.values()].reduce((sum, set) => sum + set.size, 0);
1572
+ logger_1.default.info(`MEWP L2->L1 mapping summary: l2Families=${out.size} ` +
1573
+ `fromTitle=${titleDerivedCount} fallbackFromLinkedL1=0 linkedL1Keys=${linkedL1Count}`);
1574
+ return out;
1575
+ }
1576
+ const relatedWorkItems = await this.fetchWorkItemsByIds(projectName, [...relatedIds], false);
1577
+ const l1BaseByWorkItemId = new Map();
1578
+ for (const workItem of relatedWorkItems || []) {
1579
+ const workItemId = Number((workItem === null || workItem === void 0 ? void 0 : workItem.id) || 0);
1580
+ if (!Number.isFinite(workItemId) || workItemId <= 0)
1581
+ continue;
1582
+ const fields = (workItem === null || workItem === void 0 ? void 0 : workItem.fields) || {};
1583
+ const customerId = this.extractMewpRequirementIdentifier(fields);
1584
+ const baseKey = this.toRequirementKey(customerId);
1585
+ if (!baseKey)
1586
+ continue;
1587
+ l1BaseByWorkItemId.set(workItemId, baseKey);
1588
+ }
1589
+ let linkedFallbackCount = 0;
1590
+ for (const requirement of requirements || []) {
1591
+ const l2BaseKey = String((requirement === null || requirement === void 0 ? void 0 : requirement.baseKey) || '').trim();
1592
+ if (!l2BaseKey)
1593
+ continue;
1594
+ if (!out.has(l2BaseKey))
1595
+ out.set(l2BaseKey, new Set());
1596
+ const targetSet = out.get(l2BaseKey);
1597
+ if (targetSet.size > 0) {
1598
+ continue;
1599
+ }
1600
+ for (const relatedId of (requirement === null || requirement === void 0 ? void 0 : requirement.relatedWorkItemIds) || []) {
1601
+ const baseKey = l1BaseByWorkItemId.get(Number(relatedId));
1602
+ if (baseKey)
1603
+ targetSet.add(baseKey);
1604
+ }
1605
+ if (targetSet.size > 0) {
1606
+ linkedFallbackCount += 1;
1607
+ }
1608
+ }
1609
+ const l2Families = out.size;
1610
+ const withLinkedL1 = [...out.values()].filter((set) => set.size > 0).length;
1611
+ const linkedL1Count = [...out.values()].reduce((sum, set) => sum + set.size, 0);
1612
+ logger_1.default.info(`MEWP L2->L1 mapping summary: l2Families=${l2Families} withLinkedL1=${withLinkedL1} ` +
1613
+ `fromTitle=${titleDerivedCount} fallbackFromLinkedL1=${linkedFallbackCount} linkedL1Keys=${linkedL1Count}`);
1614
+ return out;
1615
+ }
1616
+ buildMewpExternalJoinKeysByL2Requirement(requirements, l2ToLinkedL1BaseKeys) {
1617
+ const out = new Map();
1618
+ for (const requirement of requirements || []) {
1619
+ const l2BaseKey = String((requirement === null || requirement === void 0 ? void 0 : requirement.baseKey) || '').trim();
1620
+ if (!l2BaseKey)
1621
+ continue;
1622
+ const joinKeys = new Set([l2BaseKey]);
1623
+ for (const l1Key of l2ToLinkedL1BaseKeys.get(l2BaseKey) || []) {
1624
+ const normalized = String(l1Key || '').trim();
1625
+ if (normalized)
1626
+ joinKeys.add(normalized);
1627
+ }
1628
+ out.set(l2BaseKey, joinKeys);
1629
+ }
1630
+ return out;
1631
+ }
1466
1632
  buildRequirementFamilyMap(requirements, scopedRequirementKeys) {
1467
1633
  const familyMap = new Map();
1468
1634
  for (const requirement of requirements || []) {
@@ -1713,48 +1879,30 @@ class ResultDataProvider {
1713
1879
  }
1714
1880
  return [...out].sort((a, b) => a - b);
1715
1881
  }
1716
- extractMewpRequirementIdentifier(fields, fallbackWorkItemId) {
1882
+ extractMewpRequirementIdentifier(fields) {
1717
1883
  const entries = Object.entries(fields || {});
1718
- // First pass: only trusted identifier-like fields.
1719
- const strictHints = [
1884
+ const normalizeFieldKey = (value) => String(value || '')
1885
+ .toLowerCase()
1886
+ .replace(/[^a-z0-9]/g, '');
1887
+ // Strict MEWP mode: only explicit MEWP customer-id fields are accepted.
1888
+ // API display name: "Customer ID"
1889
+ // API reference name: "Custom.CustomerID"
1890
+ const customerIdFieldKeys = new Set([
1720
1891
  'customerid',
1721
- 'customer id',
1722
- 'customerrequirementid',
1723
- 'requirementid',
1724
- 'externalid',
1725
- 'srid',
1726
- 'sapwbsid',
1727
- ];
1892
+ 'customcustomerid',
1893
+ ]);
1728
1894
  for (const [key, value] of entries) {
1729
- const normalizedKey = String(key || '').toLowerCase();
1730
- if (!strictHints.some((hint) => normalizedKey.includes(hint)))
1895
+ const normalizedKey = normalizeFieldKey(key);
1896
+ if (!customerIdFieldKeys.has(normalizedKey))
1731
1897
  continue;
1732
1898
  const valueAsString = this.toMewpComparableText(value);
1733
1899
  if (!valueAsString)
1734
1900
  continue;
1735
- const normalized = this.normalizeMewpRequirementCodeWithSuffix(valueAsString);
1736
- if (normalized)
1737
- return normalized;
1901
+ const normalizedRequirementId = this.normalizeMewpRequirementCodeWithSuffix(valueAsString);
1902
+ if (normalizedRequirementId)
1903
+ return normalizedRequirementId;
1738
1904
  }
1739
- // Second pass: weaker hints, but still key-based only.
1740
- const looseHints = ['customer', 'requirement', 'external', 'sapwbs', 'sr'];
1741
- for (const [key, value] of entries) {
1742
- const normalizedKey = String(key || '').toLowerCase();
1743
- if (!looseHints.some((hint) => normalizedKey.includes(hint)))
1744
- continue;
1745
- const valueAsString = this.toMewpComparableText(value);
1746
- if (!valueAsString)
1747
- continue;
1748
- const normalized = this.normalizeMewpRequirementCodeWithSuffix(valueAsString);
1749
- if (normalized)
1750
- return normalized;
1751
- }
1752
- // Optional fallback from title only (avoid scanning all fields and accidental SR matches).
1753
- const title = this.toMewpComparableText(fields === null || fields === void 0 ? void 0 : fields['System.Title']);
1754
- const titleCode = this.normalizeMewpRequirementCodeWithSuffix(title);
1755
- if (titleCode)
1756
- return titleCode;
1757
- return fallbackWorkItemId ? `SR${fallbackWorkItemId}` : '';
1905
+ return '';
1758
1906
  }
1759
1907
  deriveMewpResponsibility(fields) {
1760
1908
  const explicitSapWbs = this.toMewpComparableText(fields === null || fields === void 0 ? void 0 : fields['Custom.SAPWBS']);