scene-capability-engine 3.6.32 → 3.6.36
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.
- package/CHANGELOG.md +86 -1
- package/README.md +119 -122
- package/README.zh.md +123 -121
- package/bin/scene-capability-engine.js +11 -0
- package/docs/README.md +21 -32
- package/docs/auto-refactor-index.md +384 -0
- package/docs/command-reference.md +94 -2
- package/docs/magicball-adaptation-task-checklist-v1.md +385 -0
- package/docs/magicball-app-bundle-sqlite-and-command-draft.md +539 -0
- package/docs/magicball-capability-iteration-api.md +2 -0
- package/docs/magicball-capability-iteration-ui.md +2 -0
- package/docs/magicball-capability-library.md +2 -0
- package/docs/magicball-cli-invocation-examples.md +336 -0
- package/docs/magicball-frontend-state-and-command-mapping.md +244 -0
- package/docs/magicball-integration-doc-index.md +137 -0
- package/docs/magicball-integration-issue-tracker.md +218 -0
- package/docs/magicball-mode-home-and-ontology-empty-state-playbook.md +249 -0
- package/docs/magicball-sce-adaptation-guide.md +203 -0
- package/docs/magicball-three-mode-alignment-plan.md +551 -0
- package/docs/magicball-ui-surface-checklist.md +126 -0
- package/docs/magicball-write-auth-adaptation-guide.md +328 -0
- package/docs/refactor-completion-roadmap.md +116 -0
- package/docs/zh/README.md +27 -30
- package/docs/zh/refactor-completion-roadmap.md +116 -0
- package/lib/app/registry-config.js +73 -0
- package/lib/app/registry-sync-service.js +228 -0
- package/lib/auto/archive-schema-service.js +276 -0
- package/lib/auto/archive-summary.js +60 -0
- package/lib/auto/batch-goal-input-service.js +543 -0
- package/lib/auto/batch-output.js +201 -0
- package/lib/auto/batch-summary-storage-service.js +110 -0
- package/lib/auto/close-loop-batch-service.js +116 -0
- package/lib/auto/close-loop-controller-service.js +287 -0
- package/lib/auto/close-loop-program-service.js +283 -0
- package/lib/auto/close-loop-recovery-service.js +191 -0
- package/lib/auto/close-loop-session-storage-service.js +50 -0
- package/lib/auto/controller-lock-service.js +55 -0
- package/lib/auto/controller-output.js +32 -0
- package/lib/auto/controller-queue-service.js +127 -0
- package/lib/auto/controller-session-storage-service.js +105 -0
- package/lib/auto/governance-advisory-service.js +208 -0
- package/lib/auto/governance-close-loop-service.js +411 -0
- package/lib/auto/governance-maintenance-presenter.js +162 -0
- package/lib/auto/governance-maintenance-service.js +112 -0
- package/lib/auto/governance-session-presenter.js +70 -0
- package/lib/auto/governance-session-storage-service.js +198 -0
- package/lib/auto/governance-signals.js +139 -0
- package/lib/auto/governance-stats-presenter.js +337 -0
- package/lib/auto/governance-stats-service.js +115 -0
- package/lib/auto/governance-summary.js +703 -0
- package/lib/auto/handoff-capability-matrix-service.js +281 -0
- package/lib/auto/handoff-evidence-review-service.js +251 -0
- package/lib/auto/handoff-release-evidence-service.js +190 -0
- package/lib/auto/handoff-release-gate-history-loaders-service.js +502 -0
- package/lib/auto/handoff-release-gate-history-service.js +257 -0
- package/lib/auto/handoff-reporting-service.js +1407 -0
- package/lib/auto/handoff-run-service.js +486 -0
- package/lib/auto/handoff-snapshots-service.js +645 -0
- package/lib/auto/observability-service.js +132 -0
- package/lib/auto/output-writer.js +34 -0
- package/lib/auto/program-auto-remediation-service.js +130 -0
- package/lib/auto/program-diagnostics.js +138 -0
- package/lib/auto/program-governance-helpers.js +306 -0
- package/lib/auto/program-governance-loop-service.js +413 -0
- package/lib/auto/program-output.js +106 -0
- package/lib/auto/program-summary.js +183 -0
- package/lib/auto/recovery-memory-service.js +684 -0
- package/lib/auto/recovery-selection-service.js +52 -0
- package/lib/auto/retention-policy.js +98 -0
- package/lib/auto/session-persistence-service.js +106 -0
- package/lib/auto/session-presenter.js +105 -0
- package/lib/auto/session-prune-service.js +190 -0
- package/lib/auto/session-query-service.js +249 -0
- package/lib/auto/spec-protection.js +141 -0
- package/lib/commands/app.js +911 -0
- package/lib/commands/assurance.js +212 -0
- package/lib/commands/auto.js +1091 -11063
- package/lib/commands/mode.js +321 -0
- package/lib/commands/ontology.js +415 -0
- package/lib/commands/pm.js +422 -0
- package/lib/ontology/seed-profiles.js +160 -0
- package/lib/state/sce-state-store.js +3369 -1200
- package/package.json +1 -1
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
async function listCloseLoopSessions(projectPath, options = {}, dependencies = {}) {
|
|
2
|
+
const {
|
|
3
|
+
readCloseLoopSessionEntries,
|
|
4
|
+
normalizeStatusFilter,
|
|
5
|
+
filterEntriesByStatus,
|
|
6
|
+
normalizeLimit,
|
|
7
|
+
presentCloseLoopSessionList,
|
|
8
|
+
buildStatusCounts,
|
|
9
|
+
getCloseLoopSessionDir
|
|
10
|
+
} = dependencies;
|
|
11
|
+
const sessions = await readCloseLoopSessionEntries(projectPath);
|
|
12
|
+
const statusFilter = normalizeStatusFilter(options.status);
|
|
13
|
+
const filteredSessions = filterEntriesByStatus(sessions, statusFilter);
|
|
14
|
+
const limit = normalizeLimit(options.limit, 20);
|
|
15
|
+
return presentCloseLoopSessionList(
|
|
16
|
+
projectPath,
|
|
17
|
+
filteredSessions,
|
|
18
|
+
statusFilter,
|
|
19
|
+
limit,
|
|
20
|
+
buildStatusCounts,
|
|
21
|
+
getCloseLoopSessionDir
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async function statsCloseLoopSessions(projectPath, options = {}, dependencies = {}) {
|
|
26
|
+
const {
|
|
27
|
+
readCloseLoopSessionEntries,
|
|
28
|
+
normalizeStatsWindowDays,
|
|
29
|
+
normalizeStatusFilter,
|
|
30
|
+
filterEntriesByStatus,
|
|
31
|
+
presentCloseLoopSessionStats,
|
|
32
|
+
buildStatusCounts,
|
|
33
|
+
buildMasterSpecCounts,
|
|
34
|
+
isFailedStatus,
|
|
35
|
+
getCloseLoopSessionDir,
|
|
36
|
+
now = () => Date.now()
|
|
37
|
+
} = dependencies;
|
|
38
|
+
const sessions = await readCloseLoopSessionEntries(projectPath);
|
|
39
|
+
const days = normalizeStatsWindowDays(options.days);
|
|
40
|
+
const statusFilter = normalizeStatusFilter(options.status);
|
|
41
|
+
const nowMs = typeof now === 'function' ? Number(now()) : Date.now();
|
|
42
|
+
const cutoffMs = days === null ? null : nowMs - (days * 24 * 60 * 60 * 1000);
|
|
43
|
+
const withinWindow = sessions.filter((session) => cutoffMs === null || Number(session && session.mtime_ms) >= cutoffMs);
|
|
44
|
+
const filteredSessions = filterEntriesByStatus(withinWindow, statusFilter);
|
|
45
|
+
return presentCloseLoopSessionStats(
|
|
46
|
+
getCloseLoopSessionDir(projectPath),
|
|
47
|
+
filteredSessions,
|
|
48
|
+
statusFilter,
|
|
49
|
+
days,
|
|
50
|
+
cutoffMs,
|
|
51
|
+
buildStatusCounts,
|
|
52
|
+
buildMasterSpecCounts,
|
|
53
|
+
isFailedStatus
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async function listGovernanceCloseLoopSessions(projectPath, options = {}, dependencies = {}) {
|
|
58
|
+
const {
|
|
59
|
+
readGovernanceCloseLoopSessionEntries,
|
|
60
|
+
normalizeStatusFilter,
|
|
61
|
+
filterEntriesByStatus,
|
|
62
|
+
filterGovernanceEntriesByResumeMode,
|
|
63
|
+
normalizeLimit,
|
|
64
|
+
presentGovernanceSessionList,
|
|
65
|
+
buildStatusCounts,
|
|
66
|
+
getGovernanceCloseLoopSessionDir
|
|
67
|
+
} = dependencies;
|
|
68
|
+
const sessions = await readGovernanceCloseLoopSessionEntries(projectPath);
|
|
69
|
+
const statusFilter = normalizeStatusFilter(options.status);
|
|
70
|
+
const resumeOnly = Boolean(options.resumeOnly);
|
|
71
|
+
const statusFiltered = filterEntriesByStatus(sessions, statusFilter);
|
|
72
|
+
const filteredSessions = filterGovernanceEntriesByResumeMode(statusFiltered, resumeOnly);
|
|
73
|
+
const limit = normalizeLimit(options.limit, 20);
|
|
74
|
+
return presentGovernanceSessionList(
|
|
75
|
+
projectPath,
|
|
76
|
+
filteredSessions,
|
|
77
|
+
statusFilter,
|
|
78
|
+
resumeOnly,
|
|
79
|
+
buildStatusCounts,
|
|
80
|
+
getGovernanceCloseLoopSessionDir,
|
|
81
|
+
limit
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
async function statsGovernanceCloseLoopSessions(projectPath, options = {}, dependencies = {}) {
|
|
86
|
+
const {
|
|
87
|
+
readGovernanceCloseLoopSessionEntries,
|
|
88
|
+
normalizeStatsWindowDays,
|
|
89
|
+
normalizeStatusFilter,
|
|
90
|
+
filterEntriesByStatus,
|
|
91
|
+
filterGovernanceEntriesByResumeMode,
|
|
92
|
+
presentGovernanceSessionStats,
|
|
93
|
+
normalizeStatusToken,
|
|
94
|
+
isCompletedStatus,
|
|
95
|
+
isFailedStatus,
|
|
96
|
+
calculatePercent,
|
|
97
|
+
toGovernanceReleaseGateNumber,
|
|
98
|
+
getGovernanceCloseLoopSessionDir,
|
|
99
|
+
buildStatusCounts,
|
|
100
|
+
parseAutoHandoffGateBoolean,
|
|
101
|
+
now = () => Date.now()
|
|
102
|
+
} = dependencies;
|
|
103
|
+
const sessions = await readGovernanceCloseLoopSessionEntries(projectPath);
|
|
104
|
+
const days = normalizeStatsWindowDays(options.days);
|
|
105
|
+
const statusFilter = normalizeStatusFilter(options.status);
|
|
106
|
+
const resumeOnly = Boolean(options.resumeOnly);
|
|
107
|
+
const nowMs = typeof now === 'function' ? Number(now()) : Date.now();
|
|
108
|
+
const cutoffMs = days === null ? null : nowMs - (days * 24 * 60 * 60 * 1000);
|
|
109
|
+
const withinWindow = sessions.filter((session) => cutoffMs === null || Number(session && session.mtime_ms) >= cutoffMs);
|
|
110
|
+
const statusFiltered = filterEntriesByStatus(withinWindow, statusFilter);
|
|
111
|
+
const filteredSessions = filterGovernanceEntriesByResumeMode(statusFiltered, resumeOnly);
|
|
112
|
+
return presentGovernanceSessionStats(projectPath, filteredSessions, {
|
|
113
|
+
days,
|
|
114
|
+
status_filter: statusFilter,
|
|
115
|
+
resume_only: resumeOnly,
|
|
116
|
+
cutoff_ms: cutoffMs
|
|
117
|
+
}, {
|
|
118
|
+
normalizeStatusToken,
|
|
119
|
+
isCompletedStatus,
|
|
120
|
+
isFailedStatus,
|
|
121
|
+
calculatePercent,
|
|
122
|
+
toGovernanceReleaseGateNumber,
|
|
123
|
+
getGovernanceCloseLoopSessionDir,
|
|
124
|
+
buildStatusCounts,
|
|
125
|
+
parseAutoHandoffGateBoolean
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
async function listCloseLoopControllerSessions(projectPath, options = {}, dependencies = {}) {
|
|
130
|
+
const {
|
|
131
|
+
readCloseLoopControllerSessionEntries,
|
|
132
|
+
normalizeStatusFilter,
|
|
133
|
+
filterEntriesByStatus,
|
|
134
|
+
normalizeLimit,
|
|
135
|
+
presentControllerSessionList,
|
|
136
|
+
buildStatusCounts,
|
|
137
|
+
getCloseLoopControllerSessionDir
|
|
138
|
+
} = dependencies;
|
|
139
|
+
const sessions = await readCloseLoopControllerSessionEntries(projectPath);
|
|
140
|
+
const statusFilter = normalizeStatusFilter(options.status);
|
|
141
|
+
const filteredSessions = filterEntriesByStatus(sessions, statusFilter);
|
|
142
|
+
const limit = normalizeLimit(options.limit, 20);
|
|
143
|
+
return presentControllerSessionList(
|
|
144
|
+
projectPath,
|
|
145
|
+
filteredSessions,
|
|
146
|
+
statusFilter,
|
|
147
|
+
limit,
|
|
148
|
+
buildStatusCounts,
|
|
149
|
+
getCloseLoopControllerSessionDir
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
async function statsCloseLoopControllerSessions(projectPath, options = {}, dependencies = {}) {
|
|
154
|
+
const {
|
|
155
|
+
readCloseLoopControllerSessionEntries,
|
|
156
|
+
normalizeStatsWindowDays,
|
|
157
|
+
normalizeStatusFilter,
|
|
158
|
+
filterEntriesByStatus,
|
|
159
|
+
normalizeStatusToken,
|
|
160
|
+
isFailedStatus,
|
|
161
|
+
buildStatusCounts,
|
|
162
|
+
buildQueueFormatCounts,
|
|
163
|
+
getCloseLoopControllerSessionDir,
|
|
164
|
+
now = () => Date.now()
|
|
165
|
+
} = dependencies;
|
|
166
|
+
const sessions = await readCloseLoopControllerSessionEntries(projectPath);
|
|
167
|
+
const days = normalizeStatsWindowDays(options.days);
|
|
168
|
+
const statusFilter = normalizeStatusFilter(options.status);
|
|
169
|
+
const nowMs = typeof now === 'function' ? Number(now()) : Date.now();
|
|
170
|
+
const cutoffMs = days === null ? null : nowMs - (days * 24 * 60 * 60 * 1000);
|
|
171
|
+
const withinWindow = sessions.filter((session) => cutoffMs === null || Number(session && session.mtime_ms) >= cutoffMs);
|
|
172
|
+
const filteredSessions = filterEntriesByStatus(withinWindow, statusFilter);
|
|
173
|
+
|
|
174
|
+
let completedSessions = 0;
|
|
175
|
+
let failedSessions = 0;
|
|
176
|
+
let processedGoalsSum = 0;
|
|
177
|
+
let pendingGoalsSum = 0;
|
|
178
|
+
let sessionsWithProcessed = 0;
|
|
179
|
+
let sessionsWithPending = 0;
|
|
180
|
+
|
|
181
|
+
for (const session of filteredSessions) {
|
|
182
|
+
const status = normalizeStatusToken(session && session.status) || 'unknown';
|
|
183
|
+
if (status === 'completed') {
|
|
184
|
+
completedSessions += 1;
|
|
185
|
+
}
|
|
186
|
+
if (isFailedStatus(status)) {
|
|
187
|
+
failedSessions += 1;
|
|
188
|
+
}
|
|
189
|
+
const processedGoals = Number(session && session.processed_goals);
|
|
190
|
+
if (Number.isFinite(processedGoals)) {
|
|
191
|
+
processedGoalsSum += processedGoals;
|
|
192
|
+
sessionsWithProcessed += 1;
|
|
193
|
+
}
|
|
194
|
+
const pendingGoals = Number(session && session.pending_goals);
|
|
195
|
+
if (Number.isFinite(pendingGoals)) {
|
|
196
|
+
pendingGoalsSum += pendingGoals;
|
|
197
|
+
sessionsWithPending += 1;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
const totalSessions = filteredSessions.length;
|
|
202
|
+
const completionRate = totalSessions > 0 ? Number(((completedSessions / totalSessions) * 100).toFixed(2)) : 0;
|
|
203
|
+
const failureRate = totalSessions > 0 ? Number(((failedSessions / totalSessions) * 100).toFixed(2)) : 0;
|
|
204
|
+
const latestSession = totalSessions > 0 ? filteredSessions[0] : null;
|
|
205
|
+
const oldestSession = totalSessions > 0 ? filteredSessions[totalSessions - 1] : null;
|
|
206
|
+
|
|
207
|
+
return {
|
|
208
|
+
mode: 'auto-controller-session-stats',
|
|
209
|
+
session_dir: getCloseLoopControllerSessionDir(projectPath),
|
|
210
|
+
criteria: {
|
|
211
|
+
days,
|
|
212
|
+
status_filter: statusFilter,
|
|
213
|
+
since: cutoffMs === null ? null : new Date(cutoffMs).toISOString()
|
|
214
|
+
},
|
|
215
|
+
total_sessions: totalSessions,
|
|
216
|
+
completed_sessions: completedSessions,
|
|
217
|
+
failed_sessions: failedSessions,
|
|
218
|
+
completion_rate_percent: completionRate,
|
|
219
|
+
failure_rate_percent: failureRate,
|
|
220
|
+
processed_goals_sum: processedGoalsSum,
|
|
221
|
+
pending_goals_sum: pendingGoalsSum,
|
|
222
|
+
average_processed_goals_per_session: sessionsWithProcessed > 0 ? Number((processedGoalsSum / sessionsWithProcessed).toFixed(2)) : 0,
|
|
223
|
+
average_pending_goals_per_session: sessionsWithPending > 0 ? Number((pendingGoalsSum / sessionsWithPending).toFixed(2)) : 0,
|
|
224
|
+
status_counts: buildStatusCounts(filteredSessions),
|
|
225
|
+
queue_format_counts: buildQueueFormatCounts(filteredSessions),
|
|
226
|
+
latest_updated_at: latestSession ? latestSession.updated_at : null,
|
|
227
|
+
oldest_updated_at: oldestSession ? oldestSession.updated_at : null,
|
|
228
|
+
latest_sessions: filteredSessions.slice(0, 10).map((item) => ({
|
|
229
|
+
id: item.id,
|
|
230
|
+
status: item.status,
|
|
231
|
+
queue_file: item.queue_file,
|
|
232
|
+
queue_format: item.queue_format,
|
|
233
|
+
processed_goals: item.processed_goals,
|
|
234
|
+
pending_goals: item.pending_goals,
|
|
235
|
+
updated_at: item.updated_at,
|
|
236
|
+
parse_error: item.parse_error
|
|
237
|
+
}))
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
module.exports = {
|
|
242
|
+
listCloseLoopSessions,
|
|
243
|
+
statsCloseLoopSessions,
|
|
244
|
+
listGovernanceCloseLoopSessions,
|
|
245
|
+
statsGovernanceCloseLoopSessions,
|
|
246
|
+
listCloseLoopControllerSessions,
|
|
247
|
+
statsCloseLoopControllerSessions
|
|
248
|
+
};
|
|
249
|
+
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
function normalizeStatusToken(statusCandidate) {
|
|
2
|
+
return String(statusCandidate || '').trim().toLowerCase();
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
function collectSpecNamesFromBatchSummary(summary) {
|
|
6
|
+
const names = new Set();
|
|
7
|
+
const results = Array.isArray(summary && summary.results) ? summary.results : [];
|
|
8
|
+
for (const item of results) {
|
|
9
|
+
const masterSpec = String(item && item.master_spec ? item.master_spec : '').trim();
|
|
10
|
+
if (masterSpec) {
|
|
11
|
+
names.add(masterSpec);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
return [...names];
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function collectSpecNamesFromCloseLoopSessionPayload(payload) {
|
|
18
|
+
const names = new Set();
|
|
19
|
+
const portfolio = payload && payload.portfolio && typeof payload.portfolio === 'object'
|
|
20
|
+
? payload.portfolio
|
|
21
|
+
: {};
|
|
22
|
+
const masterSpec = String(portfolio.master_spec || '').trim();
|
|
23
|
+
if (masterSpec) {
|
|
24
|
+
names.add(masterSpec);
|
|
25
|
+
}
|
|
26
|
+
const subSpecs = Array.isArray(portfolio.sub_specs) ? portfolio.sub_specs : [];
|
|
27
|
+
for (const item of subSpecs) {
|
|
28
|
+
const specName = String(item || '').trim();
|
|
29
|
+
if (specName) {
|
|
30
|
+
names.add(specName);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return names;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function collectSpecNamesFromBatchSummaryPayload(payload, includeCompleted = false) {
|
|
37
|
+
const names = new Set();
|
|
38
|
+
const results = Array.isArray(payload && payload.results) ? payload.results : [];
|
|
39
|
+
for (const item of results) {
|
|
40
|
+
const status = normalizeStatusToken(item && item.status);
|
|
41
|
+
if (!includeCompleted && status === 'completed') {
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
const masterSpec = String(item && item.master_spec ? item.master_spec : '').trim();
|
|
45
|
+
if (masterSpec) {
|
|
46
|
+
names.add(masterSpec);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return names;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function createProtectionReasonRecord() {
|
|
53
|
+
return {
|
|
54
|
+
additional: 0,
|
|
55
|
+
collaboration_active: 0,
|
|
56
|
+
close_loop_session_recent_or_incomplete: 0,
|
|
57
|
+
batch_summary_recent_or_incomplete: 0,
|
|
58
|
+
controller_session_recent_or_incomplete: 0,
|
|
59
|
+
total_references: 0
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function ensureProtectionReasonRecord(reasonMap, specName) {
|
|
64
|
+
const key = String(specName || '').trim();
|
|
65
|
+
if (!key) {
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
if (!reasonMap.has(key)) {
|
|
69
|
+
reasonMap.set(key, createProtectionReasonRecord());
|
|
70
|
+
}
|
|
71
|
+
return reasonMap.get(key);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function incrementProtectionReason(reasonMap, specName, reasonKey, delta = 1) {
|
|
75
|
+
if (!Number.isFinite(delta) || delta <= 0) {
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
const record = ensureProtectionReasonRecord(reasonMap, specName);
|
|
79
|
+
if (!record) {
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
const normalizedReason = String(reasonKey || '').trim();
|
|
83
|
+
if (!normalizedReason || !Object.prototype.hasOwnProperty.call(record, normalizedReason)) {
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
record[normalizedReason] += delta;
|
|
87
|
+
record.total_references += delta;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function buildProtectionRanking(reasonMap) {
|
|
91
|
+
const entries = [];
|
|
92
|
+
for (const [spec, reasons] of reasonMap.entries()) {
|
|
93
|
+
entries.push({
|
|
94
|
+
spec,
|
|
95
|
+
total_references: Number(reasons.total_references) || 0,
|
|
96
|
+
reasons: {
|
|
97
|
+
additional: Number(reasons.additional) || 0,
|
|
98
|
+
collaboration_active: Number(reasons.collaboration_active) || 0,
|
|
99
|
+
close_loop_session_recent_or_incomplete: Number(reasons.close_loop_session_recent_or_incomplete) || 0,
|
|
100
|
+
batch_summary_recent_or_incomplete: Number(reasons.batch_summary_recent_or_incomplete) || 0,
|
|
101
|
+
controller_session_recent_or_incomplete: Number(reasons.controller_session_recent_or_incomplete) || 0
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
entries.sort((left, right) => {
|
|
106
|
+
if (right.total_references !== left.total_references) {
|
|
107
|
+
return right.total_references - left.total_references;
|
|
108
|
+
}
|
|
109
|
+
return left.spec.localeCompare(right.spec);
|
|
110
|
+
});
|
|
111
|
+
return entries;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function buildSpecProtectionReasonPayload(specName, reasonMap) {
|
|
115
|
+
if (!reasonMap || typeof reasonMap.get !== 'function') {
|
|
116
|
+
return null;
|
|
117
|
+
}
|
|
118
|
+
const record = reasonMap.get(specName);
|
|
119
|
+
if (!record) {
|
|
120
|
+
return null;
|
|
121
|
+
}
|
|
122
|
+
return {
|
|
123
|
+
total_references: Number(record.total_references) || 0,
|
|
124
|
+
additional: Number(record.additional) || 0,
|
|
125
|
+
collaboration_active: Number(record.collaboration_active) || 0,
|
|
126
|
+
close_loop_session_recent_or_incomplete: Number(record.close_loop_session_recent_or_incomplete) || 0,
|
|
127
|
+
batch_summary_recent_or_incomplete: Number(record.batch_summary_recent_or_incomplete) || 0,
|
|
128
|
+
controller_session_recent_or_incomplete: Number(record.controller_session_recent_or_incomplete) || 0
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
module.exports = {
|
|
133
|
+
collectSpecNamesFromBatchSummary,
|
|
134
|
+
collectSpecNamesFromCloseLoopSessionPayload,
|
|
135
|
+
collectSpecNamesFromBatchSummaryPayload,
|
|
136
|
+
createProtectionReasonRecord,
|
|
137
|
+
ensureProtectionReasonRecord,
|
|
138
|
+
incrementProtectionReason,
|
|
139
|
+
buildProtectionRanking,
|
|
140
|
+
buildSpecProtectionReasonPayload
|
|
141
|
+
};
|