Міжпроєктна звітність
Агрегуйте та аналізуйте дані по кількох проєктах ACC для портфельних інсайтів.
Огляд робочого процесу
Кілька проєктів
Проєкт A
Проєкт B
Проєкт C
Збір даних
Проблеми
RFI
Подачі
Чек-листи
Агрегація
Об’єднання даних
Нормалізація полів
Додавання контексту проєкту
Звіти
Портфельне зведення
Міжпроєктні метрики
Дашборд для керівництва
Проєкти → Дані → Агрегація → Звіти
Підхід через CLI
Отримання всіх проєктів
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
Агрегація проблем по проєктах
# 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
Портфельне зведення
# 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
}))
}'
Порівняння проєктів
# 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-конвеєр
# .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_ACCESS_TOKEN: ${{ secrets.APS_ACCESS_TOKEN }}
APS_REFRESH_TOKEN: ${{ secrets.APS_REFRESH_TOKEN }}
run: raps auth login --token "$APS_ACCESS_TOKEN" --refresh-token "$APS_REFRESH_TOKEN" --non-interactive
- 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/
Потік конвеєра
Щотижня, понеділок
Розклад
→
Збір даних
Список проєктів
Отримання проблем/RFI/подач
→
Агрегація
Об’єднання даних
Додавання контексту проєкту
→
Звіти
report.md
all-issues.json
портфельні метрики
Інтеграція з MCP
Огляд портфеля
User: "Give me a summary across all our active construction projects"
КористувачAI-асистентRAPS MCPACC Cloud
Корист.Зведення по портфелюAI
AIaps_hub_list()MCP
AIaps_project_list(hub_id)MCP
Цикл: Для кожного проєкту
AIaps_acc_issue_list, rfi_list, submittal_listACC
AI: Агрегація та аналіз
AIПортфельний дашбордКорист.
Відповідь AI:
Зведення по портфелю - 5 активних проєктів
Проєкт Проблеми RFI Подачі Статус Main Street Tower 45 (12 відкр.) 23 89 За графіком Riverside Complex 32 (8 відкр.) 18 67 Незначні затримки Tech Campus Ph2 28 (5 відкр.) 12 45 За графіком Harbor View 56 (18 відкр.) 31 102 Під загрозою Metro Station 19 (3 відкр.) 8 34 За графіком Підсумки по портфелю:
- Всього відкритих проблем: 46
- Всього відкритих RFI: 28
- Подач в очікуванні: 45
Увага: Harbor View має високу кількість відкритих проблем.
Порівняння проєктів
User: "Compare issue resolution rates across projects"
Відповідь AI:
Порівняння швидкості вирішення проблем:
Проєкт Створено Вирішено Частка Сер. днів Tech Campus Ph2 45 40 89% 5.2 Metro Station 28 24 86% 6.1 Main Street Tower 78 65 83% 7.4 Riverside Complex 52 42 81% 8.2 Harbor View 89 58 65% 12.3 Висновок: Harbor View має значно нижчу швидкість вирішення та довший час обробки. Рекомендується збільшити ресурси на управління проблемами.
Запити для дашборду
Критичні елементи по портфелю
cat all-issues.json | jq '
[.[] | select(.priority == "critical" and .status == "open")] |
group_by(.projectName) |
map({project: .[0].projectName, criticalOpen: length, issues: [.[] | .title]})'
Зведення прострочених елементів
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
Тижневий тренд
# 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)'