@bpmsoftwaresolutions/ai-engine-client 1.1.70 → 1.1.71

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +2 -1
  2. package/package.json +1 -1
  3. package/src/index.js +124 -0
package/README.md CHANGED
@@ -654,7 +654,7 @@ Generated scripts are ephemeral helpers, not source-of-truth. The server keeps S
654
654
 
655
655
  ### Governed Implementation
656
656
 
657
- Current package version: `1.0.0-beta.13`.
657
+ Current package version: `1.1.71`.
658
658
 
659
659
  | Method | Description |
660
660
  |---|---|
@@ -763,6 +763,7 @@ Engine returns the required gate and next action.
763
763
  | `getPortfolioProject(projectId)` | Project detail via portfolio. |
764
764
  | `getPortfolioReport()` | Full portfolio report. |
765
765
  | `getPortfolioBundle()` | Portfolio bundle. |
766
+ | `getPortfolioClosureReadiness({ projectLimit, includeInactive, includeLogaPortfolioProjection, includeLogaRoadmapProjections })` | Read-only portfolio readiness view that composes bundle, project list, roadmap summaries, active items, and open task counts into a conservative closure-ready flag. |
766
767
 
767
768
  ### Self-Learning
768
769
  | Method | Description |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bpmsoftwaresolutions/ai-engine-client",
3
- "version": "1.1.70",
3
+ "version": "1.1.71",
4
4
  "description": "Thin npm client for the AI Engine operator and retrieval APIs",
5
5
  "type": "module",
6
6
  "main": "./src/index.js",
package/src/index.js CHANGED
@@ -5767,6 +5767,130 @@ export class AIEngineClient {
5767
5767
  return this._request('/api/operator/portfolio/bundle');
5768
5768
  }
5769
5769
 
5770
+ async getPortfolioClosureReadiness({
5771
+ projectLimit = 25,
5772
+ includeInactive = false,
5773
+ includeLogaPortfolioProjection = false,
5774
+ includeLogaRoadmapProjections = false,
5775
+ } = {}) {
5776
+ const portfolioBundle = await this.getPortfolioBundle();
5777
+ const projectListPayload = await this.listProjects({
5778
+ limit: projectLimit,
5779
+ includeInactive,
5780
+ });
5781
+
5782
+ const bundleProjects = Array.isArray(portfolioBundle?.projects)
5783
+ ? portfolioBundle.projects.filter((project) => isPlainObject(project))
5784
+ : [];
5785
+ const listedProjects = Array.isArray(projectListPayload?.projects)
5786
+ ? projectListPayload.projects.filter((project) => isPlainObject(project))
5787
+ : Array.isArray(projectListPayload)
5788
+ ? projectListPayload.filter((project) => isPlainObject(project))
5789
+ : [];
5790
+ const activeProjects = listedProjects.length > 0 ? listedProjects : bundleProjects;
5791
+ const logaPortfolioProjection = includeLogaPortfolioProjection
5792
+ ? await this.getLogaProjectPortfolioProjection()
5793
+ : null;
5794
+ const logaRoadmapProjections = {};
5795
+
5796
+ const projectReadiness = [];
5797
+ for (const project of activeProjects) {
5798
+ const projectId = cleanText(project.project_id) || cleanText(project.projectId);
5799
+ if (!projectId) {
5800
+ continue;
5801
+ }
5802
+
5803
+ const roadmapSummaryPayload = await this.getProjectRoadmapSummary(projectId);
5804
+ const roadmapSummary = isPlainObject(roadmapSummaryPayload?.summary) ? roadmapSummaryPayload.summary : {};
5805
+ const activeItemPayload = await this.getProjectRoadmapActiveItem(projectId);
5806
+ const activeItem = isPlainObject(activeItemPayload?.active_item)
5807
+ ? activeItemPayload.active_item
5808
+ : isPlainObject(activeItemPayload)
5809
+ ? activeItemPayload
5810
+ : {};
5811
+ const openTasksPayload = await this.listProjectOpenTasks(projectId);
5812
+ const openTasks = Array.isArray(openTasksPayload?.tasks)
5813
+ ? openTasksPayload.tasks.filter((task) => isPlainObject(task))
5814
+ : Array.isArray(openTasksPayload)
5815
+ ? openTasksPayload.filter((task) => isPlainObject(task))
5816
+ : [];
5817
+
5818
+ const activeItemStatus = cleanText(activeItem.item_status) || cleanText(activeItem.status);
5819
+ const completionPercentage = Number(
5820
+ roadmapSummary.completion_percentage
5821
+ ?? roadmapSummary.completion_pct
5822
+ ?? roadmapSummary.progress_percentage
5823
+ ?? 0,
5824
+ );
5825
+ const totalItems = Number(
5826
+ roadmapSummary.total_items
5827
+ ?? roadmapSummary.item_count
5828
+ ?? roadmapSummary.total_count
5829
+ ?? 0,
5830
+ );
5831
+ const openItems = Number(
5832
+ roadmapSummary.open_items
5833
+ ?? roadmapSummary.open_item_count
5834
+ ?? roadmapSummary.remaining_items
5835
+ ?? openTasks.length,
5836
+ );
5837
+ const terminalStatuses = new Set(['accepted', 'verified', 'done', 'completed', 'closed']);
5838
+ const activeItemReady = !activeItemStatus || terminalStatuses.has(activeItemStatus.toLowerCase());
5839
+ const projectReady = openItems === 0 && openTasks.length === 0 && activeItemReady && completionPercentage >= 100;
5840
+ const blockingReason = projectReady
5841
+ ? null
5842
+ : [
5843
+ openTasks.length > 0 ? `${openTasks.length} open task(s) remain` : null,
5844
+ openItems > 0 ? `${openItems} roadmap item(s) remain open` : null,
5845
+ !activeItemReady ? `active item status is ${activeItemStatus || 'missing'}` : null,
5846
+ completionPercentage < 100 ? `completion is ${completionPercentage.toFixed(1)}%` : null,
5847
+ ].filter(Boolean).join('; ') || 'portfolio closure readiness is not satisfied';
5848
+
5849
+ const roadmapCompletion = {
5850
+ completion_percentage: completionPercentage,
5851
+ total_items: totalItems,
5852
+ open_items: openItems,
5853
+ completed_items: Math.max(totalItems - openItems, 0),
5854
+ };
5855
+ const projectReadinessEntry = {
5856
+ project: {
5857
+ project_id: projectId,
5858
+ project_name: cleanText(project.project_name) || cleanText(project.projectName) || null,
5859
+ project_slug: cleanText(project.project_slug) || cleanText(project.projectSlug) || null,
5860
+ process_status: cleanText(project.process_status) || cleanText(project.processStatus) || null,
5861
+ charter_status: cleanText(project.charter_status) || cleanText(project.charterStatus) || null,
5862
+ },
5863
+ roadmap_completion: roadmapCompletion,
5864
+ active_item: activeItem,
5865
+ open_task_count: openTasks.length,
5866
+ closure_ready: projectReady,
5867
+ blocking_reason: blockingReason,
5868
+ };
5869
+ if (includeLogaRoadmapProjections) {
5870
+ projectReadinessEntry.loga_roadmap_projection = await this.getLogaProjectRoadmapProjection(projectId);
5871
+ logaRoadmapProjections[projectId] = projectReadinessEntry.loga_roadmap_projection;
5872
+ }
5873
+ projectReadiness.push(projectReadinessEntry);
5874
+ }
5875
+
5876
+ const closureReady = projectReadiness.length === 0 || projectReadiness.every((project) => project.closure_ready === true);
5877
+ const blockingProject = projectReadiness.find((project) => project.closure_ready !== true);
5878
+
5879
+ return {
5880
+ portfolio_summary: isPlainObject(portfolioBundle?.summary) ? portfolioBundle.summary : {},
5881
+ portfolio_bundle: portfolioBundle,
5882
+ active_projects: projectReadiness,
5883
+ closure_readiness: closureReady,
5884
+ blocking_reason: closureReady ? null : `${cleanText(blockingProject?.project?.project_name) || cleanText(blockingProject?.project?.project_id) || 'project'}: ${blockingProject?.blocking_reason || 'closure readiness is not satisfied'}`,
5885
+ blocking_project_id: closureReady ? null : cleanText(blockingProject?.project?.project_id),
5886
+ blocking_project_name: closureReady ? null : cleanText(blockingProject?.project?.project_name),
5887
+ project_count: projectReadiness.length,
5888
+ open_task_count: projectReadiness.reduce((total, project) => total + Number(project.open_task_count || 0), 0),
5889
+ loga_portfolio_projection: logaPortfolioProjection,
5890
+ loga_roadmap_projections: includeLogaRoadmapProjections ? logaRoadmapProjections : null,
5891
+ };
5892
+ }
5893
+
5770
5894
  // ─── Self-Learning ─────────────────────────────────────────────────────────
5771
5895
 
5772
5896
  async getSelfLearningPosture({ workflowRunId, limit } = {}) {