Cross-Project Reporting

Aggregate and analyze data across multiple ACC projects for portfolio insights.

Workflow Overview

Multiple Projects
Project A
Project B
Project C
Data Collection
Issues
RFIs
Submittals
Checklists
Aggregation
Combine Data
Normalize Fields
Add Project Context
Reports
Portfolio Summary
Cross-Project Metrics
Executive Dashboard
Projects → Data → Aggregation → Reports

CLI Approach

Get All Projects

HUB_ID="b.hub-id"

# List all projects
raps project list "$HUB_ID" --output json > projects.json

# Filter active construction projects
cat projects.json | jq '[.[] | select(.status == "active")]' > active-projects.json

Aggregate Issues Across Projects

# Collect issues from all projects
echo '[]' > all-issues.json

jq -r '.[].id' active-projects.json | while read proj_id; do
  PROJ_NAME=$(jq -r ".[] | select(.id == \"$proj_id\") | .name" active-projects.json)

  raps acc issue list "$proj_id" --output json 2>/dev/null | \
    jq --arg proj "$proj_id" --arg name "$PROJ_NAME" \
    '[.[] | . + {projectId: $proj, projectName: $name}]' >> temp-issues.json
done

# Combine all
cat temp-issues.json | jq -s 'flatten' > all-issues.json
rm temp-issues.json

Portfolio Summary

# Generate portfolio-wide summary
cat all-issues.json | jq '
  {
    totalIssues: length,
    openIssues: [.[] | select(.status == "open")] | length,
    byProject: (group_by(.projectName) | map({
      project: .[0].projectName,
      total: length,
      open: [.[] | select(.status == "open")] | length
    })),
    byPriority: (group_by(.priority) | map({
      priority: .[0].priority,
      count: length
    }))
  }'

Cross-Project Comparison

# Compare metrics across projects
echo "# Portfolio Comparison" > portfolio-report.md
echo "" >> portfolio-report.md

jq -r '.[].id' active-projects.json | while read proj_id; do
  PROJ_NAME=$(jq -r ".[] | select(.id == \"$proj_id\") | .name" active-projects.json)

  # Get metrics
  ISSUES=$(jq --arg p "$proj_id" '[.[] | select(.projectId == $p)] | length' all-issues.json)
  OPEN=$(jq --arg p "$proj_id" '[.[] | select(.projectId == $p and .status == "open")] | length' all-issues.json)

  echo "## $PROJ_NAME" >> portfolio-report.md
  echo "- Total Issues: $ISSUES" >> portfolio-report.md
  echo "- Open Issues: $OPEN" >> portfolio-report.md
  echo "" >> portfolio-report.md
done

CI/CD Pipeline

# .github/workflows/portfolio-report.yml
name: Portfolio Reporting

on:
  schedule:
    - cron: '0 6 * * 1'  # Weekly on Monday
  workflow_dispatch:

env:
  HUB_ID: ${{ secrets.ACC_HUB_ID }}

jobs:
  portfolio-report:
    runs-on: ubuntu-latest
    steps:
      - name: Install RAPS
        run: cargo install raps

      - name: Authenticate
        env:
          APS_CLIENT_ID: ${{ secrets.APS_CLIENT_ID }}
          APS_CLIENT_SECRET: ${{ secrets.APS_CLIENT_SECRET }}
          APS_REFRESH_TOKEN: ${{ secrets.APS_REFRESH_TOKEN }}
        run: raps auth refresh

      - name: Get all projects
        env:
          APS_CLIENT_ID: ${{ secrets.APS_CLIENT_ID }}
          APS_CLIENT_SECRET: ${{ secrets.APS_CLIENT_SECRET }}
        run: |
          mkdir -p ./portfolio

          raps project list "$HUB_ID" --output json > ./portfolio/projects.json
          jq '[.[] | select(.status == "active")]' ./portfolio/projects.json > ./portfolio/active.json

      - name: Collect cross-project data
        env:
          APS_CLIENT_ID: ${{ secrets.APS_CLIENT_ID }}
          APS_CLIENT_SECRET: ${{ secrets.APS_CLIENT_SECRET }}
        run: |
          echo '[]' > ./portfolio/all-issues.json
          echo '[]' > ./portfolio/all-rfis.json
          echo '[]' > ./portfolio/all-submittals.json

          jq -r '.[].id' ./portfolio/active.json | while read proj_id; do
            NAME=$(jq -r ".[] | select(.id == \"$proj_id\") | .name" ./portfolio/active.json)

            # Issues
            raps acc issue list "$proj_id" --output json 2>/dev/null | \
              jq --arg p "$proj_id" --arg n "$NAME" '[.[] | . + {projectId: $p, projectName: $n}]' >> ./portfolio/temp-issues.json || true

            # RFIs
            raps acc rfi list "$proj_id" --output json 2>/dev/null | \
              jq --arg p "$proj_id" --arg n "$NAME" '[.[] | . + {projectId: $p, projectName: $n}]' >> ./portfolio/temp-rfis.json || true

            # Submittals
            raps acc submittal list "$proj_id" --output json 2>/dev/null | \
              jq --arg p "$proj_id" --arg n "$NAME" '[.[] | . + {projectId: $p, projectName: $n}]' >> ./portfolio/temp-submittals.json || true
          done

          # Combine files
          cat ./portfolio/temp-issues.json 2>/dev/null | jq -s 'flatten' > ./portfolio/all-issues.json || echo '[]' > ./portfolio/all-issues.json
          cat ./portfolio/temp-rfis.json 2>/dev/null | jq -s 'flatten' > ./portfolio/all-rfis.json || echo '[]' > ./portfolio/all-rfis.json
          cat ./portfolio/temp-submittals.json 2>/dev/null | jq -s 'flatten' > ./portfolio/all-submittals.json || echo '[]' > ./portfolio/all-submittals.json

      - name: Generate portfolio report
        run: |
          echo "# Portfolio Report" > ./portfolio/report.md
          echo "Generated: $(date)" >> ./portfolio/report.md
          echo "" >> ./portfolio/report.md

          PROJECTS=$(jq 'length' ./portfolio/active.json)
          echo "## Overview" >> ./portfolio/report.md
          echo "Active Projects: $PROJECTS" >> ./portfolio/report.md
          echo "" >> ./portfolio/report.md

          # Issues summary
          echo "## Issues Summary" >> ./portfolio/report.md
          TOTAL_ISSUES=$(jq 'length' ./portfolio/all-issues.json)
          OPEN_ISSUES=$(jq '[.[] | select(.status == "open")] | length' ./portfolio/all-issues.json)
          echo "- Total: $TOTAL_ISSUES" >> ./portfolio/report.md
          echo "- Open: $OPEN_ISSUES" >> ./portfolio/report.md
          echo "" >> ./portfolio/report.md

          # By project table
          echo "## By Project" >> ./portfolio/report.md
          echo "| Project | Issues | Open | RFIs | Submittals |" >> ./portfolio/report.md
          echo "|---------|--------|------|------|------------|" >> ./portfolio/report.md

          jq -r '.[].name' ./portfolio/active.json | while read name; do
            ISSUES=$(jq --arg n "$name" '[.[] | select(.projectName == $n)] | length' ./portfolio/all-issues.json)
            OPEN=$(jq --arg n "$name" '[.[] | select(.projectName == $n and .status == "open")] | length' ./portfolio/all-issues.json)
            RFIS=$(jq --arg n "$name" '[.[] | select(.projectName == $n)] | length' ./portfolio/all-rfis.json)
            SUBS=$(jq --arg n "$name" '[.[] | select(.projectName == $n)] | length' ./portfolio/all-submittals.json)
            echo "| $name | $ISSUES | $OPEN | $RFIS | $SUBS |" >> ./portfolio/report.md
          done

      - name: Upload report
        uses: actions/upload-artifact@v4
        with:
          name: portfolio-report
          path: ./portfolio/

Pipeline Flow

Weekly Monday
Schedule
Data Collection
List Projects
Get Issues/RFIs/Submittals
Aggregation
Combine Data
Add Project Context
Reports
report.md
all-issues.json
portfolio metrics

MCP Integration

Get Portfolio Overview

User: "Give me a summary across all our active construction projects"
UserAI AssistantRAPS MCPACC Cloud
UserPortfolio summaryAI
AIaps_hub_list()MCP
AIaps_project_list(hub_id)MCP
Loop: For each project
AIaps_acc_issue_list, rfi_list, submittal_listACC
AI: Aggregate and analyze
AIPortfolio dashboardUser

AI Response:

Portfolio Summary - 5 Active Projects

ProjectIssuesRFIsSubmittalsStatus
Main Street Tower45 (12 open)2389On Track
Riverside Complex32 (8 open)1867Minor Delays
Tech Campus Ph228 (5 open)1245On Track
Harbor View56 (18 open)31102At Risk
Metro Station19 (3 open)834On Track

Portfolio Totals:

  • Total Open Issues: 46
  • Total Open RFIs: 28
  • Pending Submittals: 45

Attention: Harbor View has high open issue count.

Compare Projects

User: "Compare issue resolution rates across projects"

AI Response:

Issue Resolution Rate Comparison:

ProjectCreatedResolvedRateAvg Days
Tech Campus Ph2454089%5.2
Metro Station282486%6.1
Main Street Tower786583%7.4
Riverside Complex524281%8.2
Harbor View895865%12.3

Insight: Harbor View has significantly lower resolution rate and longer time to resolve. Consider increasing issue management resources.


Dashboard Queries

Critical Items Across Portfolio

cat all-issues.json | jq '
  [.[] | select(.priority == "critical" and .status == "open")] |
  group_by(.projectName) |
  map({project: .[0].projectName, criticalOpen: length, issues: [.[] | .title]})'

Overdue Items Summary

TODAY=$(date +%Y-%m-%d)

echo "Overdue items by project:"
for type in issues rfis submittals; do
  echo "=== $type ==="
  cat "./portfolio/all-${type}.json" | jq --arg today "$TODAY" '
    [.[] | select(.dueDate != null and .dueDate < $today and .status == "open")] |
    group_by(.projectName) |
    map({project: .[0].projectName, overdue: length})'
done

Weekly Trend

# Issues created per week across portfolio
cat all-issues.json | jq '
  group_by(.createdAt[:10] | strptime("%Y-%m-%d") | strftime("%Y-W%V")) |
  map({week: .[0].createdAt[:10] | strptime("%Y-%m-%d") | strftime("%Y-W%V"), created: length}) |
  sort_by(.week)'