cpee 2.1.130 → 2.1.132

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.
Files changed (55) hide show
  1. checksums.yaml +4 -4
  2. data/FEATURES.md +9 -19
  3. data/cockpit/compliance-view.html +313 -0
  4. data/cockpit/compliance.html +34 -23
  5. data/cockpit/config.json.example +7 -6
  6. data/cockpit/css/compliance.css +70 -2
  7. data/cockpit/css/llm.css +9 -1
  8. data/cockpit/css/llmmodel.css +3 -0
  9. data/cockpit/css/ui.css +9 -30
  10. data/cockpit/edit.html +1 -1
  11. data/cockpit/graph.html +1 -1
  12. data/cockpit/index.html +6 -1
  13. data/cockpit/js/compliance.js +925 -10
  14. data/cockpit/js/instance.js +19 -9
  15. data/cockpit/js/parameters.js +37 -5
  16. data/cockpit/js/ui.js +10 -40
  17. data/cockpit/js/wfadaptor.js +4 -5
  18. data/cockpit/llm.html +24 -26
  19. data/cockpit/llmmodel.html +27 -25
  20. data/cockpit/model.html +1 -1
  21. data/cockpit/only_llm.html +5 -5
  22. data/cockpit/rngs/attributes.rng +2 -2
  23. data/cockpit/rngs/dataelements.rng +2 -2
  24. data/cockpit/rngs/documents.rng +8 -0
  25. data/cockpit/rngs/endpoints.rng +2 -2
  26. data/cockpit/rngs/requirements.rng +2 -2
  27. data/cockpit/themes/THEMES.MD +419 -0
  28. data/cockpit/themes/preset_model/symbols/critical.svg +13 -3
  29. data/cockpit/track.html +1 -1
  30. data/cpee.gemspec +1 -1
  31. data/lib/cpee/implementation.rb +3 -1
  32. data/lib/cpee/implementation_properties.rb +6 -5
  33. data/lib/cpee/persistence.rb +6 -7
  34. data/lib/properties/documents.rng +10 -0
  35. data/lib/properties/properties.rng +2 -0
  36. data/lib/properties/set-properties.rng +2 -0
  37. data/lib/properties/set-some-properties.rng +4 -0
  38. data/lib/properties/set-testset.rng +3 -0
  39. data/lib/properties/t_documents.rng +7 -0
  40. data/lib/properties.xml +22 -0
  41. data/server/executionhandlers/eval/connection.rb +1 -0
  42. data/server/executionhandlers/eval/execution.rb +2 -0
  43. data/server/executionhandlers/ruby/connection.rb +1 -0
  44. data/server/executionhandlers/ruby/controller.rb +4 -8
  45. data/server/executionhandlers/ruby/execution.rb +2 -0
  46. data/server/resources/properties.empty +1 -0
  47. data/server/resources/properties.init +1 -0
  48. data/server/routing/end.pid +1 -1
  49. data/server/routing/forward-events-00.pid +1 -1
  50. data/server/routing/forward-votes.pid +1 -1
  51. data/server/routing/persist.pid +1 -1
  52. data/server/routing/persist.rb +2 -1
  53. data/tools/cpee +1 -1
  54. metadata +7 -2
  55. data/lib/cpee/attributes_helper.rb +0 -41
@@ -1,3 +1,5 @@
1
+ var latestComplianceResult = null;
2
+
1
3
  $(document).ready(function() { //{{{
2
4
  $.ajax({
3
5
  type: "GET",
@@ -5,21 +7,330 @@ $(document).ready(function() { //{{{
5
7
  dataType: "xml",
6
8
  success: function(rng){
7
9
  save['requirements'] = new RelaxNGui(rng,$('#dat_requirements'));
10
+ initialize_nl_requirements_from_saved_requirements();
8
11
  }
9
12
  });
10
13
 
14
+ initialize_nl_requirements_tab();
15
+
16
+ function loadComplianceLog(uuid, options) {
17
+ var renderOptions = options || {};
18
+ var baseUrl = 'https://cpee.org/comp-log/';
19
+ var currentUrl = baseUrl + uuid + '.xes.yaml';
20
+ var allUrl = baseUrl + uuid + '.all.xes.yaml';
21
+
22
+ $('#comp-verify-all-log').attr('href', allUrl);
23
+ $('#areacomp .yourlog').show();
24
+ $('#areacomp .yourlog [style]').show();
25
+
26
+ function tryLoad(url, fallback) {
27
+ $.ajax({
28
+ type: 'GET',
29
+ url: url,
30
+ dataType: 'text',
31
+ success: function(yaml) {
32
+ $('#comp-verify-current-log').attr('href', url).text(url.split('/').pop()).show();
33
+ displayComplianceMessages(yaml, renderOptions);
34
+ if (typeof renderOptions.onLoaded === 'function') {
35
+ renderOptions.onLoaded({ yaml: yaml, url: url });
36
+ }
37
+ },
38
+ error: function() {
39
+ if (fallback) {
40
+ tryLoad(fallback, null);
41
+ } else {
42
+ $('#comp_log').html('Could not load compliance log.');
43
+ if (typeof renderOptions.onError === 'function') {
44
+ renderOptions.onError('Could not load compliance log.');
45
+ }
46
+ }
47
+ }
48
+ });
49
+ }
50
+
51
+ tryLoad(currentUrl, baseUrl + uuid + '.current.xes.yaml');
52
+ }
53
+
54
+ function extractLatestLogTimestamp(yamlText) {
55
+ if (typeof yamlText !== 'string' || !yamlText.trim()) {
56
+ return null;
57
+ }
58
+
59
+ // Match ISO timestamps such as 2026-07-28T10:11:12Z or with timezone offsets.
60
+ var matches = yamlText.match(/\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})/g);
61
+ if (!matches || matches.length === 0) {
62
+ return null;
63
+ }
64
+
65
+ var latest = null;
66
+ for (var i = 0; i < matches.length; i++) {
67
+ var ts = Date.parse(matches[i]);
68
+ if (!isNaN(ts) && (latest == null || ts > latest)) {
69
+ latest = ts;
70
+ }
71
+ }
72
+
73
+ return latest;
74
+ }
75
+
76
+ function isLogOlderThanSeconds(yamlText, seconds) {
77
+ var latestTimestamp = extractLatestLogTimestamp(yamlText);
78
+ if (latestTimestamp == null) {
79
+ return false;
80
+ }
81
+
82
+ var ageMs = Date.now() - latestTimestamp;
83
+ return ageMs > (seconds * 1000);
84
+ }
85
+
86
+ function triggerComplianceSubscriber(uuid, onSuccess, onError) {
87
+ getCurrentTestsetXml(
88
+ function(testsetXml) {
89
+ var notification;
90
+ try {
91
+ notification = buildSubscriptionLikeNotification(testsetXml, uuid);
92
+ } catch (error) {
93
+ onError(error.message || 'Failed to build compliance notification payload.');
94
+ return;
95
+ }
96
+
97
+ var formData = new FormData();
98
+ formData.append('notification', JSON.stringify(notification));
99
+ formData.append('type', 'event');
100
+ formData.append('topic', 'description');
101
+ formData.append('event', 'change');
102
+
103
+ $.ajax({
104
+ method: 'POST',
105
+ type: 'POST',
106
+ url: 'https://power.bpm.cit.tum.de/compliance/Subscriber',
107
+ data: formData,
108
+ processData: false,
109
+ contentType: false,
110
+ dataType: 'text',
111
+ success: function(_response, _textStatus, xhr) {
112
+ if (xhr && xhr.status === 200) {
113
+ onSuccess();
114
+ return;
115
+ }
116
+
117
+ var body = xhr && xhr.responseText ? xhr.responseText : 'Compliance verify returned an unexpected response.';
118
+ onError(body);
119
+ },
120
+ error: function(xhr) {
121
+ var body = xhr && xhr.responseText ? xhr.responseText : 'Compliance verify failed.';
122
+ onError(body);
123
+ }
124
+ });
125
+ },
126
+ function(message) {
127
+ onError(message);
128
+ }
129
+ );
130
+ }
131
+
132
+ function getCurrentTestsetXml(onSuccess, onError) {
133
+ if (typeof get_testset !== 'function') {
134
+ onError('Could not collect the current testset XML.');
135
+ return;
136
+ }
137
+
138
+ var deferred = new $.Deferred();
139
+
140
+ deferred.done(function(_name, testset) {
141
+ if (!testset || typeof testset.serializePrettyXML !== 'function') {
142
+ onError('Could not serialize testset XML.');
143
+ return;
144
+ }
145
+
146
+ onSuccess(testset.serializePrettyXML());
147
+ });
148
+
149
+ deferred.fail(function() {
150
+ onError('Could not load the current testset.');
151
+ });
152
+
153
+ get_testset(deferred);
154
+ }
155
+
156
+ function extractDescriptionXmlFromTestset(testsetXml) {
157
+ var xmlDoc = $.parseXML(testsetXml);
158
+ var descriptionContainer = xmlDoc.getElementsByTagName('description')[0];
159
+
160
+ if (!descriptionContainer) {
161
+ return null;
162
+ }
163
+
164
+ var descriptionNode = null;
165
+ for (var i = 0; i < descriptionContainer.childNodes.length; i++) {
166
+ var child = descriptionContainer.childNodes[i];
167
+ if (child.nodeType === 1) {
168
+ descriptionNode = child;
169
+ break;
170
+ }
171
+ }
172
+
173
+ if (!descriptionNode) {
174
+ return null;
175
+ }
176
+
177
+ return new XMLSerializer().serializeToString(descriptionNode);
178
+ }
179
+
180
+ function extractAttributesFromTestset(testsetXml) {
181
+ var attributes = {};
182
+ var xmlDoc = $.parseXML(testsetXml);
183
+ var attributesContainer = xmlDoc.getElementsByTagName('attributes')[0];
184
+
185
+ if (!attributesContainer) {
186
+ return attributes;
187
+ }
188
+
189
+ for (var i = 0; i < attributesContainer.childNodes.length; i++) {
190
+ var child = attributesContainer.childNodes[i];
191
+ if (child.nodeType !== 1) {
192
+ continue;
193
+ }
194
+ attributes[child.localName || child.nodeName] = (child.textContent || '').trim();
195
+ }
196
+
197
+ return attributes;
198
+ }
199
+
200
+ function extractInstanceId() {
201
+ var currentInstanceUrl = $('body').attr('current-instance') || '';
202
+ var match = currentInstanceUrl.match(/\/(\d+)\/?$/);
203
+ return match ? parseInt(match[1], 10) : null;
204
+ }
205
+
206
+ function buildSubscriptionLikeNotification(testsetXml, uuid) {
207
+ var attributes = extractAttributesFromTestset(testsetXml);
208
+ var descriptionXml = extractDescriptionXmlFromTestset(testsetXml);
209
+
210
+ if (!descriptionXml) {
211
+ throw new Error('Could not extract description XML from testset.');
212
+ }
213
+
214
+ var instanceId = extractInstanceId();
215
+
216
+ return {
217
+ cpee: $('body').attr('current-base') || '',
218
+ 'instance-url': $('body').attr('current-instance') || '',
219
+ instance: instanceId,
220
+ topic: 'description',
221
+ type: 'event',
222
+ name: 'change',
223
+ timestamp: new Date().toISOString(),
224
+ content: {
225
+ attributes: attributes,
226
+ description: descriptionXml,
227
+ testset: testsetXml,
228
+ },
229
+ 'instance-uuid': uuid,
230
+ 'instance-name': (attributes.info || 'semantic-verify'),
231
+ };
232
+ }
233
+
11
234
  $("#verify").click(function(){
12
235
  var uuid = save.attributes_raw && save.attributes_raw.uuid;
13
236
  if (!uuid) {
14
- $('#comp_log').html('<p>No instance loaded.</p>');
237
+ $('#comp_log').html('No instance loaded.');
238
+ return;
239
+ }
240
+
241
+ $('#comp_log').html('Loading compliance log...');
242
+
243
+ loadComplianceLog(uuid, {
244
+ onLoaded: function(result) {
245
+ if (!result || !result.yaml) {
246
+ return;
247
+ }
248
+
249
+ if (!isLogOlderThanSeconds(result.yaml, 60)) {
250
+ return;
251
+ }
252
+
253
+ $('#comp_log').html('Last log is older than 60 seconds. Requesting fresh verification...');
254
+ triggerComplianceSubscriber(
255
+ uuid,
256
+ function() {
257
+ loadComplianceLog(uuid);
258
+ },
259
+ function(message) {
260
+ $('#comp_log').html('<pre>' + escapeHtml(message || 'Failed to request fresh verification.') + '</pre>');
261
+ }
262
+ );
263
+ }
264
+ });
265
+ });
266
+
267
+ $("#semantic_verify").click(function(){
268
+ var uuid = save.attributes_raw && save.attributes_raw.uuid;
269
+ if (!uuid) {
270
+ $('#comp_log').html('No instance loaded.');
15
271
  return;
16
272
  }
17
273
 
274
+ $('#comp_log').html('Preparing semantic verification payload...');
275
+
276
+ getCurrentTestsetXml(
277
+ function(testsetXml) {
278
+ $('#comp_log').html('Running semantic verification...');
279
+
280
+ var notification;
281
+ try {
282
+ notification = buildSubscriptionLikeNotification(testsetXml, uuid);
283
+ } catch (error) {
284
+ $('#comp_log').html(escapeHtml(error.message || 'Failed to build semantic verification payload.'));
285
+ return;
286
+ }
287
+
288
+ var formData = new FormData();
289
+ formData.append('notification', JSON.stringify(notification));
290
+ formData.append('type', 'event');
291
+ formData.append('topic', 'description');
292
+ formData.append('event', 'change');
293
+
294
+ $.ajax({
295
+ method: 'POST',
296
+ type: 'POST',
297
+ url: 'https://power.bpm.cit.tum.de/compliance/SubscriberSemantic',
298
+ data: formData,
299
+ processData: false,
300
+ contentType: false,
301
+ dataType: 'text',
302
+ success: function(_response, _textStatus, xhr) {
303
+ if (xhr && xhr.status === 200) {
304
+ loadComplianceLog(uuid, { semantic: true });
305
+ return;
306
+ }
307
+
308
+ var body = xhr && xhr.responseText ? xhr.responseText : 'Semantic verification returned an unexpected response.';
309
+ $('#comp_log').html('<pre>' + escapeHtml(body) + '</pre>');
310
+ },
311
+ error: function(xhr) {
312
+ var body = xhr && xhr.responseText ? xhr.responseText : 'Semantic verification failed.';
313
+ $('#comp_log').html('<pre>' + escapeHtml(body) + '</pre>');
314
+ }
315
+ });
316
+ },
317
+ function(message) {
318
+ $('#comp_log').html(escapeHtml(message));
319
+ }
320
+ );
321
+ });
322
+
323
+ $("#identify-violations").click(function(){
324
+ var uuid = save.attributes_raw && save.attributes_raw.uuid;
325
+ if (!uuid) {
326
+ $('#comp_log').html('No instance loaded.');
327
+ return;
328
+ }
329
+
330
+ $('#comp_log').html('Loading compliance log...');
331
+
18
332
  var baseUrl = 'https://cpee.org/comp-log/';
19
333
  var currentUrl = baseUrl + uuid + '.xes.yaml';
20
- var allUrl = baseUrl + uuid + '.all.xes.yaml';
21
-
22
- $('#comp-all-log').attr('href', allUrl).show();
23
334
 
24
335
  function tryLoad(url, fallback) {
25
336
  $.ajax({
@@ -27,14 +338,38 @@ $(document).ready(function() { //{{{
27
338
  url: url,
28
339
  dataType: 'text',
29
340
  success: function(yaml) {
30
- $('#comp-current-log').attr('href', url).text(url.split('/').pop()).show();
31
- displayComplianceMessages(yaml);
341
+ var formData = new FormData();
342
+ var fileName = uuid + '.xes.yaml';
343
+ formData.append(
344
+ 'file',
345
+ new Blob([yaml], { type: 'text/yaml' }),
346
+ fileName
347
+ );
348
+
349
+ $('#comp_log').html('Identifying violations...');
350
+
351
+ $.ajax({
352
+ type: 'POST',
353
+ url: 'https://power.bpm.cit.tum.de/comprepair/violations',
354
+ data: formData,
355
+ processData: false,
356
+ contentType: false,
357
+ dataType: 'json',
358
+ success: function(result) {
359
+ latestComplianceResult = result;
360
+ renderViolationMessages(result);
361
+ },
362
+ error: function(xhr) {
363
+ var detail = xhr && xhr.responseText ? xhr.responseText : 'Unknown error';
364
+ $('#comp_log').html('Violation identification failed: ' + detail);
365
+ }
366
+ });
32
367
  },
33
368
  error: function() {
34
369
  if (fallback) {
35
370
  tryLoad(fallback, null);
36
371
  } else {
37
- $('#comp_log').html('<p>Could not load compliance log.</p>');
372
+ $('#comp_log').html('Could not load compliance log.');
38
373
  }
39
374
  }
40
375
  });
@@ -43,6 +378,61 @@ $(document).ready(function() { //{{{
43
378
  tryLoad(currentUrl, baseUrl + uuid + '.current.xes.yaml');
44
379
  });
45
380
 
381
+ $("#repair-violations").click(function(){
382
+ var uuid = save.attributes_raw && save.attributes_raw.uuid;
383
+ if (!uuid) {
384
+ $('#comp_log').html('No instance loaded.');
385
+ return;
386
+ }
387
+
388
+ if (!latestComplianceResult) {
389
+ $('#comp_log').html('No compliance result available. Run Identify Violations first.');
390
+ return;
391
+ }
392
+
393
+ var originalPstXml = getCurrentProcessXml();
394
+ if (!originalPstXml) {
395
+ $('#comp_log').html('Could not extract the current process XML.');
396
+ return;
397
+ }
398
+
399
+ var formData = new FormData();
400
+ formData.append(
401
+ 'original_pst',
402
+ new Blob([originalPstXml], { type: 'application/xml' }),
403
+ 'original_pst.xml'
404
+ );
405
+ formData.append(
406
+ 'compliance_result',
407
+ new Blob([JSON.stringify(latestComplianceResult)], { type: 'application/json' }),
408
+ 'compliance_result.json'
409
+ );
410
+
411
+ $('#comp_log').html('Repairing violations...');
412
+
413
+ $.ajax({
414
+ type: 'POST',
415
+ url: 'https://power.bpm.cit.tum.de/comprepair/repair_full',
416
+ data: formData,
417
+ processData: false,
418
+ contentType: false,
419
+ dataType: 'json',
420
+ success: function(result) {
421
+ if (hasResolutionStrategies(result)) {
422
+ renderResolutionStrategies(result);
423
+ return;
424
+ }
425
+
426
+ var pretty = JSON.stringify(result, null, 2);
427
+ $('#comp_log').html('<pre>' + escapeHtml(pretty) + '</pre>');
428
+ },
429
+ error: function(xhr) {
430
+ var detail = xhr && xhr.responseText ? xhr.responseText : 'Unknown error';
431
+ $('#comp_log').html('Repair failed: ' + escapeHtml(detail));
432
+ }
433
+ });
434
+ });
435
+
46
436
  document.addEventListener('attributes:changed', function (e) {
47
437
  let req = $X("<requirements xmlns='http://cpee.org/ns/properties/2.0'/>");
48
438
  let reqs;
@@ -59,6 +449,11 @@ $(document).ready(function() { //{{{
59
449
  req.append(ele);
60
450
  }
61
451
  save['requirements'].content(req);
452
+ sync_nl_rows_to_requirement_keys(reqj);
453
+
454
+ let nlReqj = parse_saved_object(save.attributes_raw['NL-requirements']);
455
+ sync_nl_rows_to_requirement_keys(nlReqj);
456
+ apply_nl_requirements_values(nlReqj);
62
457
  });
63
458
 
64
459
  var timer;
@@ -69,6 +464,8 @@ $(document).ready(function() { //{{{
69
464
  });
70
465
  $(document).on('relaxngui_remove', '#dat_requirements', function(event){
71
466
  clearTimeout(timer);
467
+ sync_nl_rows_to_requirement_keys(read_requirements_object(), true);
468
+ do_nl_requirements_save(event);
72
469
  do_requirements_save(event);
73
470
  });
74
471
  $(document).on('relaxngui_move', '#dat_requirements', function(event){
@@ -79,9 +476,344 @@ $(document).ready(function() { //{{{
79
476
  clearTimeout(timer);
80
477
  do_requirements_save(event);
81
478
  });
479
+
480
+ var nlTimer;
481
+ $(document).on('input', '#dat_nlrequirements .nlreq-text', function(event){
482
+ clearTimeout(nlTimer);
483
+ nlTimer = setTimeout(function(){ do_nl_requirements_save(event); }, 2000);
484
+ });
82
485
  }); //}}}
83
486
 
84
- function displayComplianceMessages(yaml) { //{{{
487
+ function initialize_nl_requirements_tab() { //{{{
488
+ $('#nlreq_add').on('click', function() {
489
+ add_nl_requirement_row();
490
+ do_nl_requirements_save();
491
+ });
492
+
493
+ if ($('#dat_nlrequirements .nlreq-row').length === 0) {
494
+ add_nl_requirement_row();
495
+ }
496
+ } //}}}
497
+
498
+ function initialize_nl_requirements_from_saved_requirements() { //{{{
499
+ if (!save['requirements']) {
500
+ return;
501
+ }
502
+ var reqObject = read_requirements_object();
503
+ sync_nl_rows_to_requirement_keys(reqObject);
504
+ } //}}}
505
+
506
+ function add_nl_requirement_row(requirementId, textValue) { //{{{
507
+ var rid = requirementId || next_requirement_id();
508
+ if ($('#dat_nlrequirements .nlreq-row[data-requirement-id="' + rid + '"]').length > 0) {
509
+ return;
510
+ }
511
+
512
+ var row = $('<div class="nlreq-row"></div>').attr('data-requirement-id', rid);
513
+ row.append('<button type="button" class="nlreq-extract">Extract</button>');
514
+ row.append($('<span class="nlreq-id"></span>').text(rid));
515
+ row.append(
516
+ $('<input type="text" class="nlreq-text" placeholder="Write natural-language requirement"/>')
517
+ .val(textValue || '')
518
+ );
519
+ row.append('<span class="nlreq-status"></span>');
520
+ row.find('.nlreq-extract').on('click', function() {
521
+ extract_single_nl_requirement(row);
522
+ });
523
+ $('#dat_nlrequirements').append(row);
524
+ } //}}}
525
+
526
+ function extract_single_nl_requirement(row) { //{{{
527
+ var requirementId = row.attr('data-requirement-id');
528
+ var naturalText = row.find('.nlreq-text').val().trim();
529
+ var status = row.find('.nlreq-status');
530
+ var button = row.find('.nlreq-extract');
531
+
532
+ if (!naturalText) {
533
+ status.text('Enter text').removeClass('ok').addClass('error');
534
+ return;
535
+ }
536
+ if (!save['requirements']) {
537
+ status.text('Not ready').removeClass('ok').addClass('error');
538
+ return;
539
+ }
540
+
541
+ status.text('Extracting...').removeClass('ok error');
542
+ button.prop('disabled', true);
543
+
544
+ extract_ast_from_natural_language(
545
+ naturalText,
546
+ function(ast) {
547
+ upsert_requirement_ast(requirementId, ast);
548
+ do_nl_requirements_save();
549
+ status.text('Added').removeClass('error').addClass('ok');
550
+ button.prop('disabled', false);
551
+ },
552
+ function(message) {
553
+ status.text(message || 'Failed').removeClass('ok').addClass('error');
554
+ button.prop('disabled', false);
555
+ }
556
+ );
557
+ } //}}}
558
+
559
+ function sync_nl_rows_to_requirement_keys(requirementsObject, pruneMissing) { //{{{
560
+ if (!requirementsObject || typeof requirementsObject !== 'object') {
561
+ return;
562
+ }
563
+
564
+ var keys = Object.keys(requirementsObject);
565
+
566
+ if (pruneMissing) {
567
+ $('#dat_nlrequirements .nlreq-row').each(function() {
568
+ var rid = $(this).attr('data-requirement-id');
569
+ if (rid && keys.indexOf(rid) === -1) {
570
+ $(this).remove();
571
+ }
572
+ });
573
+ }
574
+
575
+ keys.forEach(function(key) {
576
+ if ($('#dat_nlrequirements .nlreq-row[data-requirement-id="' + key + '"]').length === 0) {
577
+ add_nl_requirement_row(key, '');
578
+ }
579
+ });
580
+ } //}}}
581
+
582
+ function next_requirement_id() { //{{{
583
+ var maxId = 0;
584
+ $('#dat_nlrequirements .nlreq-row').each(function() {
585
+ var rid = $(this).attr('data-requirement-id') || '';
586
+ var m = rid.match(/^R(\d+)$/);
587
+ if (m) {
588
+ maxId = Math.max(maxId, parseInt(m[1], 10));
589
+ }
590
+ });
591
+
592
+ var currentReqs = read_requirements_object();
593
+ Object.keys(currentReqs).forEach(function(key) {
594
+ var m = key.match(/^R(\d+)$/);
595
+ if (m) {
596
+ maxId = Math.max(maxId, parseInt(m[1], 10));
597
+ }
598
+ });
599
+
600
+ return 'R' + (maxId + 1);
601
+ } //}}}
602
+
603
+ function upsert_requirement_ast(requirementId, astText) { //{{{
604
+ var reqObject = read_requirements_object();
605
+ reqObject[requirementId] = astText;
606
+ write_requirements_object(reqObject);
607
+ } //}}}
608
+
609
+ function read_requirements_object() { //{{{
610
+ if (!save['requirements']) {
611
+ return {};
612
+ }
613
+
614
+ var raw = save['requirements'].save_json();
615
+ if (raw == null || raw === '') {
616
+ return {};
617
+ }
618
+ if (typeof raw === 'string') {
619
+ try {
620
+ return JSON.parse(raw);
621
+ } catch (e) {
622
+ return {};
623
+ }
624
+ }
625
+ if (typeof raw === 'object') {
626
+ return raw;
627
+ }
628
+ return {};
629
+ } //}}}
630
+
631
+ function parse_saved_object(raw) { //{{{
632
+ if (raw == null || raw === '') {
633
+ return {};
634
+ }
635
+ if (typeof raw === 'object') {
636
+ return raw;
637
+ }
638
+ if (typeof raw === 'string') {
639
+ var normalized = raw;
640
+ if (/\"\s*=>\s*"/g.test(normalized)) {
641
+ normalized = normalized.replace(/\"\s*=>\s*"/g, '\":\"');
642
+ }
643
+ try {
644
+ return JSON.parse(normalized);
645
+ } catch (e) {
646
+ return {};
647
+ }
648
+ }
649
+ return {};
650
+ } //}}}
651
+
652
+ function apply_nl_requirements_values(nlReqObject) { //{{{
653
+ if (!nlReqObject || typeof nlReqObject !== 'object') {
654
+ return;
655
+ }
656
+
657
+ Object.keys(nlReqObject).forEach(function(key) {
658
+ var row = $('#dat_nlrequirements .nlreq-row[data-requirement-id="' + key + '"]');
659
+ if (row.length > 0) {
660
+ row.find('.nlreq-text').val(nlReqObject[key] || '');
661
+ }
662
+ });
663
+ } //}}}
664
+
665
+ function read_nl_requirements_object_from_ui() { //{{{
666
+ var data = {};
667
+ $('#dat_nlrequirements .nlreq-row').each(function() {
668
+ var requirementId = $(this).attr('data-requirement-id');
669
+ if (!requirementId) {
670
+ return;
671
+ }
672
+ var text = $(this).find('.nlreq-text').val();
673
+ data[requirementId] = text == null ? '' : String(text);
674
+ });
675
+ return data;
676
+ } //}}}
677
+
678
+ function write_requirements_object(reqObject) { //{{{
679
+ let req = $X("<requirements xmlns='http://cpee.org/ns/properties/2.0'/>");
680
+ Object.keys(reqObject)
681
+ .sort(sort_requirement_ids)
682
+ .forEach(function(key) {
683
+ let ele = $X("<" + key + " xmlns='http://cpee.org/ns/properties/2.0'/>");
684
+ ele.text(reqObject[key]);
685
+ req.append(ele);
686
+ });
687
+ save['requirements'].content(req);
688
+ do_requirements_save({ target: $('#dat_requirements')[0] });
689
+ } //}}}
690
+
691
+ function sort_requirement_ids(a, b) { //{{{
692
+ var am = String(a).match(/^R(\d+)$/);
693
+ var bm = String(b).match(/^R(\d+)$/);
694
+ if (am && bm) {
695
+ return parseInt(am[1], 10) - parseInt(bm[1], 10);
696
+ }
697
+ return String(a).localeCompare(String(b));
698
+ } //}}}
699
+
700
+ function extract_ast_from_natural_language(naturalText, onSuccess, onError) { //{{{
701
+ var configuredEndpoint = $('body').attr('data-singlerule-endpoint') || '/compextract/singlerule';
702
+ var url = absolute_extract_url(configuredEndpoint);
703
+ var fallbackUrl = absolute_extract_url(configuredEndpoint.replace(/\/?singlerule\/?$/, '/text'));
704
+
705
+ var attempts = [
706
+ { url: url, payload: { rule: naturalText } },
707
+ { url: url, payload: { text: naturalText } }
708
+ ];
709
+ if (fallbackUrl !== url) {
710
+ attempts.push({ url: fallbackUrl, payload: { text: naturalText } });
711
+ }
712
+
713
+ run_extract_attempt(attempts, 0, onSuccess, onError);
714
+ } //}}}
715
+
716
+ function run_extract_attempt(attempts, index, onSuccess, onError) { //{{{
717
+ if (index >= attempts.length) {
718
+ onError('Extract failed');
719
+ return;
720
+ }
721
+
722
+ var attempt = attempts[index];
723
+ $.ajax({
724
+ type: 'POST',
725
+ url: attempt.url,
726
+ contentType: 'application/json',
727
+ dataType: 'json',
728
+ data: JSON.stringify(attempt.payload),
729
+ success: function(response) {
730
+ var ast = parse_extracted_ast(response);
731
+ if (ast) {
732
+ onSuccess(ast);
733
+ } else {
734
+ run_extract_attempt(attempts, index + 1, onSuccess, onError);
735
+ }
736
+ },
737
+ error: function() {
738
+ run_extract_attempt(attempts, index + 1, onSuccess, onError);
739
+ }
740
+ });
741
+ } //}}}
742
+
743
+ function parse_extracted_ast(response) { //{{{
744
+ if (!response) {
745
+ return null;
746
+ }
747
+
748
+ if (typeof response.rule === 'string' && response.rule.trim()) {
749
+ return response.rule.trim();
750
+ }
751
+
752
+ var payload = response.asts != null ? response.asts : response.ast;
753
+ if (payload == null) {
754
+ return null;
755
+ }
756
+
757
+ if (typeof payload === 'string') {
758
+ var trimmed = payload.trim();
759
+ if (!trimmed) {
760
+ return null;
761
+ }
762
+ try {
763
+ var parsed = JSON.parse(trimmed);
764
+ return first_rule_from_payload(parsed) || trimmed;
765
+ } catch (e) {
766
+ return trimmed;
767
+ }
768
+ }
769
+
770
+ return first_rule_from_payload(payload);
771
+ } //}}}
772
+
773
+ function first_rule_from_payload(payload) { //{{{
774
+ if (!payload) {
775
+ return null;
776
+ }
777
+ if (typeof payload.rule === 'string' && payload.rule.trim()) {
778
+ return payload.rule.trim();
779
+ }
780
+ if (payload.ast && typeof payload.ast === 'object') {
781
+ var nested = first_rule_from_payload(payload.ast);
782
+ if (nested) {
783
+ return nested;
784
+ }
785
+ }
786
+ if (Array.isArray(payload)) {
787
+ if (payload.length === 0) {
788
+ return null;
789
+ }
790
+ return typeof payload[0] === 'string' ? payload[0] : JSON.stringify(payload[0]);
791
+ }
792
+ if (payload.rules && Array.isArray(payload.rules) && payload.rules.length > 0) {
793
+ return typeof payload.rules[0] === 'string' ? payload.rules[0] : JSON.stringify(payload.rules[0]);
794
+ }
795
+ if (payload.rules && typeof payload.rules === 'string') {
796
+ return payload.rules;
797
+ }
798
+ if (typeof payload === 'object') {
799
+ return JSON.stringify(payload);
800
+ }
801
+ return null;
802
+ } //}}}
803
+
804
+ function absolute_extract_url(endpoint) { //{{{
805
+ if (/^https?:\/\//.test(endpoint)) {
806
+ return endpoint;
807
+ }
808
+ if (endpoint.charAt(0) !== '/') {
809
+ endpoint = '/' + endpoint;
810
+ }
811
+ return window.location.origin + endpoint;
812
+ } //}}}
813
+
814
+ function displayComplianceMessages(yaml, options) { //{{{
815
+ var renderOptions = options || {};
816
+ var semanticMode = !!renderOptions.semantic;
85
817
  var messages = [];
86
818
  var parts = yaml.split(/\n---\s*\n/);
87
819
 
@@ -107,12 +839,26 @@ function displayComplianceMessages(yaml) { //{{{
107
839
  messages.push(val);
108
840
  }
109
841
 
842
+ if (semanticMode) {
843
+ var firstRequirementIndex = -1;
844
+ for (var k = 0; k < messages.length; k++) {
845
+ if (/^Verifying Requirement R/.test(messages[k])) {
846
+ firstRequirementIndex = k;
847
+ break;
848
+ }
849
+ }
850
+
851
+ if (firstRequirementIndex >= 0) {
852
+ messages = messages.slice(firstRequirementIndex);
853
+ }
854
+ }
855
+
110
856
  if (messages.length === 0) {
111
857
  $('#comp_log').html('<div>No messages found.</div>');
112
858
  return;
113
859
  }
114
860
 
115
- var html = '<div>Pre-Tests</div>';
861
+ var html = semanticMode ? '' : '<div>Pre-Tests</div>';
116
862
  for (var j = 0; j < messages.length; j++) {
117
863
  var msg = messages[j];
118
864
  var isHeader = /^Verifying Requirement R/.test(msg) || /^Requirement R/.test(msg);
@@ -122,13 +868,182 @@ function displayComplianceMessages(yaml) { //{{{
122
868
  $('#comp_log').html(html);
123
869
  } //}}}
124
870
 
871
+ function renderViolationMessages(result) { //{{{
872
+ var violations = Array.isArray(result && result.violations)
873
+ ? result.violations
874
+ : [];
875
+
876
+ var html = '<div>Violations:</div>';
877
+
878
+ if (violations.length === 0) {
879
+ html += '<div class="indent">None</div>';
880
+ $('#comp_log').html(html);
881
+ return;
882
+ }
883
+
884
+ for (var i = 0; i < violations.length; i++) {
885
+ var violation = violations[i] || {};
886
+ var requirementId = violation.requirement_id || 'unknown';
887
+
888
+ html += '<div class="indent">' + escapeHtml(requirementId) + '</div>';
889
+
890
+ var evidenceList = Array.isArray(violation.evidence)
891
+ ? violation.evidence
892
+ : [];
893
+
894
+ for (var j = 0; j < evidenceList.length; j++) {
895
+ html += '<div class="indent indent-level-2">'
896
+ + escapeHtml(String(evidenceList[j]))
897
+ + '</div>';
898
+ }
899
+ }
900
+
901
+ $('#comp_log').html(html);
902
+ } //}}}
903
+
904
+ function hasResolutionStrategies(result) { //{{{
905
+ return !!(
906
+ result
907
+ && Array.isArray(result.resolution_strategies)
908
+ );
909
+ } //}}}
910
+
911
+ function renderResolutionStrategies(result) { //{{{
912
+ var strategies = result.resolution_strategies;
913
+
914
+ if (!Array.isArray(strategies)) {
915
+ var pretty = JSON.stringify(result, null, 2);
916
+ $('#comp_log').html('<pre>' + escapeHtml(pretty) + '</pre>');
917
+ return;
918
+ }
919
+
920
+ var html = '';
921
+
922
+ for (var i = 0; i < strategies.length; i++) {
923
+ var strategy = strategies[i] || {};
924
+ var requirementId = strategy.requirement_id || 'unknown';
925
+ var changeDescription = strategy.change_description || strategy.resolution_strategy || 'N/A';
926
+ var riskValue = extractRiskValue(strategy.change_risk);
927
+ var status = typeof strategy.status === 'string' ? strategy.status.toLowerCase() : '';
928
+ var pstXml = typeof strategy.pst_xml === 'string' ? strategy.pst_xml : '';
929
+
930
+ html += '<div>Resolution ' + escapeHtml(requirementId) + ':</div>';
931
+ html += '<div class="indent">Repair Action: ' + escapeHtml(changeDescription) + '</div>';
932
+ html += '<div class="indent">Risk: ' + escapeHtml(riskValue) + '</div>';
933
+
934
+ if (
935
+ (status === 'success' || status === 'warning') &&
936
+ pstXml
937
+ ) {
938
+ html += '<div class="indent">PST: ' + buildPstDownloadLink(pstXml, requirementId, i) + '</div>';
939
+ } else {
940
+ html += '<div class="indent error">Error</div>';
941
+ }
942
+ }
943
+
944
+ if (html === '') {
945
+ html = '<pre>' + escapeHtml(JSON.stringify(result, null, 2)) + '</pre>';
946
+ }
947
+
948
+ $('#comp_log').html(html);
949
+ } //}}}
950
+
951
+ function extractRiskValue(changeRisk) { //{{{
952
+ if (changeRisk && typeof changeRisk === 'object') {
953
+ if (typeof changeRisk.value === 'string' && changeRisk.value.trim()) {
954
+ return changeRisk.value.trim();
955
+ }
956
+ }
957
+
958
+ if (typeof changeRisk === 'string' && changeRisk.trim()) {
959
+ return changeRisk.trim();
960
+ }
961
+
962
+ return 'N/A';
963
+ } //}}}
964
+
965
+ function buildPstDownloadLink(pstXml, requirementId, index) { //{{{
966
+ var blob = new Blob([pstXml], { type: 'application/xml' });
967
+ var url = URL.createObjectURL(blob);
968
+ var fileName = 'resolution_' + requirementId + '_' + index + '.xml';
969
+ return '<a href="' + escapeHtml(url) + '" download="' + escapeHtml(fileName) + '">Download the PST</a>';
970
+ } //}}}
971
+
972
+ function escapeHtml(value) { //{{{
973
+ return $('<span>').text(value == null ? '' : String(value)).html();
974
+ } //}}}
975
+
976
+ function getCurrentProcessXml() { //{{{
977
+ if (
978
+ save['graph_adaptor']
979
+ && typeof save['graph_adaptor'].get_description === 'function'
980
+ ) {
981
+ return save['graph_adaptor'].get_description();
982
+ }
983
+
984
+ if (
985
+ save['graph']
986
+ && typeof save['graph'].serializePrettyXML === 'function'
987
+ ) {
988
+ return save['graph'].serializePrettyXML();
989
+ }
990
+
991
+ return null;
992
+ } //}}}
993
+
125
994
  function do_requirements_save(event) { //{{{
126
995
  if (save['requirements'].has_changed()) {
127
996
  save['requirements'].set_checkpoint();
128
997
  var url = $('body').attr('current-instance');
129
998
  let reqj = save['requirements'].save_json();
130
999
  let att = save['attributes'].save();
131
- $(' > attributes > requirements', att).text(reqj);
1000
+ let attributesNode = $(' > attributes', att);
1001
+ if (attributesNode.length === 0) {
1002
+ return;
1003
+ }
1004
+ let requirementsNode = attributesNode.children('requirements');
1005
+ if (requirementsNode.length === 0) {
1006
+ requirementsNode = $X("<requirements xmlns='http://cpee.org/ns/properties/2.0'/>");
1007
+ attributesNode.append(requirementsNode);
1008
+ }
1009
+ requirementsNode.text(reqj);
132
1010
  do_parameters_save_part('attributes',$(att).serializePrettyXML());
133
1011
  }
134
1012
  } //}}}
1013
+
1014
+ function do_nl_requirements_save(event) { //{{{
1015
+ if (!save['attributes']) {
1016
+ return;
1017
+ }
1018
+
1019
+ // Keep regular requirements in sync when NL requirements are persisted.
1020
+ if (save['requirements']) {
1021
+ do_requirements_save({ target: $('#dat_requirements')[0] });
1022
+ }
1023
+
1024
+ let nlReqj = JSON.stringify(read_nl_requirements_object_from_ui());
1025
+ let att = save['attributes'].save();
1026
+ let attributesNode = $(' > attributes', att);
1027
+ if (attributesNode.length === 0) {
1028
+ return;
1029
+ }
1030
+
1031
+ let nlReqNode = attributesNode.children('NL-requirements');
1032
+ if (nlReqNode.length === 0) {
1033
+ nlReqNode = $X("<NL-requirements xmlns='http://cpee.org/ns/properties/2.0'/>");
1034
+ attributesNode.append(nlReqNode);
1035
+ }
1036
+
1037
+ if (save['requirements']) {
1038
+ let reqj = save['requirements'].save_json();
1039
+ let requirementsNode = attributesNode.children('requirements');
1040
+ if (requirementsNode.length === 0) {
1041
+ requirementsNode = $X("<requirements xmlns='http://cpee.org/ns/properties/2.0'/>");
1042
+ attributesNode.append(requirementsNode);
1043
+ }
1044
+ requirementsNode.text(reqj);
1045
+ }
1046
+
1047
+ nlReqNode.text(nlReqj);
1048
+ do_parameters_save_part('attributes',$(att).serializePrettyXML());
1049
+ } //}}}