@bicon/mcp-composer 0.13.1 → 0.16.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.
- package/README.md +6 -4
- package/dist/index.mjs +819 -47
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -11,9 +11,6 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
|
|
|
11
11
|
// src/server.ts
|
|
12
12
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
13
13
|
|
|
14
|
-
// src/version.ts
|
|
15
|
-
var COMPOSER_VERSION = true ? "0.13.1" : readPackageVersion();
|
|
16
|
-
|
|
17
14
|
// ../../../node_modules/.pnpm/zod@4.6.7/node_modules/zod/v4/classic/external.js
|
|
18
15
|
var external_exports = {};
|
|
19
16
|
__export(external_exports, {
|
|
@@ -19731,6 +19728,9 @@ function date4(params) {
|
|
|
19731
19728
|
return _coercedDate(ZodDate, params);
|
|
19732
19729
|
}
|
|
19733
19730
|
|
|
19731
|
+
// src/version.ts
|
|
19732
|
+
var COMPOSER_VERSION = true ? "0.16.0" : readPackageVersion();
|
|
19733
|
+
|
|
19734
19734
|
// ../../api-zod/src/generated/api.ts
|
|
19735
19735
|
var HealthCheckResponse = object({
|
|
19736
19736
|
"status": string2(),
|
|
@@ -19846,7 +19846,7 @@ var ListOrganizationMembersResponseItem = object({
|
|
|
19846
19846
|
"email": string2(),
|
|
19847
19847
|
"displayName": string2().nullable(),
|
|
19848
19848
|
"role": _enum2(["admin", "member", "viewer"]),
|
|
19849
|
-
"permissions": array(string2()).describe("App-managed permission overrides on top of the fixed role (free-form strings, not a closed enum). Known keys: manage_skills, manage_ai_act, manage_metadata, manage_stage_gate, manage_economics, manage_decision_log, manage_dependencies, manage_required_skills, manage_scoring, manage_status_report, manage_prioritisation, manage_capabilities, manage_capability_impact, manage_milestones, manage_risks, manage_follow_ups.")
|
|
19849
|
+
"permissions": array(string2()).describe("App-managed permission overrides on top of the fixed role (free-form strings, not a closed enum). Known keys: manage_skills, manage_ai_act, manage_metadata, manage_stage_gate, manage_economics, manage_decision_log, manage_dependencies, manage_required_skills, manage_scoring, manage_status_report, manage_prioritisation, manage_capabilities, manage_capability_impact, manage_milestones, manage_risks, manage_follow_ups, manage_quality.")
|
|
19850
19850
|
});
|
|
19851
19851
|
var ListOrganizationMembersResponse = array(ListOrganizationMembersResponseItem);
|
|
19852
19852
|
var SyncOrganizationMembersResponse = object({
|
|
@@ -19856,7 +19856,7 @@ var SyncOrganizationMembersResponse = object({
|
|
|
19856
19856
|
var GetMyOrganizationAccessResponse = object({
|
|
19857
19857
|
"userId": string2().describe(`The caller's own local user id (matches AuthorRef.userId on initiatives), so the client can tell its own changes apart from a teammate's \u2014 e.g. to suppress the "updated by X" collaboration banner when an edit echoing back is the caller's own save.`),
|
|
19858
19858
|
"role": _enum2(["admin", "member", "viewer"]),
|
|
19859
|
-
"permissions": array(string2()).describe("App-managed permission overrides on top of the fixed role (free-form strings, not a closed enum). Known keys: manage_skills, manage_ai_act, manage_metadata, manage_stage_gate, manage_economics, manage_decision_log, manage_dependencies, manage_required_skills, manage_scoring, manage_status_report, manage_prioritisation, manage_capabilities, manage_capability_impact, manage_milestones, manage_risks, manage_follow_ups."),
|
|
19859
|
+
"permissions": array(string2()).describe("App-managed permission overrides on top of the fixed role (free-form strings, not a closed enum). Known keys: manage_skills, manage_ai_act, manage_metadata, manage_stage_gate, manage_economics, manage_decision_log, manage_dependencies, manage_required_skills, manage_scoring, manage_status_report, manage_prioritisation, manage_capabilities, manage_capability_impact, manage_milestones, manage_risks, manage_follow_ups, manage_quality."),
|
|
19860
19860
|
"initiativeLimit": number2().describe("The most initiatives this organisation may hold, archived ones included. Creating one more answers 507 with code initiative_limit_reached, so the client blocks New, Duplicate and Import up front.")
|
|
19861
19861
|
});
|
|
19862
19862
|
var ListOrganizationDirectoryResponseItem = object({
|
|
@@ -19868,11 +19868,11 @@ var UpdateOrganizationMemberPermissionsParams = object({
|
|
|
19868
19868
|
"userId": guid2()
|
|
19869
19869
|
});
|
|
19870
19870
|
var UpdateOrganizationMemberPermissionsBody = object({
|
|
19871
|
-
"permissions": array(string2()).describe("App-managed permission overrides on top of the fixed role (free-form strings, not a closed enum). Known keys: manage_skills, manage_ai_act, manage_metadata, manage_stage_gate, manage_economics, manage_decision_log, manage_dependencies, manage_required_skills, manage_scoring, manage_status_report, manage_prioritisation, manage_capabilities, manage_capability_impact, manage_milestones, manage_risks, manage_follow_ups.")
|
|
19871
|
+
"permissions": array(string2()).describe("App-managed permission overrides on top of the fixed role (free-form strings, not a closed enum). Known keys: manage_skills, manage_ai_act, manage_metadata, manage_stage_gate, manage_economics, manage_decision_log, manage_dependencies, manage_required_skills, manage_scoring, manage_status_report, manage_prioritisation, manage_capabilities, manage_capability_impact, manage_milestones, manage_risks, manage_follow_ups, manage_quality.")
|
|
19872
19872
|
});
|
|
19873
19873
|
var UpdateOrganizationMemberPermissionsResponse = object({
|
|
19874
19874
|
"userId": guid2(),
|
|
19875
|
-
"permissions": array(string2()).describe("App-managed permission overrides on top of the fixed role (free-form strings, not a closed enum). Known keys: manage_skills, manage_ai_act, manage_metadata, manage_stage_gate, manage_economics, manage_decision_log, manage_dependencies, manage_required_skills, manage_scoring, manage_status_report, manage_prioritisation, manage_capabilities, manage_capability_impact, manage_milestones, manage_risks, manage_follow_ups.")
|
|
19875
|
+
"permissions": array(string2()).describe("App-managed permission overrides on top of the fixed role (free-form strings, not a closed enum). Known keys: manage_skills, manage_ai_act, manage_metadata, manage_stage_gate, manage_economics, manage_decision_log, manage_dependencies, manage_required_skills, manage_scoring, manage_status_report, manage_prioritisation, manage_capabilities, manage_capability_impact, manage_milestones, manage_risks, manage_follow_ups, manage_quality.")
|
|
19876
19876
|
});
|
|
19877
19877
|
var ListPermissionProfilesResponseItem = object({
|
|
19878
19878
|
"id": guid2(),
|
|
@@ -19986,6 +19986,10 @@ var listSnapshotsResponseInitiativesItemFollowUpsItemDueDateRegExp = new RegExp(
|
|
|
19986
19986
|
var listSnapshotsResponseInitiativesItemFollowUpsItemDoneDateRegExp = new RegExp("^\\d{4}-\\d{2}-\\d{2}$");
|
|
19987
19987
|
var listSnapshotsResponseInitiativesItemFollowUpsItemNoteMax = 1e4;
|
|
19988
19988
|
var listSnapshotsResponseInitiativesItemFollowUpsMax = 50;
|
|
19989
|
+
var listSnapshotsResponseInitiativesItemQualityFactorsItemStateMax = 5;
|
|
19990
|
+
var listSnapshotsResponseInitiativesItemQualityFactorsItemTargetDateRegExp = new RegExp("^\\d{4}-\\d{2}-\\d{2}$");
|
|
19991
|
+
var listSnapshotsResponseInitiativesItemQualityFactorsItemNoteMax = 2e3;
|
|
19992
|
+
var listSnapshotsResponseInitiativesItemQualityFactorsMax = 7;
|
|
19989
19993
|
var listSnapshotsResponseInitiativesItemDependsOnMax = 1e3;
|
|
19990
19994
|
var listSnapshotsResponseInitiativesItemRequiredSkillsItemRunFteMin = 0;
|
|
19991
19995
|
var listSnapshotsResponseInitiativesItemRequiredSkillsMax = 1e3;
|
|
@@ -20224,6 +20228,13 @@ var ListSnapshotsResponseItem = object({
|
|
|
20224
20228
|
"riskId": string2().optional(),
|
|
20225
20229
|
"note": string2().max(listSnapshotsResponseInitiativesItemFollowUpsItemNoteMax).optional()
|
|
20226
20230
|
}).describe("An agreed next step: who does what by when (docs/risks-issues-follow-ups-concept-2026-10-19.md). assigneeUserId is a member of the org (an id that is not is dropped on save, the name kept); milestoneId and riskId name the same initiative's milestones and risks (a link that does not is cleared on save). Dates are yyyy-mm-dd.")).max(listSnapshotsResponseInitiativesItemFollowUpsMax).optional(),
|
|
20231
|
+
"qualityFactors": array(object({
|
|
20232
|
+
"factor": _enum2(["reliability", "security", "performance", "usability", "maintainability", "compatibility", "dataQuality"]),
|
|
20233
|
+
"required": _enum2(["low", "medium", "high"]).optional(),
|
|
20234
|
+
"state": number2().min(1).max(listSnapshotsResponseInitiativesItemQualityFactorsItemStateMax).optional(),
|
|
20235
|
+
"targetDate": string2().regex(listSnapshotsResponseInitiativesItemQualityFactorsItemTargetDateRegExp).optional(),
|
|
20236
|
+
"note": string2().max(listSnapshotsResponseInitiativesItemQualityFactorsItemNoteMax).optional()
|
|
20237
|
+
}).describe("The quality of the delivered solution on one factor (docs/quality-factors-concept-2026-10-21.md, after ISO/IEC 25010). One entry per factor; required is how much it matters, state (1-5) how well the solution meets it today. Never part of a score.")).max(listSnapshotsResponseInitiativesItemQualityFactorsMax).optional(),
|
|
20227
20238
|
"dependsOn": array(string2()).max(listSnapshotsResponseInitiativesItemDependsOnMax),
|
|
20228
20239
|
"requiredSkills": array(object({
|
|
20229
20240
|
"skillId": string2(),
|
|
@@ -20386,6 +20397,10 @@ var createSnapshotBodyInitiativesItemFollowUpsItemDueDateRegExp = new RegExp("^\
|
|
|
20386
20397
|
var createSnapshotBodyInitiativesItemFollowUpsItemDoneDateRegExp = new RegExp("^\\d{4}-\\d{2}-\\d{2}$");
|
|
20387
20398
|
var createSnapshotBodyInitiativesItemFollowUpsItemNoteMax = 1e4;
|
|
20388
20399
|
var createSnapshotBodyInitiativesItemFollowUpsMax = 50;
|
|
20400
|
+
var createSnapshotBodyInitiativesItemQualityFactorsItemStateMax = 5;
|
|
20401
|
+
var createSnapshotBodyInitiativesItemQualityFactorsItemTargetDateRegExp = new RegExp("^\\d{4}-\\d{2}-\\d{2}$");
|
|
20402
|
+
var createSnapshotBodyInitiativesItemQualityFactorsItemNoteMax = 2e3;
|
|
20403
|
+
var createSnapshotBodyInitiativesItemQualityFactorsMax = 7;
|
|
20389
20404
|
var createSnapshotBodyInitiativesItemDependsOnMax = 1e3;
|
|
20390
20405
|
var createSnapshotBodyInitiativesItemRequiredSkillsItemRunFteMin = 0;
|
|
20391
20406
|
var createSnapshotBodyInitiativesItemRequiredSkillsMax = 1e3;
|
|
@@ -20624,6 +20639,13 @@ var CreateSnapshotBody = object({
|
|
|
20624
20639
|
"riskId": string2().optional(),
|
|
20625
20640
|
"note": string2().max(createSnapshotBodyInitiativesItemFollowUpsItemNoteMax).optional()
|
|
20626
20641
|
}).describe("An agreed next step: who does what by when (docs/risks-issues-follow-ups-concept-2026-10-19.md). assigneeUserId is a member of the org (an id that is not is dropped on save, the name kept); milestoneId and riskId name the same initiative's milestones and risks (a link that does not is cleared on save). Dates are yyyy-mm-dd.")).max(createSnapshotBodyInitiativesItemFollowUpsMax).optional(),
|
|
20642
|
+
"qualityFactors": array(object({
|
|
20643
|
+
"factor": _enum2(["reliability", "security", "performance", "usability", "maintainability", "compatibility", "dataQuality"]),
|
|
20644
|
+
"required": _enum2(["low", "medium", "high"]).optional(),
|
|
20645
|
+
"state": number2().min(1).max(createSnapshotBodyInitiativesItemQualityFactorsItemStateMax).optional(),
|
|
20646
|
+
"targetDate": string2().regex(createSnapshotBodyInitiativesItemQualityFactorsItemTargetDateRegExp).optional(),
|
|
20647
|
+
"note": string2().max(createSnapshotBodyInitiativesItemQualityFactorsItemNoteMax).optional()
|
|
20648
|
+
}).describe("The quality of the delivered solution on one factor (docs/quality-factors-concept-2026-10-21.md, after ISO/IEC 25010). One entry per factor; required is how much it matters, state (1-5) how well the solution meets it today. Never part of a score.")).max(createSnapshotBodyInitiativesItemQualityFactorsMax).optional(),
|
|
20627
20649
|
"dependsOn": array(string2()).max(createSnapshotBodyInitiativesItemDependsOnMax),
|
|
20628
20650
|
"requiredSkills": array(object({
|
|
20629
20651
|
"skillId": string2(),
|
|
@@ -20792,6 +20814,10 @@ var renameSnapshotResponseInitiativesItemFollowUpsItemDueDateRegExp = new RegExp
|
|
|
20792
20814
|
var renameSnapshotResponseInitiativesItemFollowUpsItemDoneDateRegExp = new RegExp("^\\d{4}-\\d{2}-\\d{2}$");
|
|
20793
20815
|
var renameSnapshotResponseInitiativesItemFollowUpsItemNoteMax = 1e4;
|
|
20794
20816
|
var renameSnapshotResponseInitiativesItemFollowUpsMax = 50;
|
|
20817
|
+
var renameSnapshotResponseInitiativesItemQualityFactorsItemStateMax = 5;
|
|
20818
|
+
var renameSnapshotResponseInitiativesItemQualityFactorsItemTargetDateRegExp = new RegExp("^\\d{4}-\\d{2}-\\d{2}$");
|
|
20819
|
+
var renameSnapshotResponseInitiativesItemQualityFactorsItemNoteMax = 2e3;
|
|
20820
|
+
var renameSnapshotResponseInitiativesItemQualityFactorsMax = 7;
|
|
20795
20821
|
var renameSnapshotResponseInitiativesItemDependsOnMax = 1e3;
|
|
20796
20822
|
var renameSnapshotResponseInitiativesItemRequiredSkillsItemRunFteMin = 0;
|
|
20797
20823
|
var renameSnapshotResponseInitiativesItemRequiredSkillsMax = 1e3;
|
|
@@ -21030,6 +21056,13 @@ var RenameSnapshotResponse = object({
|
|
|
21030
21056
|
"riskId": string2().optional(),
|
|
21031
21057
|
"note": string2().max(renameSnapshotResponseInitiativesItemFollowUpsItemNoteMax).optional()
|
|
21032
21058
|
}).describe("An agreed next step: who does what by when (docs/risks-issues-follow-ups-concept-2026-10-19.md). assigneeUserId is a member of the org (an id that is not is dropped on save, the name kept); milestoneId and riskId name the same initiative's milestones and risks (a link that does not is cleared on save). Dates are yyyy-mm-dd.")).max(renameSnapshotResponseInitiativesItemFollowUpsMax).optional(),
|
|
21059
|
+
"qualityFactors": array(object({
|
|
21060
|
+
"factor": _enum2(["reliability", "security", "performance", "usability", "maintainability", "compatibility", "dataQuality"]),
|
|
21061
|
+
"required": _enum2(["low", "medium", "high"]).optional(),
|
|
21062
|
+
"state": number2().min(1).max(renameSnapshotResponseInitiativesItemQualityFactorsItemStateMax).optional(),
|
|
21063
|
+
"targetDate": string2().regex(renameSnapshotResponseInitiativesItemQualityFactorsItemTargetDateRegExp).optional(),
|
|
21064
|
+
"note": string2().max(renameSnapshotResponseInitiativesItemQualityFactorsItemNoteMax).optional()
|
|
21065
|
+
}).describe("The quality of the delivered solution on one factor (docs/quality-factors-concept-2026-10-21.md, after ISO/IEC 25010). One entry per factor; required is how much it matters, state (1-5) how well the solution meets it today. Never part of a score.")).max(renameSnapshotResponseInitiativesItemQualityFactorsMax).optional(),
|
|
21033
21066
|
"dependsOn": array(string2()).max(renameSnapshotResponseInitiativesItemDependsOnMax),
|
|
21034
21067
|
"requiredSkills": array(object({
|
|
21035
21068
|
"skillId": string2(),
|
|
@@ -21193,6 +21226,10 @@ var listInitiativesResponseFollowUpsItemDueDateRegExp = new RegExp("^\\d{4}-\\d{
|
|
|
21193
21226
|
var listInitiativesResponseFollowUpsItemDoneDateRegExp = new RegExp("^\\d{4}-\\d{2}-\\d{2}$");
|
|
21194
21227
|
var listInitiativesResponseFollowUpsItemNoteMax = 1e4;
|
|
21195
21228
|
var listInitiativesResponseFollowUpsMax = 50;
|
|
21229
|
+
var listInitiativesResponseQualityFactorsItemStateMax = 5;
|
|
21230
|
+
var listInitiativesResponseQualityFactorsItemTargetDateRegExp = new RegExp("^\\d{4}-\\d{2}-\\d{2}$");
|
|
21231
|
+
var listInitiativesResponseQualityFactorsItemNoteMax = 2e3;
|
|
21232
|
+
var listInitiativesResponseQualityFactorsMax = 7;
|
|
21196
21233
|
var listInitiativesResponseDependsOnMax = 1e3;
|
|
21197
21234
|
var listInitiativesResponseRequiredSkillsItemRunFteMin = 0;
|
|
21198
21235
|
var listInitiativesResponseRequiredSkillsMax = 1e3;
|
|
@@ -21425,6 +21462,13 @@ var ListInitiativesResponseItem = object({
|
|
|
21425
21462
|
"riskId": string2().optional(),
|
|
21426
21463
|
"note": string2().max(listInitiativesResponseFollowUpsItemNoteMax).optional()
|
|
21427
21464
|
}).describe("An agreed next step: who does what by when (docs/risks-issues-follow-ups-concept-2026-10-19.md). assigneeUserId is a member of the org (an id that is not is dropped on save, the name kept); milestoneId and riskId name the same initiative's milestones and risks (a link that does not is cleared on save). Dates are yyyy-mm-dd.")).max(listInitiativesResponseFollowUpsMax).optional(),
|
|
21465
|
+
"qualityFactors": array(object({
|
|
21466
|
+
"factor": _enum2(["reliability", "security", "performance", "usability", "maintainability", "compatibility", "dataQuality"]),
|
|
21467
|
+
"required": _enum2(["low", "medium", "high"]).optional(),
|
|
21468
|
+
"state": number2().min(1).max(listInitiativesResponseQualityFactorsItemStateMax).optional(),
|
|
21469
|
+
"targetDate": string2().regex(listInitiativesResponseQualityFactorsItemTargetDateRegExp).optional(),
|
|
21470
|
+
"note": string2().max(listInitiativesResponseQualityFactorsItemNoteMax).optional()
|
|
21471
|
+
}).describe("The quality of the delivered solution on one factor (docs/quality-factors-concept-2026-10-21.md, after ISO/IEC 25010). One entry per factor; required is how much it matters, state (1-5) how well the solution meets it today. Never part of a score.")).max(listInitiativesResponseQualityFactorsMax).optional(),
|
|
21428
21472
|
"dependsOn": array(string2()).max(listInitiativesResponseDependsOnMax),
|
|
21429
21473
|
"requiredSkills": array(object({
|
|
21430
21474
|
"skillId": string2(),
|
|
@@ -21584,6 +21628,10 @@ var createInitiativeBodyOneFollowUpsItemDueDateRegExp = new RegExp("^\\d{4}-\\d{
|
|
|
21584
21628
|
var createInitiativeBodyOneFollowUpsItemDoneDateRegExp = new RegExp("^\\d{4}-\\d{2}-\\d{2}$");
|
|
21585
21629
|
var createInitiativeBodyOneFollowUpsItemNoteMax = 1e4;
|
|
21586
21630
|
var createInitiativeBodyOneFollowUpsMax = 50;
|
|
21631
|
+
var createInitiativeBodyOneQualityFactorsItemStateMax = 5;
|
|
21632
|
+
var createInitiativeBodyOneQualityFactorsItemTargetDateRegExp = new RegExp("^\\d{4}-\\d{2}-\\d{2}$");
|
|
21633
|
+
var createInitiativeBodyOneQualityFactorsItemNoteMax = 2e3;
|
|
21634
|
+
var createInitiativeBodyOneQualityFactorsMax = 7;
|
|
21587
21635
|
var createInitiativeBodyOneDependsOnMax = 1e3;
|
|
21588
21636
|
var createInitiativeBodyOneRequiredSkillsItemRunFteMin = 0;
|
|
21589
21637
|
var createInitiativeBodyOneRequiredSkillsMax = 1e3;
|
|
@@ -21816,6 +21864,13 @@ var CreateInitiativeBody = object({
|
|
|
21816
21864
|
"riskId": string2().optional(),
|
|
21817
21865
|
"note": string2().max(createInitiativeBodyOneFollowUpsItemNoteMax).optional()
|
|
21818
21866
|
}).describe("An agreed next step: who does what by when (docs/risks-issues-follow-ups-concept-2026-10-19.md). assigneeUserId is a member of the org (an id that is not is dropped on save, the name kept); milestoneId and riskId name the same initiative's milestones and risks (a link that does not is cleared on save). Dates are yyyy-mm-dd.")).max(createInitiativeBodyOneFollowUpsMax).optional(),
|
|
21867
|
+
"qualityFactors": array(object({
|
|
21868
|
+
"factor": _enum2(["reliability", "security", "performance", "usability", "maintainability", "compatibility", "dataQuality"]),
|
|
21869
|
+
"required": _enum2(["low", "medium", "high"]).optional(),
|
|
21870
|
+
"state": number2().min(1).max(createInitiativeBodyOneQualityFactorsItemStateMax).optional(),
|
|
21871
|
+
"targetDate": string2().regex(createInitiativeBodyOneQualityFactorsItemTargetDateRegExp).optional(),
|
|
21872
|
+
"note": string2().max(createInitiativeBodyOneQualityFactorsItemNoteMax).optional()
|
|
21873
|
+
}).describe("The quality of the delivered solution on one factor (docs/quality-factors-concept-2026-10-21.md, after ISO/IEC 25010). One entry per factor; required is how much it matters, state (1-5) how well the solution meets it today. Never part of a score.")).max(createInitiativeBodyOneQualityFactorsMax).optional(),
|
|
21819
21874
|
"dependsOn": array(string2()).max(createInitiativeBodyOneDependsOnMax),
|
|
21820
21875
|
"requiredSkills": array(object({
|
|
21821
21876
|
"skillId": string2(),
|
|
@@ -21985,6 +22040,10 @@ var getInitiativeResponseFollowUpsItemDueDateRegExp = new RegExp("^\\d{4}-\\d{2}
|
|
|
21985
22040
|
var getInitiativeResponseFollowUpsItemDoneDateRegExp = new RegExp("^\\d{4}-\\d{2}-\\d{2}$");
|
|
21986
22041
|
var getInitiativeResponseFollowUpsItemNoteMax = 1e4;
|
|
21987
22042
|
var getInitiativeResponseFollowUpsMax = 50;
|
|
22043
|
+
var getInitiativeResponseQualityFactorsItemStateMax = 5;
|
|
22044
|
+
var getInitiativeResponseQualityFactorsItemTargetDateRegExp = new RegExp("^\\d{4}-\\d{2}-\\d{2}$");
|
|
22045
|
+
var getInitiativeResponseQualityFactorsItemNoteMax = 2e3;
|
|
22046
|
+
var getInitiativeResponseQualityFactorsMax = 7;
|
|
21988
22047
|
var getInitiativeResponseDependsOnMax = 1e3;
|
|
21989
22048
|
var getInitiativeResponseRequiredSkillsItemRunFteMin = 0;
|
|
21990
22049
|
var getInitiativeResponseRequiredSkillsMax = 1e3;
|
|
@@ -22217,6 +22276,13 @@ var GetInitiativeResponse = object({
|
|
|
22217
22276
|
"riskId": string2().optional(),
|
|
22218
22277
|
"note": string2().max(getInitiativeResponseFollowUpsItemNoteMax).optional()
|
|
22219
22278
|
}).describe("An agreed next step: who does what by when (docs/risks-issues-follow-ups-concept-2026-10-19.md). assigneeUserId is a member of the org (an id that is not is dropped on save, the name kept); milestoneId and riskId name the same initiative's milestones and risks (a link that does not is cleared on save). Dates are yyyy-mm-dd.")).max(getInitiativeResponseFollowUpsMax).optional(),
|
|
22279
|
+
"qualityFactors": array(object({
|
|
22280
|
+
"factor": _enum2(["reliability", "security", "performance", "usability", "maintainability", "compatibility", "dataQuality"]),
|
|
22281
|
+
"required": _enum2(["low", "medium", "high"]).optional(),
|
|
22282
|
+
"state": number2().min(1).max(getInitiativeResponseQualityFactorsItemStateMax).optional(),
|
|
22283
|
+
"targetDate": string2().regex(getInitiativeResponseQualityFactorsItemTargetDateRegExp).optional(),
|
|
22284
|
+
"note": string2().max(getInitiativeResponseQualityFactorsItemNoteMax).optional()
|
|
22285
|
+
}).describe("The quality of the delivered solution on one factor (docs/quality-factors-concept-2026-10-21.md, after ISO/IEC 25010). One entry per factor; required is how much it matters, state (1-5) how well the solution meets it today. Never part of a score.")).max(getInitiativeResponseQualityFactorsMax).optional(),
|
|
22220
22286
|
"dependsOn": array(string2()).max(getInitiativeResponseDependsOnMax),
|
|
22221
22287
|
"requiredSkills": array(object({
|
|
22222
22288
|
"skillId": string2(),
|
|
@@ -22378,6 +22444,10 @@ var updateInitiativeBodyOneFollowUpsItemDueDateRegExp = new RegExp("^\\d{4}-\\d{
|
|
|
22378
22444
|
var updateInitiativeBodyOneFollowUpsItemDoneDateRegExp = new RegExp("^\\d{4}-\\d{2}-\\d{2}$");
|
|
22379
22445
|
var updateInitiativeBodyOneFollowUpsItemNoteMax = 1e4;
|
|
22380
22446
|
var updateInitiativeBodyOneFollowUpsMax = 50;
|
|
22447
|
+
var updateInitiativeBodyOneQualityFactorsItemStateMax = 5;
|
|
22448
|
+
var updateInitiativeBodyOneQualityFactorsItemTargetDateRegExp = new RegExp("^\\d{4}-\\d{2}-\\d{2}$");
|
|
22449
|
+
var updateInitiativeBodyOneQualityFactorsItemNoteMax = 2e3;
|
|
22450
|
+
var updateInitiativeBodyOneQualityFactorsMax = 7;
|
|
22381
22451
|
var updateInitiativeBodyOneDependsOnMax = 1e3;
|
|
22382
22452
|
var updateInitiativeBodyOneRequiredSkillsItemRunFteMin = 0;
|
|
22383
22453
|
var updateInitiativeBodyOneRequiredSkillsMax = 1e3;
|
|
@@ -22610,6 +22680,13 @@ var UpdateInitiativeBody = object({
|
|
|
22610
22680
|
"riskId": string2().optional(),
|
|
22611
22681
|
"note": string2().max(updateInitiativeBodyOneFollowUpsItemNoteMax).optional()
|
|
22612
22682
|
}).describe("An agreed next step: who does what by when (docs/risks-issues-follow-ups-concept-2026-10-19.md). assigneeUserId is a member of the org (an id that is not is dropped on save, the name kept); milestoneId and riskId name the same initiative's milestones and risks (a link that does not is cleared on save). Dates are yyyy-mm-dd.")).max(updateInitiativeBodyOneFollowUpsMax).optional(),
|
|
22683
|
+
"qualityFactors": array(object({
|
|
22684
|
+
"factor": _enum2(["reliability", "security", "performance", "usability", "maintainability", "compatibility", "dataQuality"]),
|
|
22685
|
+
"required": _enum2(["low", "medium", "high"]).optional(),
|
|
22686
|
+
"state": number2().min(1).max(updateInitiativeBodyOneQualityFactorsItemStateMax).optional(),
|
|
22687
|
+
"targetDate": string2().regex(updateInitiativeBodyOneQualityFactorsItemTargetDateRegExp).optional(),
|
|
22688
|
+
"note": string2().max(updateInitiativeBodyOneQualityFactorsItemNoteMax).optional()
|
|
22689
|
+
}).describe("The quality of the delivered solution on one factor (docs/quality-factors-concept-2026-10-21.md, after ISO/IEC 25010). One entry per factor; required is how much it matters, state (1-5) how well the solution meets it today. Never part of a score.")).max(updateInitiativeBodyOneQualityFactorsMax).optional(),
|
|
22613
22690
|
"dependsOn": array(string2()).max(updateInitiativeBodyOneDependsOnMax),
|
|
22614
22691
|
"requiredSkills": array(object({
|
|
22615
22692
|
"skillId": string2(),
|
|
@@ -22776,6 +22853,10 @@ var updateInitiativeResponseFollowUpsItemDueDateRegExp = new RegExp("^\\d{4}-\\d
|
|
|
22776
22853
|
var updateInitiativeResponseFollowUpsItemDoneDateRegExp = new RegExp("^\\d{4}-\\d{2}-\\d{2}$");
|
|
22777
22854
|
var updateInitiativeResponseFollowUpsItemNoteMax = 1e4;
|
|
22778
22855
|
var updateInitiativeResponseFollowUpsMax = 50;
|
|
22856
|
+
var updateInitiativeResponseQualityFactorsItemStateMax = 5;
|
|
22857
|
+
var updateInitiativeResponseQualityFactorsItemTargetDateRegExp = new RegExp("^\\d{4}-\\d{2}-\\d{2}$");
|
|
22858
|
+
var updateInitiativeResponseQualityFactorsItemNoteMax = 2e3;
|
|
22859
|
+
var updateInitiativeResponseQualityFactorsMax = 7;
|
|
22779
22860
|
var updateInitiativeResponseDependsOnMax = 1e3;
|
|
22780
22861
|
var updateInitiativeResponseRequiredSkillsItemRunFteMin = 0;
|
|
22781
22862
|
var updateInitiativeResponseRequiredSkillsMax = 1e3;
|
|
@@ -23008,6 +23089,13 @@ var UpdateInitiativeResponse = object({
|
|
|
23008
23089
|
"riskId": string2().optional(),
|
|
23009
23090
|
"note": string2().max(updateInitiativeResponseFollowUpsItemNoteMax).optional()
|
|
23010
23091
|
}).describe("An agreed next step: who does what by when (docs/risks-issues-follow-ups-concept-2026-10-19.md). assigneeUserId is a member of the org (an id that is not is dropped on save, the name kept); milestoneId and riskId name the same initiative's milestones and risks (a link that does not is cleared on save). Dates are yyyy-mm-dd.")).max(updateInitiativeResponseFollowUpsMax).optional(),
|
|
23092
|
+
"qualityFactors": array(object({
|
|
23093
|
+
"factor": _enum2(["reliability", "security", "performance", "usability", "maintainability", "compatibility", "dataQuality"]),
|
|
23094
|
+
"required": _enum2(["low", "medium", "high"]).optional(),
|
|
23095
|
+
"state": number2().min(1).max(updateInitiativeResponseQualityFactorsItemStateMax).optional(),
|
|
23096
|
+
"targetDate": string2().regex(updateInitiativeResponseQualityFactorsItemTargetDateRegExp).optional(),
|
|
23097
|
+
"note": string2().max(updateInitiativeResponseQualityFactorsItemNoteMax).optional()
|
|
23098
|
+
}).describe("The quality of the delivered solution on one factor (docs/quality-factors-concept-2026-10-21.md, after ISO/IEC 25010). One entry per factor; required is how much it matters, state (1-5) how well the solution meets it today. Never part of a score.")).max(updateInitiativeResponseQualityFactorsMax).optional(),
|
|
23011
23099
|
"dependsOn": array(string2()).max(updateInitiativeResponseDependsOnMax),
|
|
23012
23100
|
"requiredSkills": array(object({
|
|
23013
23101
|
"skillId": string2(),
|
|
@@ -23286,6 +23374,16 @@ var RISK_CATEGORIES = [
|
|
|
23286
23374
|
var RISK_STATUSES = ["open", "mitigating", "closed", "occurred"];
|
|
23287
23375
|
var MAX_RISKS_PER_INITIATIVE = 50;
|
|
23288
23376
|
var MAX_FOLLOW_UPS_PER_INITIATIVE = 50;
|
|
23377
|
+
var QUALITY_FACTORS = [
|
|
23378
|
+
"reliability",
|
|
23379
|
+
"security",
|
|
23380
|
+
"performance",
|
|
23381
|
+
"usability",
|
|
23382
|
+
"maintainability",
|
|
23383
|
+
"compatibility",
|
|
23384
|
+
"dataQuality"
|
|
23385
|
+
];
|
|
23386
|
+
var QUALITY_REQUIRED_LEVELS = ["low", "medium", "high"];
|
|
23289
23387
|
var GATE_TRANSITIONS = [
|
|
23290
23388
|
{ key: "idea-pilot", from: "Idea", to: "Pilot" },
|
|
23291
23389
|
{ key: "pilot-scaling", from: "Pilot", to: "Scaling" },
|
|
@@ -23617,7 +23715,15 @@ var EN_DIGITAL = {
|
|
|
23617
23715
|
"method.audit.scope.governance": "<strong>Status</strong> and the four <strong>evaluation dimension scores</strong> (Value, Delivery Feasibility, Compliance & Operational Risk, Strategic Impact) with their confidence.",
|
|
23618
23716
|
"method.audit.scope.metadata": "<strong>All Core Metadata fields</strong> \u2014 name, business unit, owner, description, timeline, budget, vendor strategy, solution type, digital category and tags.",
|
|
23619
23717
|
"method.gates.enforced.intro": "Two governance fields are enforced as hard checks when the lifecycle status changes \u2014 active because the EU AI Act module is enabled for this workspace:",
|
|
23620
|
-
"method.gates.enforced.scope": "Both checks feed the EU AI Act readiness score in addition to the hard block."
|
|
23718
|
+
"method.gates.enforced.scope": "Both checks feed the EU AI Act readiness score in addition to the hard block.",
|
|
23719
|
+
// Quality factor help (docs/quality-factors-concept-2026-10-21.md).
|
|
23720
|
+
"quality.factor.reliability.help": "Does the service stay up and behave correctly? Availability against the agreed SLA, incident rate, recovery time after an outage, batch and job success.",
|
|
23721
|
+
"quality.factor.security.help": "Is the platform protected? Access control and roles, protection of customer data, secure interfaces, pen test or security review done, findings closed.",
|
|
23722
|
+
"quality.factor.performance.help": "Is it fast enough at peak load? Page and API response times, throughput at month-end or campaign peaks, headroom to grow.",
|
|
23723
|
+
"quality.factor.usability.help": "Can customers and staff use it without help? Task completion, adoption, accessibility, number of support calls.",
|
|
23724
|
+
"quality.factor.maintainability.help": "Can the team change and run it safely? Code quality, automated tests and deployment, documentation, technical debt, dependency on single experts.",
|
|
23725
|
+
"quality.factor.compatibility.help": "Does it fit the landscape? Interfaces to core and neighbouring systems, standards (APIs, formats), vendor lock-in.",
|
|
23726
|
+
"quality.factor.dataQuality.help": "Is the data the service uses fit for purpose? Accuracy, completeness, timeliness and lineage of master and transaction data (ISO/IEC 25012). The Data & Process Quality slider rates feasibility before delivery; this rates the delivered state."
|
|
23621
23727
|
};
|
|
23622
23728
|
var DE_DIGITAL = {
|
|
23623
23729
|
"brand.app": "BICon Portfolio Steering",
|
|
@@ -23827,7 +23933,15 @@ var DE_DIGITAL = {
|
|
|
23827
23933
|
"method.audit.scope.governance": "<strong>Status</strong> und die vier <strong>Bewertungsdimensions-Scores</strong> (Wert, Liefer-Machbarkeit, Compliance & Betriebsrisiko, Strategischer Impact) mit ihrer Konfidenz.",
|
|
23828
23934
|
"method.audit.scope.metadata": "<strong>Alle Kern-Metadatenfelder</strong> \u2014 Name, Gesch\xE4ftseinheit, Verantwortliche, Beschreibung, Zeitplan, Budget, Anbieterstrategie, L\xF6sungstyp, Digital-Kategorie und Tags.",
|
|
23829
23935
|
"method.gates.enforced.intro": "Zwei Governance-Felder werden beim Wechsel des Lebenszyklus-Status als harte Pr\xFCfungen erzwungen \u2014 aktiv, weil das EU-AI-Act-Modul f\xFCr diesen Arbeitsbereich aktiviert ist:",
|
|
23830
|
-
"method.gates.enforced.scope": "Beide Pr\xFCfungen fliessen zus\xE4tzlich zum harten Block in den EU-AI-Act-Reifegrad ein."
|
|
23936
|
+
"method.gates.enforced.scope": "Beide Pr\xFCfungen fliessen zus\xE4tzlich zum harten Block in den EU-AI-Act-Reifegrad ein.",
|
|
23937
|
+
// Quality factor help (docs/quality-factors-concept-2026-10-21.md).
|
|
23938
|
+
"quality.factor.reliability.help": "Bleibt der Dienst verf\xFCgbar und arbeitet korrekt? Verf\xFCgbarkeit gegen\xFCber dem vereinbarten SLA, St\xF6rungsrate, Wiederherstellungszeit nach Ausf\xE4llen, Erfolg von Batches und Jobs.",
|
|
23939
|
+
"quality.factor.security.help": "Ist die Plattform gesch\xFCtzt? Zugriffsschutz und Rollen, Schutz der Kundendaten, sichere Schnittstellen, Pentest oder Sicherheitspr\xFCfung erfolgt, Befunde behoben.",
|
|
23940
|
+
"quality.factor.performance.help": "Ist es bei Spitzenlast schnell genug? Antwortzeiten von Seiten und APIs, Durchsatz am Monatsende oder bei Kampagnen, Reserve f\xFCr Wachstum.",
|
|
23941
|
+
"quality.factor.usability.help": "K\xF6nnen Kundinnen, Kunden und Mitarbeitende es ohne Hilfe nutzen? Aufgabenerfolg, Akzeptanz, Barrierefreiheit, Zahl der Supportanfragen.",
|
|
23942
|
+
"quality.factor.maintainability.help": "Kann das Team es sicher \xE4ndern und betreiben? Codequalit\xE4t, automatisierte Tests und Deployments, Dokumentation, technische Schulden, Abh\xE4ngigkeit von einzelnen Fachleuten.",
|
|
23943
|
+
"quality.factor.compatibility.help": "Passt es in die Landschaft? Schnittstellen zu Kern- und Nachbarsystemen, Standards (APIs, Formate), Anbieterabh\xE4ngigkeit.",
|
|
23944
|
+
"quality.factor.dataQuality.help": "Sind die Daten, die der Dienst nutzt, f\xFCr den Zweck geeignet? Richtigkeit, Vollst\xE4ndigkeit, Aktualit\xE4t und Herkunft von Stamm- und Bewegungsdaten (ISO/IEC 25012). Der Schieberegler Daten- und Prozessqualit\xE4t bewertet die Machbarkeit vor der Lieferung; hier wird der gelieferte Stand bewertet."
|
|
23831
23945
|
};
|
|
23832
23946
|
var EN_INNOVATION = {
|
|
23833
23947
|
"brand.app": "BICon Portfolio Steering",
|
|
@@ -24091,7 +24205,15 @@ var EN_INNOVATION = {
|
|
|
24091
24205
|
"method.audit.scope.governance": "<strong>Status</strong> and the four <strong>evaluation dimension scores</strong> (Value Potential, Validation & Evidence, Governance & Uncertainty, Strategic Impact) with their confidence.",
|
|
24092
24206
|
"method.audit.scope.metadata": "<strong>All Core Metadata fields</strong> \u2014 name, business unit, owner, description, timeline, budget, partner strategy, solution type, innovation category and tags.",
|
|
24093
24207
|
"method.gates.enforced.intro": "Two governance fields are enforced as hard checks when the lifecycle status changes \u2014 active because the EU AI Act module is enabled for this workspace:",
|
|
24094
|
-
"method.gates.enforced.scope": "Both checks feed the EU AI Act readiness score in addition to the hard block."
|
|
24208
|
+
"method.gates.enforced.scope": "Both checks feed the EU AI Act readiness score in addition to the hard block.",
|
|
24209
|
+
// Quality factor help (docs/quality-factors-concept-2026-10-21.md).
|
|
24210
|
+
"quality.factor.reliability.help": "Does the prototype or pilot work reliably enough for the next stage? Repeatable results, uptime during the trial, known failure modes.",
|
|
24211
|
+
"quality.factor.security.help": "Is what the pilot handles protected? Access to lab and trial data, protection of IP, a security review before real users or data come in.",
|
|
24212
|
+
"quality.factor.performance.help": "Will it hold at the next stage's scale? Throughput and cost per unit at pilot volume, a credible path to production volume. Often still Low required before Pilot.",
|
|
24213
|
+
"quality.factor.usability.help": "Can the intended users work with it? Feedback from trial users, task success, what must change before roll-out.",
|
|
24214
|
+
"quality.factor.maintainability.help": "Can someone other than the inventors run and change it? Documentation, reproducible build or protocol, technical debt taken on for speed. Often still Low required before Pilot.",
|
|
24215
|
+
"quality.factor.compatibility.help": "Does it fit what it must plug into? Interfaces to lab, production or partner systems, standards, lock-in to a partner.",
|
|
24216
|
+
"quality.factor.dataQuality.help": "Is the data behind the results fit for purpose? Accuracy, completeness and lineage of experimental and trial data (ISO/IEC 25012). The Quality of Evidence slider rates feasibility before delivery; this rates the delivered state."
|
|
24095
24217
|
};
|
|
24096
24218
|
var DE_INNOVATION = {
|
|
24097
24219
|
"brand.app": "BICon Portfolio Steering",
|
|
@@ -24355,7 +24477,15 @@ var DE_INNOVATION = {
|
|
|
24355
24477
|
"method.audit.scope.governance": "<strong>Status</strong> und die vier <strong>Bewertungsdimensions-Scores</strong> (Wertpotenzial, Validierung & Evidenz, Governance & Unsicherheit, Strategischer Impact) mit ihrer Konfidenz.",
|
|
24356
24478
|
"method.audit.scope.metadata": "<strong>Alle Kern-Metadatenfelder</strong> \u2014 Name, Gesch\xE4ftseinheit, Verantwortliche, Beschreibung, Zeitplan, Budget, Partnerstrategie, L\xF6sungstyp, Innovations-Kategorie und Tags.",
|
|
24357
24479
|
"method.gates.enforced.intro": "Zwei Governance-Felder werden beim Wechsel des Lebenszyklus-Status als harte Pr\xFCfungen erzwungen \u2014 aktiv, weil das EU-AI-Act-Modul f\xFCr diesen Arbeitsbereich aktiviert ist:",
|
|
24358
|
-
"method.gates.enforced.scope": "Beide Pr\xFCfungen fliessen zus\xE4tzlich zum harten Block in den EU-AI-Act-Reifegrad ein."
|
|
24480
|
+
"method.gates.enforced.scope": "Beide Pr\xFCfungen fliessen zus\xE4tzlich zum harten Block in den EU-AI-Act-Reifegrad ein.",
|
|
24481
|
+
// Quality factor help (docs/quality-factors-concept-2026-10-21.md).
|
|
24482
|
+
"quality.factor.reliability.help": "Funktioniert der Prototyp oder Pilot zuverl\xE4ssig genug f\xFCr die n\xE4chste Stufe? Wiederholbare Ergebnisse, Verf\xFCgbarkeit w\xE4hrend des Versuchs, bekannte Fehlerbilder.",
|
|
24483
|
+
"quality.factor.security.help": "Ist gesch\xFCtzt, was der Pilot verarbeitet? Zugriff auf Labor- und Versuchsdaten, Schutz des geistigen Eigentums, Sicherheitspr\xFCfung, bevor echte Nutzer oder Daten dazukommen.",
|
|
24484
|
+
"quality.factor.performance.help": "H\xE4lt es im Massstab der n\xE4chsten Stufe? Durchsatz und St\xFCckkosten im Pilotvolumen, ein glaubw\xFCrdiger Weg zum Produktionsvolumen. Vor dem Pilot oft noch niedrig gefordert.",
|
|
24485
|
+
"quality.factor.usability.help": "K\xF6nnen die vorgesehenen Nutzerinnen und Nutzer damit arbeiten? R\xFCckmeldungen aus dem Versuch, Aufgabenerfolg, was vor dem Rollout \xE4ndern muss.",
|
|
24486
|
+
"quality.factor.maintainability.help": "Kann jemand ausser den Erfinderinnen und Erfindern es betreiben und \xE4ndern? Dokumentation, reproduzierbarer Aufbau oder reproduzierbares Protokoll, technische Schulden zugunsten der Geschwindigkeit. Vor dem Pilot oft noch niedrig gefordert.",
|
|
24487
|
+
"quality.factor.compatibility.help": "Passt es zu dem, woran es angeschlossen werden muss? Schnittstellen zu Labor-, Produktions- oder Partnersystemen, Standards, Abh\xE4ngigkeit von einem Partner.",
|
|
24488
|
+
"quality.factor.dataQuality.help": "Sind die Daten hinter den Ergebnissen f\xFCr den Zweck geeignet? Richtigkeit, Vollst\xE4ndigkeit und Herkunft der Versuchs- und Testdaten (ISO/IEC 25012). Der Schieberegler Qualit\xE4t der Evidenz bewertet die Machbarkeit vor der Lieferung; hier wird der gelieferte Stand bewertet."
|
|
24359
24489
|
};
|
|
24360
24490
|
var DEMO_DIGITAL = {
|
|
24361
24491
|
"Advisor Copilot": {
|
|
@@ -25729,6 +25859,13 @@ var OPTIONAL_SECTIONS = [
|
|
|
25729
25859
|
de: "[{title, dueDate, assigneeName?, assigneeUserId?, doneDate?, milestoneId?, riskId?, note?}] \u2014 vereinbarte n\xE4chste Schritte: wer macht was bis wann (h\xF6chstens 50, Titel bis 300 Zeichen). assigneeName ist ein Freitext-Name; assigneeUserId nur, wenn der Benutzer eine Mitglieds-ID aus der App nennt (die App l\xF6scht eine ID, die kein Mitglied ist, der Name bleibt). milestoneId / riskId zeigen auf einen Meilenstein oder ein Risiko derselben Spezifikation \xFCber die dort vergebene id (z. B. \u201Em1\u201C, \u201Er1\u201C); der Composer pr\xE4gt die gespeicherten IDs und beh\xE4lt die Verkn\xFCpfung. Nach den offenen Schritten fragen, besonders je einem pro schwerem Risiko. Bei einem Update alle Pendenzen senden, die bleiben sollen: die Datei ersetzt die Liste."
|
|
25730
25860
|
}
|
|
25731
25861
|
},
|
|
25862
|
+
{
|
|
25863
|
+
key: "qualityFactors",
|
|
25864
|
+
hint: {
|
|
25865
|
+
en: "[{factor, required?, state?, targetDate?, note?}] \u2014 is the delivered solution good enough to run? One row per factor that was rated (after ISO/IEC 25010): reliability (availability, stable output), security, performance (speed, running cost at load), usability, maintainability, compatibility (interfaces, lock-in), dataQuality (the delivered data, not the feasibility slider). required low | medium | high = how much the factor matters here; state 1\u20135 as a whole number (1 unknown or poor, 2 weak, 3 adequate, 4 good, 5 proven in production); targetDate when a gap should be closed; note = evidence or an SLA figure. A gap = required high with state 1\u20132, or required medium with state 1; nothing here changes a score. Ask for the factors that matter once the initiative is at Pilot or later. On an update, send every rating the initiative keeps: the file replaces the list.",
|
|
25866
|
+
de: "[{factor, required?, state?, targetDate?, note?}] \u2014 ist die gelieferte L\xF6sung gut genug f\xFCr den Betrieb? Eine Zeile je bewertetem Faktor (nach ISO/IEC 25010): reliability (Verf\xFCgbarkeit, stabile Ergebnisse), security, performance (Tempo, Betriebskosten unter Last), usability, maintainability, compatibility (Schnittstellen, Anbieterabh\xE4ngigkeit), dataQuality (die gelieferten Daten, nicht der Machbarkeits-Regler). required low | medium | high = wie stark der Faktor hier z\xE4hlt; state 1\u20135 als ganze Zahl (1 unbekannt oder ungen\xFCgend, 2 schwach, 3 ausreichend, 4 gut, 5 im Betrieb bew\xE4hrt); targetDate, bis wann eine L\xFCcke geschlossen sein soll; note = Nachweis oder SLA-Wert. Eine L\xFCcke = gefordert hoch mit Stand 1\u20132 oder gefordert mittel mit Stand 1; nichts davon \xE4ndert einen Score. Ab Pilot nach den Faktoren fragen, die z\xE4hlen. Bei einem Update alle Bewertungen senden, die bleiben sollen: die Datei ersetzt die Liste."
|
|
25867
|
+
}
|
|
25868
|
+
},
|
|
25732
25869
|
{
|
|
25733
25870
|
key: "capabilityImpacts",
|
|
25734
25871
|
hint: {
|
|
@@ -25822,7 +25959,7 @@ function buildSchemaDescriptor(sectionShapes) {
|
|
|
25822
25959
|
optionalSections: OPTIONAL_SECTIONS,
|
|
25823
25960
|
// Exact shapes of the object/array sections (aiAct, economics, actuals,
|
|
25824
25961
|
// decisionLog, statusReports, requiredSkills, capabilityImpacts,
|
|
25825
|
-
// milestones, risks, followUps). Keys ending in "?" are
|
|
25962
|
+
// milestones, risks, followUps, qualityFactors). Keys ending in "?" are
|
|
25826
25963
|
// optional; "enum[a|b]" lists the allowed values; "<key>" marks a map.
|
|
25827
25964
|
// Pass these sections as JSON objects/arrays. Any key not listed is
|
|
25828
25965
|
// rejected with its path.
|
|
@@ -25996,7 +26133,7 @@ var parseIfString = (v) => typeof v === "string" ? tryParseJson(v) : v;
|
|
|
25996
26133
|
function wireSection(inner, description) {
|
|
25997
26134
|
return external_exports.preprocess(parseIfString, deepStrict(inner)).describe(description);
|
|
25998
26135
|
}
|
|
25999
|
-
var SECTION_KEYS = ["aiAct", "economics", "actuals", "decisionLog", "statusReports", "requiredSkills", "capabilityImpacts", "milestones", "risks", "followUps"];
|
|
26136
|
+
var SECTION_KEYS = ["aiAct", "economics", "actuals", "decisionLog", "statusReports", "requiredSkills", "capabilityImpacts", "milestones", "risks", "followUps", "qualityFactors"];
|
|
26000
26137
|
var SECTION_SCHEMAS = Object.fromEntries(
|
|
26001
26138
|
SECTION_KEYS.map((k) => [k, contractSection(k)])
|
|
26002
26139
|
);
|
|
@@ -26010,6 +26147,7 @@ var SECTION_DESCRIPTIONS = {
|
|
|
26010
26147
|
milestones: `Milestones (max 30, title \u2264300 chars): baselineDate = the plan, forecastDate = today's expectation (omit = baseline), doneDate = reached. Dates dd/mm/yyyy or yyyy-mm-dd; ids minted \u2014 an id you give (e.g. "m1") is a label a follow-up's milestoneId can point at. An update replaces the list: send all.`,
|
|
26011
26148
|
risks: `Risks (max 50, title \u2264300 chars): category delivery|vendor|data|people|regulatory|technical|financial; probability and impact 1\u20135 as whole numbers (score = product); status open|mitigating|closed|occurred (occurred = an issue, with occurredDate); residualProbability + residualImpact = the expectation after mitigation (both or neither); owner (free text), mitigation (the plan), raisedDate, reviewDate, note. Dates dd/mm/yyyy or yyyy-mm-dd; ids minted \u2014 an id you give (e.g. "r1") is a label a follow-up's riskId can point at. An update replaces the list: send all.`,
|
|
26012
26149
|
followUps: "Follow-ups (max 50, title \u2264300 chars): who does what by when \u2014 title, dueDate, assigneeName (free text; assigneeUserId only when the user gives a member id from the app), doneDate once done, note; milestoneId / riskId = the id label of a milestone or risk in THIS spec (the Composer keeps the link when it mints the stored ids). Dates dd/mm/yyyy or yyyy-mm-dd; ids minted. An update replaces the list: send all.",
|
|
26150
|
+
qualityFactors: "Quality factors of the delivered solution (one row per factor, at most 7): factor reliability|security|performance|usability|maintainability|compatibility|dataQuality; required low|medium|high; state 1\u20135 as a whole number (1 unknown or poor \u2026 5 proven in production); targetDate; note (evidence or SLA). Leave out a factor nobody rated. An update replaces the list: send all.",
|
|
26013
26151
|
requiredSkills: "Required skills [{skillId, fte, runFte?}] \u2014 fte is the build-phase demand, the optional runFte the run-team demand after the planned end (absent = build fte continues, 0 = no run demand). skillId must be the id of a skill that exists in the organisation. With the portfolio context the Composer validates it (read_portfolio_context \u2192 lists.skills gives id + name); without it, leave the list empty and link skills in the app."
|
|
26014
26152
|
};
|
|
26015
26153
|
var contextWire = () => external_exports.preprocess(parseIfString, external_exports.union([external_exports.string().regex(/^ctx_[0-9a-f]{16}$/, "a contextId looks like ctx_<16 hex>"), external_exports.object({}).passthrough()]));
|
|
@@ -26059,6 +26197,7 @@ var compactSpecSchema = external_exports.object({
|
|
|
26059
26197
|
milestones: wireSection(SECTION_SCHEMAS.milestones, SECTION_DESCRIPTIONS.milestones).optional(),
|
|
26060
26198
|
risks: wireSection(SECTION_SCHEMAS.risks, SECTION_DESCRIPTIONS.risks).optional(),
|
|
26061
26199
|
followUps: wireSection(SECTION_SCHEMAS.followUps, SECTION_DESCRIPTIONS.followUps).optional(),
|
|
26200
|
+
qualityFactors: wireSection(SECTION_SCHEMAS.qualityFactors, SECTION_DESCRIPTIONS.qualityFactors).optional(),
|
|
26062
26201
|
aiRiskTier: external_exports.enum(AI_RISK_TIERS2).optional(),
|
|
26063
26202
|
hitlPosture: external_exports.enum(HITL_POSTURES2).optional(),
|
|
26064
26203
|
dpiaStatus: external_exports.enum(DPIA_STATUSES2).optional(),
|
|
@@ -26123,6 +26262,181 @@ function initiativeQuadrant(i) {
|
|
|
26123
26262
|
);
|
|
26124
26263
|
}
|
|
26125
26264
|
|
|
26265
|
+
// ../../initiative-model/src/portfolio-scope.ts
|
|
26266
|
+
function isLiveInitiative(i) {
|
|
26267
|
+
return !i.archived && i.status !== "Retired";
|
|
26268
|
+
}
|
|
26269
|
+
function isInFlightInitiative(i) {
|
|
26270
|
+
return isLiveInitiative(i) && i.status !== "On Hold";
|
|
26271
|
+
}
|
|
26272
|
+
|
|
26273
|
+
// ../../initiative-model/src/quality.ts
|
|
26274
|
+
var QUALITY_NOTE_MAX = 2e3;
|
|
26275
|
+
var QUALITY_STATE_MIN = 1;
|
|
26276
|
+
var QUALITY_STATE_MAX = 5;
|
|
26277
|
+
function isQualityGap(r) {
|
|
26278
|
+
if (r.state === void 0) return false;
|
|
26279
|
+
if (r.required === "high") return r.state <= 2;
|
|
26280
|
+
if (r.required === "medium") return r.state <= 1;
|
|
26281
|
+
return false;
|
|
26282
|
+
}
|
|
26283
|
+
var REQUIRED_RANK = { high: 0, medium: 1, low: 2 };
|
|
26284
|
+
var FACTOR_RANK = new Map(QUALITY_FACTORS.map((f, i) => [f, i]));
|
|
26285
|
+
function weaker(a, b) {
|
|
26286
|
+
const ra = a.required ? REQUIRED_RANK[a.required] : 3;
|
|
26287
|
+
const rb = b.required ? REQUIRED_RANK[b.required] : 3;
|
|
26288
|
+
return ra - rb || (a.state ?? 99) - (b.state ?? 99) || (FACTOR_RANK.get(a.factor) ?? 99) - (FACTOR_RANK.get(b.factor) ?? 99);
|
|
26289
|
+
}
|
|
26290
|
+
function qualityGaps(list) {
|
|
26291
|
+
return (list ?? []).filter(isQualityGap).sort(weaker);
|
|
26292
|
+
}
|
|
26293
|
+
function summariseQuality(list) {
|
|
26294
|
+
const rows = list ?? [];
|
|
26295
|
+
const gaps = qualityGaps(rows);
|
|
26296
|
+
const withState = rows.filter((r) => r.state !== void 0).sort((a, b) => a.state - b.state || weaker(a, b));
|
|
26297
|
+
return {
|
|
26298
|
+
rated: rows.filter((r) => r.required !== void 0 || r.state !== void 0).length,
|
|
26299
|
+
gaps: gaps.length,
|
|
26300
|
+
weakest: gaps[0] ?? withState[0]
|
|
26301
|
+
};
|
|
26302
|
+
}
|
|
26303
|
+
var QUALITY_SCALING_STATUSES = ["Scaling", "In Production"];
|
|
26304
|
+
function scalingQualityGaps(i) {
|
|
26305
|
+
if (!QUALITY_SCALING_STATUSES.includes(i.status)) return [];
|
|
26306
|
+
return qualityGaps(i.qualityFactors).filter((r) => r.required === "high");
|
|
26307
|
+
}
|
|
26308
|
+
function hasQualityGaps(i) {
|
|
26309
|
+
return (i.qualityFactors ?? []).some(isQualityGap);
|
|
26310
|
+
}
|
|
26311
|
+
var BOARD_QUALITY_ROWS = 10;
|
|
26312
|
+
var STAGE_RANK = { "In Production": 0, Scaling: 1, Pilot: 2, Idea: 3 };
|
|
26313
|
+
function boardQualitySlice(initiatives) {
|
|
26314
|
+
const scope = initiatives.filter((i) => isInFlightInitiative(i) && summariseQuality(i.qualityFactors).rated > 0);
|
|
26315
|
+
if (scope.length === 0) return void 0;
|
|
26316
|
+
const rows = [];
|
|
26317
|
+
const perFactor = /* @__PURE__ */ new Map();
|
|
26318
|
+
let withGaps = 0;
|
|
26319
|
+
let scaling = 0;
|
|
26320
|
+
for (const i of scope) {
|
|
26321
|
+
const gaps = qualityGaps(i.qualityFactors);
|
|
26322
|
+
if (gaps.length === 0) continue;
|
|
26323
|
+
withGaps += 1;
|
|
26324
|
+
if (scalingQualityGaps(i).length > 0) scaling += 1;
|
|
26325
|
+
for (const g of gaps) {
|
|
26326
|
+
perFactor.set(g.factor, (perFactor.get(g.factor) ?? 0) + 1);
|
|
26327
|
+
rows.push({
|
|
26328
|
+
initiativeId: i.id,
|
|
26329
|
+
initiativeName: i.name,
|
|
26330
|
+
status: i.status,
|
|
26331
|
+
factor: g.factor,
|
|
26332
|
+
required: g.required,
|
|
26333
|
+
state: g.state,
|
|
26334
|
+
...g.targetDate ? { targetDate: g.targetDate } : {}
|
|
26335
|
+
});
|
|
26336
|
+
}
|
|
26337
|
+
}
|
|
26338
|
+
rows.sort((a, b) => REQUIRED_RANK[a.required] - REQUIRED_RANK[b.required] || (STAGE_RANK[a.status] ?? 9) - (STAGE_RANK[b.status] ?? 9) || a.state - b.state || a.initiativeName.localeCompare(b.initiativeName) || (FACTOR_RANK.get(a.factor) ?? 99) - (FACTOR_RANK.get(b.factor) ?? 99));
|
|
26339
|
+
const byFactor = QUALITY_FACTORS.filter((f) => perFactor.has(f)).map((f) => ({ factor: f, initiatives: perFactor.get(f) })).sort((a, b) => b.initiatives - a.initiatives);
|
|
26340
|
+
return {
|
|
26341
|
+
gaps: rows.slice(0, BOARD_QUALITY_ROWS),
|
|
26342
|
+
gapCount: rows.length,
|
|
26343
|
+
ratedInitiativeCount: scope.length,
|
|
26344
|
+
initiativesWithGaps: withGaps,
|
|
26345
|
+
scalingWithGaps: scaling,
|
|
26346
|
+
byFactor
|
|
26347
|
+
};
|
|
26348
|
+
}
|
|
26349
|
+
function hasContent(r) {
|
|
26350
|
+
return r.required !== void 0 || r.state !== void 0 || !!r.targetDate || !!r.note;
|
|
26351
|
+
}
|
|
26352
|
+
function canonicalQualityFactors(list) {
|
|
26353
|
+
const byFactor = /* @__PURE__ */ new Map();
|
|
26354
|
+
for (const r of list) if (!byFactor.has(r.factor)) byFactor.set(r.factor, r);
|
|
26355
|
+
return QUALITY_FACTORS.flatMap((factor) => {
|
|
26356
|
+
const r = byFactor.get(factor);
|
|
26357
|
+
if (!r || !hasContent(r)) return [];
|
|
26358
|
+
return [{
|
|
26359
|
+
factor,
|
|
26360
|
+
...r.required ? { required: r.required } : {},
|
|
26361
|
+
...r.state !== void 0 ? { state: r.state } : {},
|
|
26362
|
+
...r.targetDate ? { targetDate: r.targetDate } : {},
|
|
26363
|
+
...r.note ? { note: r.note } : {}
|
|
26364
|
+
}];
|
|
26365
|
+
});
|
|
26366
|
+
}
|
|
26367
|
+
var squash = (v) => v.toLowerCase().replace(/[^a-z]/g, "");
|
|
26368
|
+
var FACTOR_BY_NAME = new Map(QUALITY_FACTORS.map((f) => [squash(f), f]));
|
|
26369
|
+
var REQUIRED_BY_NAME = new Map([
|
|
26370
|
+
...QUALITY_REQUIRED_LEVELS.map((l) => [l, l]),
|
|
26371
|
+
["l", "low"],
|
|
26372
|
+
["m", "medium"],
|
|
26373
|
+
["h", "high"],
|
|
26374
|
+
["niedrig", "low"],
|
|
26375
|
+
["mittel", "medium"],
|
|
26376
|
+
["hoch", "high"]
|
|
26377
|
+
]);
|
|
26378
|
+
function stateOf(v) {
|
|
26379
|
+
const n = typeof v === "number" ? v : typeof v === "string" && v.trim() ? Number(v.trim()) : NaN;
|
|
26380
|
+
if (!Number.isFinite(n)) return void 0;
|
|
26381
|
+
const r = Math.round(n);
|
|
26382
|
+
return r >= QUALITY_STATE_MIN && r <= QUALITY_STATE_MAX ? r : void 0;
|
|
26383
|
+
}
|
|
26384
|
+
function sanitiseQualityFactors(raw, idx, warnings) {
|
|
26385
|
+
if (raw === void 0 || raw === null) return [];
|
|
26386
|
+
if (!Array.isArray(raw)) {
|
|
26387
|
+
warnings.push(`Initiative #${idx + 1} qualityFactors is not an array; skipped.`);
|
|
26388
|
+
return [];
|
|
26389
|
+
}
|
|
26390
|
+
const out = [];
|
|
26391
|
+
const seen = /* @__PURE__ */ new Set();
|
|
26392
|
+
raw.forEach((entry, eIdx) => {
|
|
26393
|
+
if (!entry || typeof entry !== "object") return;
|
|
26394
|
+
const e = entry;
|
|
26395
|
+
const where = `Initiative #${idx + 1} quality factor #${eIdx + 1}`;
|
|
26396
|
+
const factor = typeof e.factor === "string" ? FACTOR_BY_NAME.get(squash(e.factor)) : void 0;
|
|
26397
|
+
if (!factor) {
|
|
26398
|
+
warnings.push(`${where}: factor "${String(e.factor ?? "")}" is unknown (expected ${QUALITY_FACTORS.join(", ")}); skipped.`);
|
|
26399
|
+
return;
|
|
26400
|
+
}
|
|
26401
|
+
const given = (v) => v !== void 0 && v !== null && v !== "";
|
|
26402
|
+
const required2 = typeof e.required === "string" ? REQUIRED_BY_NAME.get(e.required.trim().toLowerCase()) : void 0;
|
|
26403
|
+
if (given(e.required) && !required2) {
|
|
26404
|
+
warnings.push(`${where}: required level "${String(e.required)}" is unknown (expected low, medium or high); cleared.`);
|
|
26405
|
+
}
|
|
26406
|
+
const state = stateOf(e.state);
|
|
26407
|
+
if (given(e.state) && state === void 0) {
|
|
26408
|
+
warnings.push(`${where}: state "${String(e.state)}" is not a whole number from 1 to 5; cleared.`);
|
|
26409
|
+
}
|
|
26410
|
+
let targetDate;
|
|
26411
|
+
if (given(e.targetDate) && typeof e.targetDate !== "string") {
|
|
26412
|
+
warnings.push(`${where}: targetDate is not a date text (use dd/mm/yyyy or yyyy-mm-dd); cleared.`);
|
|
26413
|
+
} else if (typeof e.targetDate === "string" && e.targetDate.trim()) {
|
|
26414
|
+
const d = normaliseDateCell(e.targetDate);
|
|
26415
|
+
if (d && isCalendarDate(d)) targetDate = d;
|
|
26416
|
+
else warnings.push(`${where}: targetDate "${e.targetDate}" is not a recognised date (use dd/mm/yyyy or yyyy-mm-dd); cleared.`);
|
|
26417
|
+
}
|
|
26418
|
+
const note = typeof e.note === "string" && e.note.trim() ? e.note.slice(0, QUALITY_NOTE_MAX) : void 0;
|
|
26419
|
+
if (note && e.note.length > QUALITY_NOTE_MAX) {
|
|
26420
|
+
warnings.push(`${where}: the note is longer than ${QUALITY_NOTE_MAX} characters; cut.`);
|
|
26421
|
+
}
|
|
26422
|
+
const rating = {
|
|
26423
|
+
factor,
|
|
26424
|
+
...required2 ? { required: required2 } : {},
|
|
26425
|
+
...state !== void 0 ? { state } : {},
|
|
26426
|
+
...targetDate ? { targetDate } : {},
|
|
26427
|
+
...note ? { note } : {}
|
|
26428
|
+
};
|
|
26429
|
+
if (!hasContent(rating)) return;
|
|
26430
|
+
if (seen.has(factor)) {
|
|
26431
|
+
warnings.push(`${where}: ${factor} is rated twice; the later row was dropped.`);
|
|
26432
|
+
return;
|
|
26433
|
+
}
|
|
26434
|
+
seen.add(factor);
|
|
26435
|
+
out.push(rating);
|
|
26436
|
+
});
|
|
26437
|
+
return canonicalQualityFactors(out);
|
|
26438
|
+
}
|
|
26439
|
+
|
|
26126
26440
|
// src/context.ts
|
|
26127
26441
|
import { createHash } from "node:crypto";
|
|
26128
26442
|
|
|
@@ -26565,6 +26879,21 @@ function assembleInitiative(input2, now = /* @__PURE__ */ new Date()) {
|
|
|
26565
26879
|
warnings.push(`followUps.${i}.assigneeUserId: the Composer cannot verify workspace members, and the app clears an id that is not a member of the workspace \u2014 send assigneeName too, so the follow-up keeps its assignee either way.`);
|
|
26566
26880
|
}
|
|
26567
26881
|
}
|
|
26882
|
+
const quality = spec.qualityFactors ?? [];
|
|
26883
|
+
const seenFactors = /* @__PURE__ */ new Set();
|
|
26884
|
+
for (const [i, q] of quality.entries()) {
|
|
26885
|
+
const blankNote = q.note !== void 0 && !q.note.trim();
|
|
26886
|
+
const empty = q.required === void 0 && q.state === void 0 && q.targetDate === void 0 && (q.note === void 0 || blankNote);
|
|
26887
|
+
if (empty) {
|
|
26888
|
+
errors.push(`qualityFactors.${i}: ${q.factor} has nothing set \u2014 the app drops an empty row; leave the factor out.`);
|
|
26889
|
+
continue;
|
|
26890
|
+
}
|
|
26891
|
+
if (seenFactors.has(q.factor)) errors.push(`qualityFactors.${i}.factor: ${q.factor} is rated twice \u2014 send one row per factor.`);
|
|
26892
|
+
seenFactors.add(q.factor);
|
|
26893
|
+
if (q.state !== void 0 && !Number.isInteger(q.state)) errors.push(`qualityFactors.${i}.state: must be a whole number 1\u20135 (got ${q.state}).`);
|
|
26894
|
+
if (q.targetDate !== void 0 && !isCalendarDate(q.targetDate)) errors.push(`qualityFactors.${i}.targetDate: "${q.targetDate}" is not a real calendar date \u2014 dd/mm/yyyy or yyyy-mm-dd`);
|
|
26895
|
+
if (blankNote) errors.push(`qualityFactors.${i}.note: must not be blank \u2014 leave it out instead.`);
|
|
26896
|
+
}
|
|
26568
26897
|
const norm3 = (v) => (v ?? "").trim().toLowerCase();
|
|
26569
26898
|
const others = context?.initiatives ?? [];
|
|
26570
26899
|
const ext = norm3(spec.externalId);
|
|
@@ -26579,12 +26908,14 @@ function assembleInitiative(input2, now = /* @__PURE__ */ new Date()) {
|
|
|
26579
26908
|
for (const [key, what] of [
|
|
26580
26909
|
["milestones", "milestones"],
|
|
26581
26910
|
["risks", "risks"],
|
|
26582
|
-
["followUps", "follow-ups"]
|
|
26911
|
+
["followUps", "follow-ups"],
|
|
26912
|
+
["qualityFactors", "quality ratings"]
|
|
26583
26913
|
]) {
|
|
26584
26914
|
if (spec[key] === void 0) {
|
|
26585
26915
|
warnings.push(`${key}: none sent, and the app will offer this file as an update \u2014 it replaces the initiative's ${what}, so any the app holds are removed. Send the full list to keep them.`);
|
|
26586
26916
|
}
|
|
26587
26917
|
}
|
|
26918
|
+
warnings.push("gateEvidence: the Composer cannot carry stage-gate evidence, and the app will offer this file as an update \u2014 it replaces the record, so the initiative's ticked gate items and a gate bypass are cleared. Re-tick them in the app after the import.");
|
|
26588
26919
|
}
|
|
26589
26920
|
let businessUnit = spec.businessUnit;
|
|
26590
26921
|
let aiCategory = spec.aiCategory;
|
|
@@ -26657,6 +26988,9 @@ function assembleInitiative(input2, now = /* @__PURE__ */ new Date()) {
|
|
|
26657
26988
|
milestones: mintedMilestones,
|
|
26658
26989
|
risks: mintedRisks,
|
|
26659
26990
|
followUps: mintedFollowUps,
|
|
26991
|
+
// The order and key order every writer stores (the leaf's canonical form);
|
|
26992
|
+
// the note as typed, as the app and the API keep it.
|
|
26993
|
+
...spec.qualityFactors !== void 0 ? { qualityFactors: canonicalQualityFactors(spec.qualityFactors) } : {},
|
|
26660
26994
|
aiAct: aiAct ? { ...aiAct, ...aiAct.incidents ? { incidents: mintNestedIds(aiAct.incidents, stamp, true) } : {} } : void 0,
|
|
26661
26995
|
createdAt: stamp,
|
|
26662
26996
|
updatedAt: stamp
|
|
@@ -26694,7 +27028,7 @@ var IMPORT_INSTRUCTIONS = [
|
|
|
26694
27028
|
"2. In the BICon app (signed in to the target organisation), open Import/Export.",
|
|
26695
27029
|
"3. If the initiative carries EU-AI-Act data, make sure the organisation's EU-AI-Act module is switched on (Settings \u203A AI Act) \u2014 the app strips AI-Act fields while it is off.",
|
|
26696
27030
|
"4. In the 'Single initiative' card, drop the file (or Browse).",
|
|
26697
|
-
"5. Review the preview \u2014 it should show 0 warnings \u2014 then choose 'Add as new' (or 'Update' when the file carries an existing initiative's id, Initiative ID or name \u2014 the app keeps that initiative's id and creation date, and REPLACES everything else with this file, decision log, status reports, milestones, risks
|
|
27031
|
+
"5. Review the preview \u2014 it should show 0 warnings \u2014 then choose 'Add as new' (or 'Update' when the file carries an existing initiative's id, Initiative ID or name \u2014 the app keeps that initiative's id and creation date, and REPLACES everything else with this file, decision log, status reports, milestones, risks, follow-ups and quality ratings included) and Confirm.",
|
|
26698
27032
|
"6. The initiative appears in the Inventory with the derived dimension scores."
|
|
26699
27033
|
].join("\n");
|
|
26700
27034
|
|
|
@@ -27055,7 +27389,6 @@ var RULE_CATEGORY_NAME = {
|
|
|
27055
27389
|
};
|
|
27056
27390
|
var UNCATEGORISED_CATEGORY = "Uncategorised / Tactical";
|
|
27057
27391
|
var CATEGORY_NAME_MAX = 60;
|
|
27058
|
-
var RULES_ORDER_REV = 2;
|
|
27059
27392
|
var bound = (min, max) => ({
|
|
27060
27393
|
...min !== void 0 ? { min } : {},
|
|
27061
27394
|
...max !== void 0 ? { max } : {}
|
|
@@ -27076,8 +27409,7 @@ var DEFAULT_PRIORITISATION_RULES = {
|
|
|
27076
27409
|
names: {},
|
|
27077
27410
|
checkOrder: [...DEFAULT_CHECK_ORDER],
|
|
27078
27411
|
fundingOrder: [...DEFAULT_FUNDING_ORDER],
|
|
27079
|
-
weightValueAtStake: false
|
|
27080
|
-
orderRev: RULES_ORDER_REV
|
|
27412
|
+
weightValueAtStake: false
|
|
27081
27413
|
};
|
|
27082
27414
|
var LEGACY_DEFAULTS = {
|
|
27083
27415
|
quickWins: { v: 60, f: 60, g: 40 },
|
|
@@ -27150,28 +27482,23 @@ function sanitiseNames(input2) {
|
|
|
27150
27482
|
}
|
|
27151
27483
|
return out;
|
|
27152
27484
|
}
|
|
27153
|
-
var sameOrder = (a, b) => a.length === b.length && a.every((k, i) => k === b[i]);
|
|
27154
27485
|
function sanitisePrioritisationRules(input2) {
|
|
27155
27486
|
const d = DEFAULT_PRIORITISATION_RULES;
|
|
27156
27487
|
const r = input2 && typeof input2 === "object" ? input2 : {};
|
|
27157
27488
|
const weightValueAtStake = typeof r.weightValueAtStake === "boolean" ? r.weightValueAtStake : d.weightValueAtStake;
|
|
27158
27489
|
if (isLegacyShape(r)) {
|
|
27159
|
-
return { categories: migrateLegacy(r), names: {}, checkOrder: [...LEGACY_CHECK_ORDER], fundingOrder: [...DEFAULT_FUNDING_ORDER], weightValueAtStake
|
|
27490
|
+
return { categories: migrateLegacy(r), names: {}, checkOrder: [...LEGACY_CHECK_ORDER], fundingOrder: [...DEFAULT_FUNDING_ORDER], weightValueAtStake };
|
|
27160
27491
|
}
|
|
27161
27492
|
const cats = r.categories && typeof r.categories === "object" ? r.categories : {};
|
|
27162
27493
|
const categories = Object.fromEntries(
|
|
27163
27494
|
RULE_CATEGORY_KEYS.map((k) => [k, sanitiseCategory(cats[k], d.categories[k])])
|
|
27164
27495
|
);
|
|
27165
|
-
const names = sanitiseNames(r.names);
|
|
27166
|
-
let checkOrder = sanitiseCategoryOrder(r.checkOrder, DEFAULT_CHECK_ORDER);
|
|
27167
|
-
if (r.orderRev !== RULES_ORDER_REV && sameOrder(checkOrder, LEGACY_CHECK_ORDER) && Object.keys(names).length === 0 && JSON.stringify(categories) === JSON.stringify(d.categories)) checkOrder = [...DEFAULT_CHECK_ORDER];
|
|
27168
27496
|
return {
|
|
27169
27497
|
categories,
|
|
27170
|
-
names,
|
|
27171
|
-
checkOrder,
|
|
27498
|
+
names: sanitiseNames(r.names),
|
|
27499
|
+
checkOrder: sanitiseCategoryOrder(r.checkOrder, DEFAULT_CHECK_ORDER),
|
|
27172
27500
|
fundingOrder: sanitiseCategoryOrder(r.fundingOrder, DEFAULT_FUNDING_ORDER),
|
|
27173
|
-
weightValueAtStake
|
|
27174
|
-
orderRev: RULES_ORDER_REV
|
|
27501
|
+
weightValueAtStake
|
|
27175
27502
|
};
|
|
27176
27503
|
}
|
|
27177
27504
|
var KEY_BY_NAME = new Map(RULE_CATEGORY_KEYS.map((k) => [RULE_CATEGORY_NAME[k], k]));
|
|
@@ -28086,6 +28413,7 @@ function sanitiseInitiative(raw, idx, warnings) {
|
|
|
28086
28413
|
const milestones = sanitiseMilestones(o.milestones, idx, warnings);
|
|
28087
28414
|
const risks = sanitiseRisks(o.risks, idx, warnings);
|
|
28088
28415
|
const followUps = pruneFollowUpLinks(sanitiseFollowUps(o.followUps, idx, warnings), milestones, risks) ?? [];
|
|
28416
|
+
const qualityFactors = sanitiseQualityFactors(o.qualityFactors, idx, warnings);
|
|
28089
28417
|
const nowIso = (/* @__PURE__ */ new Date()).toISOString();
|
|
28090
28418
|
return {
|
|
28091
28419
|
id,
|
|
@@ -28142,6 +28470,7 @@ function sanitiseInitiative(raw, idx, warnings) {
|
|
|
28142
28470
|
...milestones.length > 0 ? { milestones } : {},
|
|
28143
28471
|
...risks.length > 0 ? { risks } : {},
|
|
28144
28472
|
...followUps.length > 0 ? { followUps } : {},
|
|
28473
|
+
...qualityFactors.length > 0 ? { qualityFactors } : {},
|
|
28145
28474
|
dependsOn,
|
|
28146
28475
|
requiredSkills,
|
|
28147
28476
|
...capabilityImpacts.length > 0 ? { capabilityImpacts } : {},
|
|
@@ -29608,14 +29937,6 @@ function budgetAmount(i, field = "budget") {
|
|
|
29608
29937
|
return typeof v === "number" && Number.isFinite(v) ? Math.max(0, v) : 0;
|
|
29609
29938
|
}
|
|
29610
29939
|
|
|
29611
|
-
// ../../initiative-model/src/portfolio-scope.ts
|
|
29612
|
-
function isLiveInitiative(i) {
|
|
29613
|
-
return !i.archived && i.status !== "Retired";
|
|
29614
|
-
}
|
|
29615
|
-
function isInFlightInitiative(i) {
|
|
29616
|
-
return isLiveInitiative(i) && i.status !== "On Hold";
|
|
29617
|
-
}
|
|
29618
|
-
|
|
29619
29940
|
// ../../initiative-model/src/guardrails.ts
|
|
29620
29941
|
var GUARDRAIL_KEYS = [
|
|
29621
29942
|
"maxBuBudgetSharePct",
|
|
@@ -30169,6 +30490,64 @@ function deriveCapabilityImpact(capabilities, initiatives) {
|
|
|
30169
30490
|
linkedInitiativeCount
|
|
30170
30491
|
};
|
|
30171
30492
|
}
|
|
30493
|
+
function quarterOf(isoDate) {
|
|
30494
|
+
if (!isoDate) return null;
|
|
30495
|
+
const m = /^(\d{4})-(\d{2})/.exec(isoDate);
|
|
30496
|
+
if (!m) return null;
|
|
30497
|
+
const month = Number(m[2]);
|
|
30498
|
+
if (month < 1 || month > 12) return null;
|
|
30499
|
+
return `${m[1]}-Q${Math.floor((month - 1) / 3) + 1}`;
|
|
30500
|
+
}
|
|
30501
|
+
function maturityTimeline(capabilities, initiatives) {
|
|
30502
|
+
const byId = new Map(capabilities.map((c) => [c.id, c]));
|
|
30503
|
+
const order = /* @__PURE__ */ new Map();
|
|
30504
|
+
const bySort = (a, b) => a.sortOrder - b.sortOrder || a.name.localeCompare(b.name);
|
|
30505
|
+
const domains = capabilities.filter((c) => c.parentId === null).sort(bySort);
|
|
30506
|
+
let n = 0;
|
|
30507
|
+
for (const d of domains) {
|
|
30508
|
+
for (const c of capabilities.filter((x) => x.parentId === d.id).sort(bySort)) order.set(c.id, n++);
|
|
30509
|
+
}
|
|
30510
|
+
const steps = [];
|
|
30511
|
+
for (const i of inScope(initiatives)) {
|
|
30512
|
+
if (!countsAsAchieved(i) && !countsAsPlanned(i)) continue;
|
|
30513
|
+
const best = /* @__PURE__ */ new Map();
|
|
30514
|
+
for (const link of i.capabilityImpacts ?? []) {
|
|
30515
|
+
const cap = byId.get(link.capabilityId);
|
|
30516
|
+
if (!cap || cap.parentId === null || !order.has(cap.id)) continue;
|
|
30517
|
+
const level2 = levelOfImpact(link, cap);
|
|
30518
|
+
const prev = best.get(cap.id);
|
|
30519
|
+
if (!prev || level2 !== null && (prev.level === null || level2 > prev.level)) {
|
|
30520
|
+
best.set(cap.id, { cap, kind: link.kind, level: level2 });
|
|
30521
|
+
}
|
|
30522
|
+
}
|
|
30523
|
+
const achieved = countsAsAchieved(i);
|
|
30524
|
+
const endDate = achieved ? null : isoDay(i.timelineEnd);
|
|
30525
|
+
for (const { cap, kind, level: level2 } of best.values()) {
|
|
30526
|
+
steps.push({
|
|
30527
|
+
capability: cap,
|
|
30528
|
+
domainName: byId.get(cap.parentId)?.name ?? "",
|
|
30529
|
+
initiative: i,
|
|
30530
|
+
kind,
|
|
30531
|
+
level: level2,
|
|
30532
|
+
quarter: achieved ? null : quarterOf(endDate),
|
|
30533
|
+
achieved,
|
|
30534
|
+
endDate
|
|
30535
|
+
});
|
|
30536
|
+
}
|
|
30537
|
+
}
|
|
30538
|
+
const groups = /* @__PURE__ */ new Map();
|
|
30539
|
+
for (const s of steps) {
|
|
30540
|
+
const key = s.achieved ? "achieved" : s.quarter ?? "undated";
|
|
30541
|
+
(groups.get(key) ?? groups.set(key, []).get(key)).push(s);
|
|
30542
|
+
}
|
|
30543
|
+
const rank = (key) => key === "achieved" ? 0 : key === "undated" ? 2 : 1;
|
|
30544
|
+
return [...groups.entries()].sort(([a], [b]) => rank(a) - rank(b) || a.localeCompare(b)).map(([key, list]) => ({
|
|
30545
|
+
key,
|
|
30546
|
+
steps: list.sort(
|
|
30547
|
+
(a, b) => (order.get(a.capability.id) ?? 0) - (order.get(b.capability.id) ?? 0) || a.initiative.name.localeCompare(b.initiative.name)
|
|
30548
|
+
)
|
|
30549
|
+
}));
|
|
30550
|
+
}
|
|
30172
30551
|
function linkedCapabilityIds(initiative, capabilities) {
|
|
30173
30552
|
const leafIds = new Set(capabilities.filter((c) => c.parentId !== null).map((c) => c.id));
|
|
30174
30553
|
const out = [];
|
|
@@ -30831,6 +31210,26 @@ function computeBoardActions(input2) {
|
|
|
30831
31210
|
});
|
|
30832
31211
|
}
|
|
30833
31212
|
}
|
|
31213
|
+
const qualityGaps2 = scalingQualityGaps(i);
|
|
31214
|
+
if (qualityGaps2.length > 0) {
|
|
31215
|
+
const first = qualityGaps2[0];
|
|
31216
|
+
actions.push({
|
|
31217
|
+
id: `qualityGapScaling:${i.id}`,
|
|
31218
|
+
kind: "qualityGapScaling",
|
|
31219
|
+
severity: "medium",
|
|
31220
|
+
href: `/evaluate/${i.id}`,
|
|
31221
|
+
initiativeId: i.id,
|
|
31222
|
+
initiativeName: i.name,
|
|
31223
|
+
params: {
|
|
31224
|
+
count: qualityGaps2.length,
|
|
31225
|
+
factor: first.factor,
|
|
31226
|
+
state: first.state,
|
|
31227
|
+
status: i.status,
|
|
31228
|
+
...first.targetDate ? { targetDate: first.targetDate } : {}
|
|
31229
|
+
},
|
|
31230
|
+
weight: i.budget || 0
|
|
31231
|
+
});
|
|
31232
|
+
}
|
|
30834
31233
|
if (i.status === "In Production") {
|
|
30835
31234
|
if (hasActualsData(i.actuals)) {
|
|
30836
31235
|
const si = computeSollIst(i.economics, i.actuals);
|
|
@@ -31459,6 +31858,7 @@ function applyScope(p, f, now, base) {
|
|
|
31459
31858
|
if (f.hasOverdueMilestone !== void 0 && summariseMilestones(i, now).overdue > 0 !== f.hasOverdueMilestone) return false;
|
|
31460
31859
|
if (f.hasOverdueFollowUp !== void 0 && summariseFollowUps(i.followUps, now).overdue > 0 !== f.hasOverdueFollowUp) return false;
|
|
31461
31860
|
if (f.hasSevereRisk !== void 0 && summariseRisks(i.risks).severe > 0 !== f.hasSevereRisk) return false;
|
|
31861
|
+
if (f.hasQualityGap !== void 0 && hasQualityGaps(i) !== f.hasQualityGap) return false;
|
|
31462
31862
|
return true;
|
|
31463
31863
|
});
|
|
31464
31864
|
return { items, errors };
|
|
@@ -31500,6 +31900,7 @@ var FIELD_NAMES = [
|
|
|
31500
31900
|
"nextMilestone",
|
|
31501
31901
|
"openFollowUps",
|
|
31502
31902
|
"overdueFollowUps",
|
|
31903
|
+
"qualityGaps",
|
|
31503
31904
|
"lastStatusReport",
|
|
31504
31905
|
"capabilities",
|
|
31505
31906
|
"requiredSkills",
|
|
@@ -31592,6 +31993,11 @@ function fieldValue(i, f, c) {
|
|
|
31592
31993
|
}
|
|
31593
31994
|
case "overdueFollowUps":
|
|
31594
31995
|
return summariseFollowUps(i.followUps, c.now).overdue;
|
|
31996
|
+
case "qualityGaps": {
|
|
31997
|
+
const s = summariseQuality(i.qualityFactors);
|
|
31998
|
+
if (s.rated === 0) return null;
|
|
31999
|
+
return { rated: s.rated, gaps: s.gaps, weakest: s.gaps > 0 && s.weakest?.state !== void 0 ? { factor: s.weakest.factor, state: s.weakest.state } : null };
|
|
32000
|
+
}
|
|
31595
32001
|
case "lastStatusReport": {
|
|
31596
32002
|
const r = latestStatusReport(i);
|
|
31597
32003
|
return r ? { date: r.date, scope: r.scope, timeline: r.timeline, budget: r.budget, skills: r.skills, summary: r.summary ?? null } : null;
|
|
@@ -31703,6 +32109,11 @@ function portfolioSummary(p) {
|
|
|
31703
32109
|
warnings: p.warnings
|
|
31704
32110
|
};
|
|
31705
32111
|
}
|
|
32112
|
+
function qualityGapSeverity(i) {
|
|
32113
|
+
const s = summariseQuality(i.qualityFactors);
|
|
32114
|
+
if (s.rated === 0) return null;
|
|
32115
|
+
return s.gaps === 0 ? 0 : s.gaps * 10 + (10 - (s.weakest?.state ?? 1));
|
|
32116
|
+
}
|
|
31706
32117
|
function queryInitiatives(p, q, now = Date.now()) {
|
|
31707
32118
|
const { items, errors } = applyScope(p, q.filter, now);
|
|
31708
32119
|
if (errors.length) return { ok: false, errors };
|
|
@@ -31712,7 +32123,7 @@ function queryInitiatives(p, q, now = Date.now()) {
|
|
|
31712
32123
|
if (q.sortBy) {
|
|
31713
32124
|
const key = q.sortBy;
|
|
31714
32125
|
const dir = q.sortDir === "asc" ? 1 : -1;
|
|
31715
|
-
const val = (i) => fieldValue(i, key, c);
|
|
32126
|
+
const val = (i) => key === "qualityGaps" ? qualityGapSeverity(i) : fieldValue(i, key, c);
|
|
31716
32127
|
ordered = [...items].sort((a, b) => {
|
|
31717
32128
|
const va = val(a);
|
|
31718
32129
|
const vb = val(b);
|
|
@@ -32027,6 +32438,7 @@ function initiativeDetail(p, ref, now = Date.now()) {
|
|
|
32027
32438
|
economics: effEcon(i) ?? null,
|
|
32028
32439
|
milestonesList: sortMilestones(i.milestones ?? []).map((m) => ({ title: m.title, baseline: m.baselineDate, forecast: effectiveForecast(m), done: m.doneDate ?? null, state: milestoneState(m, now) })),
|
|
32029
32440
|
risksList: (i.risks ?? []).map((r) => ({ title: r.title, category: r.category, probability: r.probability, impact: r.impact, residual: r.residualProbability !== void 0 ? [r.residualProbability, r.residualImpact] : null, status: r.status, owner: r.owner ?? null, mitigation: r.mitigation ?? null, unmitigated: isUnmitigated(r, i.followUps) })),
|
|
32441
|
+
qualityFactors: canonicalQualityFactors(i.qualityFactors ?? []).map((q) => ({ factor: q.factor, required: q.required ?? null, state: q.state ?? null, targetDate: q.targetDate ?? null, note: q.note ?? null, gap: isQualityGap(q) })),
|
|
32030
32442
|
followUpsList: sortFollowUps(i.followUps ?? []).map((f) => ({ title: f.title, due: f.dueDate, done: f.doneDate ?? null, state: followUpState(f, now), assignee: f.assigneeName ?? null })),
|
|
32031
32443
|
// The six latest by reporting date, newest first, as the Status Report page.
|
|
32032
32444
|
statusReports: sortStatusReports(i.statusReports ?? []).slice(0, 6),
|
|
@@ -32216,7 +32628,7 @@ function compareSnapshots(p, q, now = Date.now()) {
|
|
|
32216
32628
|
};
|
|
32217
32629
|
}
|
|
32218
32630
|
var BOARD_RISK_ROWS = 10;
|
|
32219
|
-
var BOARD_SECTIONS = ["kpis", "capacity", "milestones", "risks", "capabilityImpact"];
|
|
32631
|
+
var BOARD_SECTIONS = ["kpis", "capacity", "milestones", "risks", "quality", "capabilityImpact"];
|
|
32220
32632
|
function boardSlices(p, q, now = Date.now()) {
|
|
32221
32633
|
const want = new Set(q.sections?.length ? q.sections : BOARD_SECTIONS);
|
|
32222
32634
|
const { items, errors } = applyScope(p, q.filter, now, p.initiatives.filter(isLiveInitiative));
|
|
@@ -32288,6 +32700,17 @@ function boardSlices(p, q, now = Date.now()) {
|
|
|
32288
32700
|
roseCount: s.rose.length
|
|
32289
32701
|
} : null;
|
|
32290
32702
|
}
|
|
32703
|
+
if (want.has("quality")) {
|
|
32704
|
+
const s = boardQualitySlice(items);
|
|
32705
|
+
out.quality = s ? {
|
|
32706
|
+
ratedInitiatives: s.ratedInitiativeCount,
|
|
32707
|
+
initiativesWithGaps: s.initiativesWithGaps,
|
|
32708
|
+
scalingWithHighGap: s.scalingWithGaps,
|
|
32709
|
+
gapCount: s.gapCount,
|
|
32710
|
+
byFactor: s.byFactor,
|
|
32711
|
+
gaps: s.gaps.map((g) => ({ initiative: g.initiativeName, status: g.status, factor: g.factor, required: g.required, state: g.state, targetDate: g.targetDate ?? null }))
|
|
32712
|
+
} : null;
|
|
32713
|
+
}
|
|
32291
32714
|
if (want.has("capabilityImpact")) {
|
|
32292
32715
|
if (!p.capabilities.some((c) => c.parentId !== null)) out.capabilityImpact = null;
|
|
32293
32716
|
else {
|
|
@@ -32312,6 +32735,11 @@ function boardSlices(p, q, now = Date.now()) {
|
|
|
32312
32735
|
};
|
|
32313
32736
|
}
|
|
32314
32737
|
var SKILL_MONTHS_DEFAULT = 24;
|
|
32738
|
+
function queueStateOf(cl, names) {
|
|
32739
|
+
const funded = new Set(cl.funded.map((i) => i.id));
|
|
32740
|
+
const ranked = new Set(cl.ranked.map((i) => i.id));
|
|
32741
|
+
return (i) => funded.has(i.id) ? "funded" : !ranked.has(i.id) ? "not in queue" : cl.excludedInitiativeIds.has(i.id) ? "parked: excluded" : cl.parkedSkillReason[i.id] ? `parked: skill cap ${cl.parkedSkillReason[i.id]}` : cl.parkedDependencyReason[i.id] ? `parked: waits on ${names.get(cl.parkedDependencyUpstreamId[i.id] ?? "") ?? cl.parkedDependencyReason[i.id]}` : "parked: budget";
|
|
32742
|
+
}
|
|
32315
32743
|
function capDeltaErrors(p, deltas) {
|
|
32316
32744
|
const errors = [];
|
|
32317
32745
|
for (const [id, d] of Object.entries(deltas ?? {})) {
|
|
@@ -32346,7 +32774,7 @@ function skillAllocation(p, q, now = Date.now()) {
|
|
|
32346
32774
|
const base = baselineScenario(p.budgetField, p.exclusions);
|
|
32347
32775
|
const cl = computeScenarioCutLine(candidates, p.rules, p.discountRatePct, p.skills, p.budgetCap, base, liveAll);
|
|
32348
32776
|
const funded = new Set(cl.funded.map((i) => i.id));
|
|
32349
|
-
const
|
|
32777
|
+
const queueState = queueStateOf(cl, names);
|
|
32350
32778
|
const pipeline = pipelineSkillDemand(items.filter(isInFlightInitiative));
|
|
32351
32779
|
const run = inProductionSkillFte(liveAll);
|
|
32352
32780
|
const includeRun = q.includeRunTeams ?? false;
|
|
@@ -32368,7 +32796,6 @@ function skillAllocation(p, q, now = Date.now()) {
|
|
|
32368
32796
|
const perSkill = skills.map((s) => {
|
|
32369
32797
|
const users = items.filter((i) => (i.requiredSkills ?? []).some((rs) => rs.skillId === s.id && (rs.fte > 0 || (rs.runFte ?? 0) > 0))).map((i) => {
|
|
32370
32798
|
const rs = (i.requiredSkills ?? []).find((x) => x.skillId === s.id);
|
|
32371
|
-
const parkedBySkill = cl.parkedSkillReason[i.id];
|
|
32372
32799
|
return {
|
|
32373
32800
|
id: i.id,
|
|
32374
32801
|
name: i.name,
|
|
@@ -32378,8 +32805,7 @@ function skillAllocation(p, q, now = Date.now()) {
|
|
|
32378
32805
|
buildFte: rs.fte,
|
|
32379
32806
|
...rs.runFte !== void 0 ? { runFte: rs.runFte } : {},
|
|
32380
32807
|
timeline: { start: i.timelineStart ?? null, end: i.timelineEnd ?? null },
|
|
32381
|
-
|
|
32382
|
-
queue: funded.has(i.id) ? "funded" : !ranked.has(i.id) ? "not in queue" : cl.excludedInitiativeIds.has(i.id) ? "parked: excluded" : parkedBySkill ? `parked: skill cap ${parkedBySkill}` : cl.parkedDependencyReason[i.id] ? `parked: waits on ${names.get(cl.parkedDependencyUpstreamId[i.id] ?? "") ?? cl.parkedDependencyReason[i.id]}` : "parked: budget"
|
|
32808
|
+
queue: queueState(i)
|
|
32383
32809
|
};
|
|
32384
32810
|
}).sort((a, b) => b.fte - a.fte || a.name.localeCompare(b.name));
|
|
32385
32811
|
const series = rows.map((r) => ({ month: String(r.month), fte: Number(r[s.id] ?? 0) }));
|
|
@@ -32446,6 +32872,307 @@ function skillAllocation(p, q, now = Date.now()) {
|
|
|
32446
32872
|
while (rowCap > 5 && JSON.stringify(build(rowCap, perSkill)).length > OUTPUT_BUDGET_CHARS) rowCap = Math.max(5, Math.floor(rowCap / 2));
|
|
32447
32873
|
return fitRows((list) => build(rowCap, list), perSkill).body;
|
|
32448
32874
|
}
|
|
32875
|
+
var PLANNED = /* @__PURE__ */ new Set(["Idea", "Pilot", "Scaling"]);
|
|
32876
|
+
function capabilityAllocation(p, q, now = Date.now()) {
|
|
32877
|
+
const errors = [];
|
|
32878
|
+
const leaves = p.capabilities.filter((c) => c.parentId !== null);
|
|
32879
|
+
if (leaves.length === 0) return { ok: false, errors: ["The backup has no capability map (Settings \u2192 Taxonomy \u2192 Capabilities in the app)."] };
|
|
32880
|
+
const domainName = (c) => p.capabilities.find((d) => d.id === c.parentId)?.name ?? "";
|
|
32881
|
+
const path2 = (c) => `${domainName(c)} / ${c.name}`;
|
|
32882
|
+
let pool = leaves;
|
|
32883
|
+
if (q.domain) {
|
|
32884
|
+
const ds = p.capabilities.filter((x) => x.parentId === null && lc(x.name) === lc(q.domain));
|
|
32885
|
+
if (ds.length === 0) errors.push(`domain: "${q.domain}" is not a domain of this map (have: ${p.capabilities.filter((x) => x.parentId === null).map((x) => x.name).join(" | ")}).`);
|
|
32886
|
+
else pool = leaves.filter((c) => ds.some((d) => d.id === c.parentId));
|
|
32887
|
+
}
|
|
32888
|
+
const chosen = /* @__PURE__ */ new Map();
|
|
32889
|
+
for (const ref of q.capabilities ?? []) {
|
|
32890
|
+
const hits = pool.filter((c) => c.id === ref || lc(path2(c)) === lc(ref));
|
|
32891
|
+
const byName = hits.length ? hits : pool.filter((c) => lc(c.name) === lc(ref));
|
|
32892
|
+
if (byName.length === 1) chosen.set(byName[0].id, byName[0]);
|
|
32893
|
+
else if (byName.length > 1) errors.push(`capabilities: "${ref}" names ${byName.length} capabilities (${byName.map(path2).join(" | ")}); pass "Domain / Capability" or the id.`);
|
|
32894
|
+
else if (q.domain && leaves.some((c) => c.id === ref || lc(path2(c)) === lc(ref) || lc(c.name) === lc(ref))) errors.push(`capabilities: "${ref}" is not in the domain ${q.domain}.`);
|
|
32895
|
+
else errors.push(`capabilities: "${ref}" is not a capability of this map (a domain is not a capability; pass domain for a whole domain).`);
|
|
32896
|
+
}
|
|
32897
|
+
const known = new Set(p.initiatives.map((i) => i.id));
|
|
32898
|
+
for (const id of q.excludedIds ?? []) if (!known.has(id)) errors.push(`excludedIds: "${id}" is not an initiative id of this backup.`);
|
|
32899
|
+
const liveAll = p.initiatives.filter(isLiveInitiative);
|
|
32900
|
+
const scoped = applyScope(p, q.filter, now, liveAll);
|
|
32901
|
+
errors.push(...scoped.errors);
|
|
32902
|
+
if (errors.length) return { ok: false, errors };
|
|
32903
|
+
const items = scoped.items;
|
|
32904
|
+
const names = new Map(p.initiatives.map((i) => [i.id, i.name]));
|
|
32905
|
+
const wantedIds = new Set((chosen.size ? [...chosen.values()] : pool).map((c) => c.id));
|
|
32906
|
+
const candidates = applyScope(p, q.filter ? { ...q.filter, status: void 0 } : void 0, now, liveAll).items;
|
|
32907
|
+
const base = baselineScenario(p.budgetField, p.exclusions);
|
|
32908
|
+
const cl = computeScenarioCutLine(candidates, p.rules, p.discountRatePct, p.skills, p.budgetCap, base, liveAll);
|
|
32909
|
+
const queueState = queueStateOf(cl, names);
|
|
32910
|
+
const funded = new Set(cl.funded.map((i) => i.id));
|
|
32911
|
+
const ranked = new Set(cl.ranked.map((i) => i.id));
|
|
32912
|
+
const parked = (i) => PLANNED.has(i.status) && ranked.has(i.id) && !funded.has(i.id);
|
|
32913
|
+
const result = deriveCapabilityImpact(p.capabilities, items);
|
|
32914
|
+
const steps = maturityTimeline(p.capabilities, items).flatMap((g) => g.steps.map((st) => ({ ...st, group: g.key })));
|
|
32915
|
+
const leafIds = new Set(leaves.map((c) => c.id));
|
|
32916
|
+
const limit = Math.min(q.limit ?? ROW_LIMIT_DEFAULT, ROW_LIMIT_MAX);
|
|
32917
|
+
const perCap = result.capabilities.filter((cp) => wantedIds.has(cp.capability.id)).map((cp) => {
|
|
32918
|
+
const c = cp.capability;
|
|
32919
|
+
const rows = cp.linked.map((i) => {
|
|
32920
|
+
const link = (i.capabilityImpacts ?? []).find((l) => l.capabilityId === c.id);
|
|
32921
|
+
return {
|
|
32922
|
+
id: i.id,
|
|
32923
|
+
name: i.name,
|
|
32924
|
+
status: i.status,
|
|
32925
|
+
parked: parked(i),
|
|
32926
|
+
kind: link.kind,
|
|
32927
|
+
level: levelOfImpact(link, c),
|
|
32928
|
+
counts: cp.counting.includes(i),
|
|
32929
|
+
plannedQuarter: PLANNED.has(i.status) ? quarterOf(i.timelineEnd) : null,
|
|
32930
|
+
spend: Math.round(attributedSpend(i, leafIds).get(c.id) ?? 0),
|
|
32931
|
+
queue: i.status === "In Production" ? "in production" : queueState(i)
|
|
32932
|
+
};
|
|
32933
|
+
}).sort((a, b) => (b.level ?? -1) - (a.level ?? -1) || a.name.localeCompare(b.name));
|
|
32934
|
+
const securedLevel = Math.max(cp.current, ...rows.filter((r) => r.counts && !r.parked).map((r) => r.level ?? 0));
|
|
32935
|
+
const atRisk = cp.projected > securedLevel;
|
|
32936
|
+
return {
|
|
32937
|
+
id: c.id,
|
|
32938
|
+
capability: c.name,
|
|
32939
|
+
domain: cp.domainName,
|
|
32940
|
+
importance: c.importance,
|
|
32941
|
+
current: cp.current,
|
|
32942
|
+
projected: cp.projected,
|
|
32943
|
+
target: cp.target,
|
|
32944
|
+
achieved: cp.achieved,
|
|
32945
|
+
planned: cp.planned,
|
|
32946
|
+
plannedBy: cp.plannedBy,
|
|
32947
|
+
change: cp.change,
|
|
32948
|
+
gap: cp.gap,
|
|
32949
|
+
coverage: cp.coverage,
|
|
32950
|
+
concentrated: cp.concentrated,
|
|
32951
|
+
retire: cp.retire,
|
|
32952
|
+
spend: Math.round(cp.spend),
|
|
32953
|
+
spendSharePct: Math.round(cp.spendShare * 1e3) / 10,
|
|
32954
|
+
...atRisk ? { atRisk: { securedLevel, because: `The projected ${cp.projected} relies on pipeline work the baseline cut line parks; funded and running work brings ${securedLevel}.` } } : {},
|
|
32955
|
+
initiatives: rows.map(({ parked: _parked, ...r }) => r),
|
|
32956
|
+
initiativeCount: rows.length,
|
|
32957
|
+
path: steps.filter((st) => st.capability.id === c.id).map((st) => ({ when: st.group, initiative: st.initiative.name, kind: st.kind, level: st.level }))
|
|
32958
|
+
};
|
|
32959
|
+
});
|
|
32960
|
+
let whatIf;
|
|
32961
|
+
if (q.excludedIds || q.budgetCap !== void 0) {
|
|
32962
|
+
const scenario = { ...base, ...q.excludedIds ? { excludedIds: Object.fromEntries(q.excludedIds.map((id) => [id, {}])) } : {} };
|
|
32963
|
+
const cap = q.budgetCap === null ? void 0 : q.budgetCap ?? p.budgetCap;
|
|
32964
|
+
const alt = computeScenarioCutLine(candidates, p.rules, p.discountRatePct, p.skills, cap, scenario, liveAll);
|
|
32965
|
+
const altFunded = new Set(alt.funded.map((i) => i.id));
|
|
32966
|
+
const lost = items.filter((i) => PLANNED.has(i.status) && (funded.has(i.id) && !altFunded.has(i.id) || alt.excludedInitiativeIds.has(i.id) && !cl.excludedInitiativeIds.has(i.id)));
|
|
32967
|
+
const lostIds = new Set(lost.map((i) => i.id));
|
|
32968
|
+
const after = deriveCapabilityImpact(p.capabilities, items.filter((i) => !lostIds.has(i.id)));
|
|
32969
|
+
const afterById = new Map(after.capabilities.map((cp) => [cp.capability.id, cp]));
|
|
32970
|
+
const changed = result.capabilities.filter((cp) => wantedIds.has(cp.capability.id)).map((cp) => ({ cp, a: afterById.get(cp.capability.id) })).filter(({ cp, a }) => a.projected !== cp.projected || a.gap !== cp.gap || a.coverage !== cp.coverage).map(({ cp, a }) => ({
|
|
32971
|
+
capability: cp.capability.name,
|
|
32972
|
+
domain: cp.domainName,
|
|
32973
|
+
importance: cp.capability.importance,
|
|
32974
|
+
projected: { before: cp.projected, after: a.projected },
|
|
32975
|
+
gap: { before: cp.gap, after: a.gap },
|
|
32976
|
+
coverage: { before: cp.coverage, after: a.coverage }
|
|
32977
|
+
}));
|
|
32978
|
+
whatIf = {
|
|
32979
|
+
excluded: [...alt.excludedInitiativeIds].map((id) => names.get(id) ?? id),
|
|
32980
|
+
budgetCap: alt.cap.budgetCap ?? null,
|
|
32981
|
+
// Funded in the baseline and not in the what-if, or newly excluded.
|
|
32982
|
+
takenOut: lost.slice(0, limit).map((i) => ({ name: i.name, was: queueState(i) })),
|
|
32983
|
+
...lost.length > limit ? { takenOutCount: lost.length } : {},
|
|
32984
|
+
capabilitiesChanged: changed.slice(0, limit),
|
|
32985
|
+
...changed.length > limit ? { changedCount: changed.length } : {},
|
|
32986
|
+
newGaps: changed.filter((x) => x.gap.before === 0 && x.gap.after > 0).map((x) => x.capability),
|
|
32987
|
+
note: "Estimate with the app's rules: the planned initiatives that lose their funding, or that the what-if newly excludes, are taken out and the map is derived again. The baseline projection already counts parked pipeline work (as the page does), so a higher budget cap cannot show a gain here; see atRisk instead. excludedIds replaces the organisation's exclusions. The app has no such what-if of its own."
|
|
32988
|
+
};
|
|
32989
|
+
}
|
|
32990
|
+
const build = (rowCap2, list) => ({
|
|
32991
|
+
ok: true,
|
|
32992
|
+
asOf: p.exportedAt,
|
|
32993
|
+
scope: `${items.length} live initiatives${q.filter ? " in the filter" : ""}, ${result.linkedInitiativeCount} linked to the map`,
|
|
32994
|
+
hasCap: cl.hasCap,
|
|
32995
|
+
capabilities: list.map((x) => ({ ...x, initiatives: x.initiatives.slice(0, rowCap2), path: x.path.slice(0, rowCap2) })),
|
|
32996
|
+
...list.length < perCap.length ? { capabilitiesReturned: list.length, capabilityCount: perCap.length, cutForSize: `Cut to the first ${list.length} of ${perCap.length} capabilities to fit; pass capabilities or domain to see the others.` } : {},
|
|
32997
|
+
...rowCap2 < limit && perCap.some((x) => x.initiatives.length > rowCap2) ? { rowsCutForSize: `Initiative rows and path steps cut to ${rowCap2} per capability to fit (initiativeCount gives the full number).` } : {},
|
|
32998
|
+
...whatIf ? { whatIf } : {},
|
|
32999
|
+
note: "The Capabilities page's rules over live initiatives (archived and Retired ones are out of scope): projected = the highest level the counting initiatives bring, never below current; In Production = achieved, Idea/Pilot/Scaling = planned (plannedBy = earliest end date reaching it), On Hold and retire links do not count; a link without its own level brings the target; spend = each initiative's budget split evenly over its mapped links; coverage and concentration as on the page. queue = the baseline cut line (rank_initiatives' rule); hasCap = the cut line has a budget cap, a skill cap or exclusions (without any, everything is funded). atRisk = the projection needs parked pipeline work. path = the Timeline tab (achieved, then quarters of the planned end dates, then undated); a retire step has level null. Quote these figures rather than recomputing them."
|
|
33000
|
+
});
|
|
33001
|
+
let rowCap = limit;
|
|
33002
|
+
while (rowCap > 5 && JSON.stringify(build(rowCap, perCap)).length > OUTPUT_BUDGET_CHARS) rowCap = Math.max(5, Math.floor(rowCap / 2));
|
|
33003
|
+
return fitRows((list) => build(rowCap, list), perCap).body;
|
|
33004
|
+
}
|
|
33005
|
+
|
|
33006
|
+
// src/analysis-templates.ts
|
|
33007
|
+
var RULES = [
|
|
33008
|
+
"Rules for every step:",
|
|
33009
|
+
"- If no portfolio is loaded in this conversation, ask for the path of the backup file (BICon app: Import/Export \u2192 Backup \u2192 Export; often in Downloads) and call `load_portfolio` first; pass the portfolioId it returns as `portfolio` to every other tool.",
|
|
33010
|
+
"- Use only figures the tools return. Never compute, re-derive or round them differently; when two figures differ, say which tool gave which.",
|
|
33011
|
+
"- Name the export date the figures stand on (exportedAt in load_portfolio's answer), and pass on every note, cut or size line the tools return.",
|
|
33012
|
+
"- Answer in the user's language, plainly, without jargon."
|
|
33013
|
+
].join("\n");
|
|
33014
|
+
var ANALYSIS_TEMPLATES = [
|
|
33015
|
+
{
|
|
33016
|
+
key: "board_briefing",
|
|
33017
|
+
title: "Board briefing on the portfolio",
|
|
33018
|
+
description: "A one-page briefing for the board: health, capacity, delivery, risks, capabilities and the actions to decide.",
|
|
33019
|
+
example: "Give me a one-page board briefing on the portfolio.",
|
|
33020
|
+
tools: ["board_slices", "portfolio_readings", "rank_initiatives"],
|
|
33021
|
+
args: [],
|
|
33022
|
+
body: () => [
|
|
33023
|
+
"Write a one-page board briefing on the loaded portfolio.",
|
|
33024
|
+
"1. Call `board_slices` (all `sections`). It gives the Board Memo's own chapter data: KPIs, capacity, milestones, risks, quality gaps and capability impact.",
|
|
33025
|
+
'2. Call `portfolio_readings` with `readings` ["boardActions", "signals"] for the actions the app proposes and the early warnings.',
|
|
33026
|
+
"3. Call `rank_initiatives` with no other input for the baseline cut line: what is funded, what is parked and why.",
|
|
33027
|
+
"Structure: three headline sentences; then Health (KPIs), Capacity (funded vs parked, the must-do block), Delivery (missed and due milestones), Risks (severe and rising), Capabilities (top changes and uncovered gaps); end with the three to five decisions the board should take, each tied to a Board Action."
|
|
33028
|
+
].join("\n")
|
|
33029
|
+
},
|
|
33030
|
+
{
|
|
33031
|
+
key: "budget_what_if",
|
|
33032
|
+
title: "What if the budget changes",
|
|
33033
|
+
description: "What a budget cut or rise does to the cut line: which initiatives fall below it or come back.",
|
|
33034
|
+
example: "What happens if the budget drops by 10 %?",
|
|
33035
|
+
tools: ["rank_initiatives"],
|
|
33036
|
+
args: [{ name: "changePct", description: "Budget change in percent, a whole number from -90 to 200, e.g. -10 or 20 (default -10)", required: false, number: { min: -90, max: 200 } }],
|
|
33037
|
+
body: (a) => {
|
|
33038
|
+
const pct = a.changePct?.trim() || "-10";
|
|
33039
|
+
return [
|
|
33040
|
+
`Show what a budget change of ${pct} % does to the portfolio.`,
|
|
33041
|
+
"1. Call `rank_initiatives` with `limit` 200: the baseline cut line. If its scenario.budgetCap is null, say that the organisation has no budget cap, so a budget change moves nothing, and stop.",
|
|
33042
|
+
`2. Call \`rank_initiatives\` again with \`tradeoffPct\` ${pct} and \`limit\` 200.`,
|
|
33043
|
+
"3. Compare the two answers row by row: initiatives that lose or gain funding, the budget cap before and after (scenario.budgetCap) and the funded budget before and after (totals). If either answer says truncated, say that only the rows shown were compared.",
|
|
33044
|
+
"Report: the change in one sentence; a table of the initiatives that move (name, status, bucket, budget, before \u2192 after); what happens to the must-do block; which parked initiatives come closest to funding."
|
|
33045
|
+
].join("\n");
|
|
33046
|
+
}
|
|
33047
|
+
},
|
|
33048
|
+
{
|
|
33049
|
+
key: "skill_bottlenecks",
|
|
33050
|
+
title: "Skill bottlenecks and what frees them",
|
|
33051
|
+
description: "Which skills are over their cap, when, who drives the peak, and how many FTE would free the parked work.",
|
|
33052
|
+
example: "Which skills are over their cap in the next 12 months, and who drives the peak?",
|
|
33053
|
+
tools: ["skill_allocation"],
|
|
33054
|
+
args: [{ name: "months", description: "Months to look ahead, 1 to 48 (default 12)", required: false, number: { min: 1, max: 48 } }],
|
|
33055
|
+
body: (a) => {
|
|
33056
|
+
const months = a.months?.trim() || "12";
|
|
33057
|
+
return [
|
|
33058
|
+
`Find the skill bottlenecks over the next ${months} months.`,
|
|
33059
|
+
`1. Call \`skill_allocation\` with \`months\` ${months}.`,
|
|
33060
|
+
'2. For each skill with months over cap (timeline.monthsOverCap) or initiatives parked by its cap (queue "parked: skill cap \u2026"), call `skill_allocation` again for that skill with `skillFteDeltas` mapping that skill\'s id from step 1 to the extra FTE, which is the larger of the highest overBy in monthsOverCap and the shortfall now (minus now.headroom, when headroom is below zero), to see what the extra FTE frees.',
|
|
33061
|
+
"Report per bottleneck skill: cap vs pipeline demand now; the months over cap and the peak with the initiatives behind it; the initiatives the cap parks; what the extra FTE frees (nowFunded). End with a short ranking of where extra FTE helps most."
|
|
33062
|
+
].join("\n");
|
|
33063
|
+
}
|
|
33064
|
+
},
|
|
33065
|
+
{
|
|
33066
|
+
key: "capability_gaps",
|
|
33067
|
+
title: "Capability gaps and who closes them",
|
|
33068
|
+
description: "Which capabilities stay below target, which gaps nobody covers, and which depend on parked work.",
|
|
33069
|
+
example: "Which capability gaps stay open, and which ones depend on work we have parked?",
|
|
33070
|
+
tools: ["capability_allocation"],
|
|
33071
|
+
args: [{ name: "domain", description: "Only this domain of the capability map (default all)", required: false }],
|
|
33072
|
+
body: (a) => {
|
|
33073
|
+
const domain2 = a.domain?.trim();
|
|
33074
|
+
return [
|
|
33075
|
+
`Review the capability gaps${domain2 ? ` in the domain ${domain2}` : ""}.`,
|
|
33076
|
+
`1. Call \`capability_allocation\`${domain2 ? ` with \`domain\` ${JSON.stringify(domain2)}` : ""}.`,
|
|
33077
|
+
"2. Group the capabilities: (a) uncovered gaps (coverage uncovered), differentiating first; (b) gaps closed only on paper (atRisk: the projection needs parked work); (c) partly covered gaps; (d) concentration (concentrated).",
|
|
33078
|
+
'3. For the two atRisk capabilities with the largest gap between projected and securedLevel, name the parked initiatives (queue starting "parked:") and their reason.',
|
|
33079
|
+
"Report: a table per group (capability, domain, importance, current \u2192 projected \u2192 target, initiatives); then the maturity path of the differentiating capabilities by quarter (path); end with the decisions that would close the most important gaps."
|
|
33080
|
+
].join("\n");
|
|
33081
|
+
}
|
|
33082
|
+
},
|
|
33083
|
+
{
|
|
33084
|
+
key: "risk_review",
|
|
33085
|
+
title: "Risk and delivery review",
|
|
33086
|
+
description: "Severe and rising risks, missed milestones, overdue follow-ups and the early warnings not yet recorded.",
|
|
33087
|
+
example: "Which high risks have no owner, and which milestones are slipping?",
|
|
33088
|
+
tools: ["portfolio_readings", "board_slices", "query_initiatives", "initiative_detail"],
|
|
33089
|
+
args: [],
|
|
33090
|
+
body: () => [
|
|
33091
|
+
"Review risks and delivery across the portfolio.",
|
|
33092
|
+
'1. Call `portfolio_readings` with `readings` ["risks", "signals"].',
|
|
33093
|
+
'2. Call `board_slices` with `sections` ["milestones", "risks"] for missed and due milestones and the risks that rose since the baseline.',
|
|
33094
|
+
'3. Call `query_initiatives` with `filter` {"hasSevereRisk": true} and `fields` ["status", "severeRisks", "topRisk", "unmitigatedRisks", "overdueMilestones", "nextMilestone", "overdueFollowUps"] for the initiatives behind them.',
|
|
33095
|
+
'4. Call `query_initiatives` with `filter` {"hasOverdueFollowUp": true} and `fields` ["status", "overdueFollowUps", "openFollowUps"] for overdue follow-ups anywhere in the portfolio.',
|
|
33096
|
+
"5. For each initiative from step 3 with unmitigatedRisks above zero (at most five, most first), call `initiative_detail` with `initiative` set to its name: risksList marks each unmitigated risk (no owner, or no linked follow-up).",
|
|
33097
|
+
"Report: severe risks without an owner or a follow-up (from step 5, and say how many initiatives were not opened); risks that rose; missed milestones and the next 90 days; overdue follow-ups; early warnings not yet in the register; end with the five actions that reduce exposure most."
|
|
33098
|
+
].join("\n")
|
|
33099
|
+
},
|
|
33100
|
+
{
|
|
33101
|
+
key: "since_snapshot",
|
|
33102
|
+
title: "What changed since the last snapshot",
|
|
33103
|
+
description: "KPIs, added and dropped initiatives, score and risk moves since a snapshot.",
|
|
33104
|
+
example: "What changed since the last snapshot?",
|
|
33105
|
+
tools: ["compare_snapshots"],
|
|
33106
|
+
args: [{ name: "from", description: "Snapshot id or label to compare from (default the newest)", required: false }],
|
|
33107
|
+
body: (a) => {
|
|
33108
|
+
const from = a.from?.trim();
|
|
33109
|
+
return [
|
|
33110
|
+
`Show what changed since ${from ? `the snapshot "${from}"` : "the newest snapshot"}.`,
|
|
33111
|
+
`1. Call \`compare_snapshots\`${from ? ` with \`from\` "${from}"` : ""}.`,
|
|
33112
|
+
"Report: the KPI moves in one table; initiatives added and gone; the largest score changes (value and governance side by side); risks that rose. End with what deserves the board's attention and why."
|
|
33113
|
+
].join("\n");
|
|
33114
|
+
}
|
|
33115
|
+
},
|
|
33116
|
+
{
|
|
33117
|
+
key: "initiative_brief",
|
|
33118
|
+
title: "One initiative, one page",
|
|
33119
|
+
description: "A one-page brief on one initiative: scores, economics, delivery, risks, capabilities and decisions.",
|
|
33120
|
+
example: "Give me a one-page brief on KYC Remediation.",
|
|
33121
|
+
tools: ["initiative_detail", "rank_initiatives"],
|
|
33122
|
+
args: [{ name: "initiative", description: "Initiative name or id", required: true }],
|
|
33123
|
+
body: (a) => {
|
|
33124
|
+
const name = a.initiative?.trim() ?? "";
|
|
33125
|
+
return [
|
|
33126
|
+
`Write a one-page brief on the initiative ${name}.`,
|
|
33127
|
+
`1. Call \`initiative_detail\` with \`initiative\` ${JSON.stringify(name)}.`,
|
|
33128
|
+
"2. Call `rank_initiatives` with `limit` 200 and find the initiative's row: rank, bucket, funded or parked and why. In Production, Retired and archived initiatives are not in the queue: then say so and take the bucket from initiative_detail (priorityBucket); never call it parked.",
|
|
33129
|
+
"Report: purpose and status in two sentences; scores and quadrant; economics; place in the cut line; milestones; open risks and follow-ups; capabilities it moves; the latest status report; the last decisions. End with the open questions for its sponsor."
|
|
33130
|
+
].join("\n");
|
|
33131
|
+
}
|
|
33132
|
+
}
|
|
33133
|
+
];
|
|
33134
|
+
function checkTemplateArgs(t, args = {}) {
|
|
33135
|
+
const errors = [];
|
|
33136
|
+
const out = {};
|
|
33137
|
+
for (const name of Object.keys(args)) {
|
|
33138
|
+
if (!t.args.some((a) => a.name === name)) errors.push(`${t.key} takes no argument "${name}"${t.args.length ? ` (it takes: ${t.args.map((a) => a.name).join(", ")})` : ""}`);
|
|
33139
|
+
}
|
|
33140
|
+
for (const a of t.args) {
|
|
33141
|
+
const raw = args[a.name];
|
|
33142
|
+
const v = raw === void 0 ? "" : String(raw).trim();
|
|
33143
|
+
if (!v) {
|
|
33144
|
+
if (a.required) errors.push(`${t.key} needs "${a.name}": ${a.description}`);
|
|
33145
|
+
continue;
|
|
33146
|
+
}
|
|
33147
|
+
if (a.number) {
|
|
33148
|
+
const n = Number(v);
|
|
33149
|
+
if (!Number.isInteger(n) || n < a.number.min || n > a.number.max) {
|
|
33150
|
+
errors.push(`"${a.name}" must be a whole number from ${a.number.min} to ${a.number.max}, not ${JSON.stringify(raw)}`);
|
|
33151
|
+
continue;
|
|
33152
|
+
}
|
|
33153
|
+
out[a.name] = String(n);
|
|
33154
|
+
} else {
|
|
33155
|
+
out[a.name] = v;
|
|
33156
|
+
}
|
|
33157
|
+
}
|
|
33158
|
+
return errors.length ? { ok: false, errors } : { ok: true, args: out };
|
|
33159
|
+
}
|
|
33160
|
+
function templateText(t, args = {}) {
|
|
33161
|
+
return `${t.title}
|
|
33162
|
+
|
|
33163
|
+
${t.body(args)}
|
|
33164
|
+
|
|
33165
|
+
${RULES}`;
|
|
33166
|
+
}
|
|
33167
|
+
function nextSteps() {
|
|
33168
|
+
return {
|
|
33169
|
+
hint: "Unless the user already asked something specific, give a two-line summary and offer these as a short numbered list (title and example), then wait for the choice. To run one, call get_analysis_template with its key and follow the text; an option with needs asks the user for those inputs first.",
|
|
33170
|
+
options: ANALYSIS_TEMPLATES.map((t) => {
|
|
33171
|
+
const needs = t.args.filter((a) => a.required).map((a) => a.name);
|
|
33172
|
+
return { key: t.key, title: t.title, example: t.example, ...needs.length ? { needs } : {} };
|
|
33173
|
+
})
|
|
33174
|
+
};
|
|
33175
|
+
}
|
|
32449
33176
|
|
|
32450
33177
|
// src/analysis-schema.ts
|
|
32451
33178
|
var portfolioId = () => external_exports.string().regex(/^pf_[0-9a-f]{16}$/, "a portfolioId looks like pf_<16 hex>, from load_portfolio").describe("The portfolioId load_portfolio returned");
|
|
@@ -32466,6 +33193,7 @@ var scopeFilter = () => external_exports.object({
|
|
|
32466
33193
|
hasOverdueMilestone: external_exports.boolean().optional(),
|
|
32467
33194
|
hasOverdueFollowUp: external_exports.boolean().optional(),
|
|
32468
33195
|
hasSevereRisk: external_exports.boolean().optional().describe("live high/critical risk"),
|
|
33196
|
+
hasQualityGap: external_exports.boolean().optional().describe("a quality gap, any status"),
|
|
32469
33197
|
ids: external_exports.array(external_exports.string()).optional()
|
|
32470
33198
|
}).strict().optional().describe("Scope (AND; text matches ignore case; archived left out unless includeArchived; scores 0\u2013100, composite = Prioritisation's)");
|
|
32471
33199
|
var loadPortfolioInputSchema = external_exports.object({
|
|
@@ -32534,6 +33262,15 @@ var skillAllocationInputSchema = external_exports.object({
|
|
|
32534
33262
|
excludedIds: external_exports.array(external_exports.string()).optional().describe("What-if: initiative ids parked out of the queue (replaces the organisation's exclusions)"),
|
|
32535
33263
|
limit: external_exports.number().int().min(1).max(ROW_LIMIT_MAX).optional().describe("Initiative rows per skill (default 50)")
|
|
32536
33264
|
}).strict();
|
|
33265
|
+
var capabilityAllocationInputSchema = external_exports.object({
|
|
33266
|
+
portfolio: portfolioId(),
|
|
33267
|
+
capabilities: external_exports.array(external_exports.string().trim().min(1)).optional().describe('Capability ids, names or "Domain / Capability" (default every capability; a domain is not a capability, use domain)'),
|
|
33268
|
+
domain: external_exports.string().trim().min(1).optional().describe("Only the capabilities of this domain (by name)"),
|
|
33269
|
+
filter: scopeFilter(),
|
|
33270
|
+
excludedIds: external_exports.array(external_exports.string()).optional().describe("What-if: initiative ids parked out of the queue (replaces the organisation's exclusions)"),
|
|
33271
|
+
budgetCap: external_exports.number().positive().nullable().optional().describe("What-if: budget cap, above 0; null = no cap; default the organisation's envelope"),
|
|
33272
|
+
limit: external_exports.number().int().min(1).max(ROW_LIMIT_MAX).optional().describe("Initiative rows per capability (default 50)")
|
|
33273
|
+
}).strict();
|
|
32537
33274
|
|
|
32538
33275
|
// src/server.ts
|
|
32539
33276
|
var SERVER_NAME = "bicon-initiative-composer";
|
|
@@ -32570,12 +33307,13 @@ function createServer() {
|
|
|
32570
33307
|
async () => text({
|
|
32571
33308
|
capabilities: [
|
|
32572
33309
|
"strict typed schemas: unknown keys anywhere in the spec are rejected with their exact path (never silently dropped)",
|
|
32573
|
-
"sections (aiAct/economics/actuals/decisionLog/statusReports/requiredSkills/capabilityImpacts/milestones/risks/followUps) accept a JSON object/array or the same JSON as a string; row ids are minted by the Composer, and a follow-up's milestoneId / riskId names a milestone or risk of the same spec by the id label given there",
|
|
33310
|
+
"sections (aiAct/economics/actuals/decisionLog/statusReports/requiredSkills/capabilityImpacts/milestones/risks/followUps/qualityFactors) accept a JSON object/array or the same JSON as a string; row ids are minted by the Composer, and a follow-up's milestoneId / riskId names a milestone or risk of the same spec by the id label given there",
|
|
32574
33311
|
"dates accepted as dd/mm/yyyy, dd.mm.yyyy or yyyy-mm-dd (stored as yyyy-mm-dd)",
|
|
32575
33312
|
"get_initiative_schema: sectionShapes, aiActVocabularies, portfolio-mode wording overlays",
|
|
32576
33313
|
"AI-Act vocabulary and id-format enforcement with the allowed values in the error",
|
|
32577
33314
|
"one pipeline: export_initiative_file takes the same spec as assemble_initiative \u2014 scores are always derived; pass `id` to update an existing initiative",
|
|
32578
|
-
"portfolio analysis: load_portfolio reads the app's full backup (by path) with the app's own sanitiser; query_initiatives, portfolio_readings, rank_initiatives, initiative_detail, compare_snapshots, board_slices and
|
|
33315
|
+
"portfolio analysis: load_portfolio reads the app's full backup (by path) with the app's own sanitiser; query_initiatives, portfolio_readings, rank_initiatives, initiative_detail, compare_snapshots, board_slices, skill_allocation and capability_allocation compute with the app's own rules (ranking and cut line, KPIs, concentration and HHI, operating model, capability impact, risk register, early warnings, Board Actions, snapshot deltas, the Board Memo's chapter data, skill allocation over time, capability allocation)",
|
|
33316
|
+
"ready-made analyses: seven recipes (board briefing, budget what-if, skill bottlenecks, capability gaps, risk review, change since a snapshot, one-page initiative brief) as prompts, as load_portfolio's nextSteps and by get_analysis_template",
|
|
32579
33317
|
"portfolio context: read_portfolio_context turns the app's exported context file into an organisation-specific interview plan and a short contextId; with it, switched-off sub-dimensions are skipped in the score, business unit / category must be the organisation's own, and skill / dependency / capability / update ids are validated (capabilities from the version-2 context file)"
|
|
32580
33318
|
]
|
|
32581
33319
|
})
|
|
@@ -32650,14 +33388,14 @@ function createServer() {
|
|
|
32650
33388
|
},
|
|
32651
33389
|
async (input2) => {
|
|
32652
33390
|
const r = loadPortfolio(input2);
|
|
32653
|
-
return r.ok ? compact(fitAnswer({ ok: true, ...portfolioSummary(r.portfolio) })) : compact({ ok: false, errors: r.errors });
|
|
33391
|
+
return r.ok ? compact({ ...fitAnswer({ ok: true, ...portfolioSummary(r.portfolio) }), nextSteps: nextSteps() }) : compact({ ok: false, errors: r.errors });
|
|
32654
33392
|
}
|
|
32655
33393
|
);
|
|
32656
33394
|
server.registerTool(
|
|
32657
33395
|
"query_initiatives",
|
|
32658
33396
|
{
|
|
32659
33397
|
title: "Query initiatives",
|
|
32660
|
-
description: "Filters the loaded portfolio's initiatives (status, business unit, category, driver, tags, saved portfolio, capability, skill, score ranges, overdue milestones or follow-ups, severe risks) and returns chosen columns per initiative, sorted, plus aggregates over the whole scope (count, budget sum, mean scores, count by status). Columns include the four dimension scores, composite, quadrant, priority bucket, NPV, risks, milestones, follow-ups, last status report, capabilities, skills and dependencies.",
|
|
33398
|
+
description: "Filters the loaded portfolio's initiatives (status, business unit, category, driver, tags, saved portfolio, capability, skill, score ranges, overdue milestones or follow-ups, severe risks, quality gaps) and returns chosen columns per initiative, sorted, plus aggregates over the whole scope (count, budget sum, mean scores, count by status). Columns include the four dimension scores, composite, quadrant, priority bucket, NPV, risks, milestones, follow-ups, quality gaps, last status report, capabilities, skills and dependencies.",
|
|
32661
33399
|
inputSchema: queryInputSchema
|
|
32662
33400
|
},
|
|
32663
33401
|
async ({ portfolio, ...q }) => withPortfolio(portfolio, (p) => queryInitiatives(p, q))
|
|
@@ -32684,7 +33422,7 @@ function createServer() {
|
|
|
32684
33422
|
"initiative_detail",
|
|
32685
33423
|
{
|
|
32686
33424
|
title: "Initiative detail",
|
|
32687
|
-
description: "One initiative in full from the loaded portfolio, by id, externalId or name: every column of query_initiatives plus description, sub-scores, economics, milestones, risks, follow-ups, recent status reports and decisions, capability impacts and the initiatives that depend on it.",
|
|
33425
|
+
description: "One initiative in full from the loaded portfolio, by id, externalId or name: every column of query_initiatives plus description, sub-scores, economics, milestones, risks, follow-ups, quality factors, recent status reports and decisions, capability impacts and the initiatives that depend on it.",
|
|
32688
33426
|
inputSchema: detailInputSchema
|
|
32689
33427
|
},
|
|
32690
33428
|
async ({ portfolio, initiative }) => withPortfolio(portfolio, (p) => initiativeDetail(p, initiative))
|
|
@@ -32702,7 +33440,7 @@ function createServer() {
|
|
|
32702
33440
|
"board_slices",
|
|
32703
33441
|
{
|
|
32704
33442
|
title: "Board memo slices",
|
|
32705
|
-
description: "The raw data of the Board Memo's chapters, from the same functions the memo and the board pack use: KPIs since the baseline snapshot, capacity (cut line totals, parked by reason, the must-do block), milestones (missed and due in the next 90 days), risks (heat map, top severe, issues and rises since the baseline) and capability impact (domains, top changes, uncovered gaps). Use it to draft a memo section, a committee note or slides; state the snapshot and export date the numbers stand on.",
|
|
33443
|
+
description: "The raw data of the Board Memo's chapters, from the same functions the memo and the board pack use: KPIs since the baseline snapshot, capacity (cut line totals, parked by reason, the must-do block), milestones (missed and due in the next 90 days), risks (heat map, top severe, issues and rises since the baseline), quality gaps (top 10, per factor) and capability impact (domains, top changes, uncovered gaps). Use it to draft a memo section, a committee note or slides; state the snapshot and export date the numbers stand on.",
|
|
32706
33444
|
inputSchema: boardSlicesInputSchema
|
|
32707
33445
|
},
|
|
32708
33446
|
async ({ portfolio, ...q }) => withPortfolio(portfolio, (p) => boardSlices(p, q))
|
|
@@ -32716,6 +33454,40 @@ function createServer() {
|
|
|
32716
33454
|
},
|
|
32717
33455
|
async ({ portfolio, ...q }) => withPortfolio(portfolio, (p) => skillAllocation(p, q))
|
|
32718
33456
|
);
|
|
33457
|
+
server.registerTool(
|
|
33458
|
+
"capability_allocation",
|
|
33459
|
+
{
|
|
33460
|
+
title: "Capability allocation",
|
|
33461
|
+
description: "Per capability of the map (or the ones named, or one domain): current, projected and target maturity, the gap, coverage, concentration and attributed spend, from the Capabilities page's own rules; every initiative linked to it (status, kind, the level it brings, planned quarter, spend, and its place in the baseline cut line: funded or parked and why), with atRisk when the projection needs parked pipeline work; the maturity path by quarter (the Timeline tab); and with excludedIds or budgetCap, which capabilities fall back, which gaps open and which initiatives are taken out (funded in the baseline but not in the what-if, or excluded).",
|
|
33462
|
+
inputSchema: capabilityAllocationInputSchema
|
|
33463
|
+
},
|
|
33464
|
+
async ({ portfolio, ...q }) => withPortfolio(portfolio, (p) => capabilityAllocation(p, q))
|
|
33465
|
+
);
|
|
33466
|
+
for (const t of ANALYSIS_TEMPLATES) {
|
|
33467
|
+
const argsSchema = Object.fromEntries(t.args.map((a) => [a.name, a.required ? external_exports.string().describe(a.description) : external_exports.string().optional().describe(a.description)]));
|
|
33468
|
+
server.registerPrompt(`bicon_${t.key}`, { title: `BICon: ${t.title}`, description: t.description, argsSchema }, (args) => {
|
|
33469
|
+
const c = checkTemplateArgs(t, args);
|
|
33470
|
+
if (!c.ok) throw new Error(c.errors.join("; "));
|
|
33471
|
+
return { messages: [{ role: "user", content: { type: "text", text: templateText(t, c.args) } }] };
|
|
33472
|
+
});
|
|
33473
|
+
}
|
|
33474
|
+
server.registerTool(
|
|
33475
|
+
"get_analysis_template",
|
|
33476
|
+
{
|
|
33477
|
+
title: "Ready-made analysis",
|
|
33478
|
+
description: `Returns the recipe of a ready-made portfolio analysis: which analysis tools to call with which inputs, what to report, and the rules (quote the tools' figures, name the export date). Follow it step by step. The same recipes are offered as prompts and as load_portfolio's nextSteps. Keys: ${ANALYSIS_TEMPLATES.map((t) => `${t.key} (${t.title})`).join("; ")}.`,
|
|
33479
|
+
inputSchema: external_exports.object({
|
|
33480
|
+
key: external_exports.enum(ANALYSIS_TEMPLATES.map((t) => t.key)).describe("Which analysis"),
|
|
33481
|
+
args: external_exports.record(external_exports.string(), external_exports.union([external_exports.string(), external_exports.number()])).optional().describe(`Inputs by name: ${ANALYSIS_TEMPLATES.filter((t) => t.args.length).map((t) => `${t.key}: ${t.args.map((a) => `${a.name}${a.required ? " (required)" : ""}`).join(", ")}`).join("; ")}`)
|
|
33482
|
+
}).strict()
|
|
33483
|
+
},
|
|
33484
|
+
async ({ key, args }) => {
|
|
33485
|
+
const t = ANALYSIS_TEMPLATES.find((x) => x.key === key);
|
|
33486
|
+
const c = checkTemplateArgs(t, args ?? {});
|
|
33487
|
+
if (!c.ok) return { ...compact({ ok: false, errors: c.errors }), isError: true };
|
|
33488
|
+
return compact({ ok: true, key: t.key, title: t.title, text: templateText(t, c.args) });
|
|
33489
|
+
}
|
|
33490
|
+
);
|
|
32719
33491
|
return server;
|
|
32720
33492
|
}
|
|
32721
33493
|
|