{
  "feature": "portfolio-management",
  "version": "1.0.0",
  "description": "Retrieve, manage, and report on investment portfolio holdings, positions, valuations, and transaction history",
  "category": "data",
  "tags": [
    "portfolio",
    "holdings",
    "valuations",
    "wealth-management",
    "positions"
  ],
  "actors": [
    {
      "id": "investor",
      "name": "Investment Client",
      "type": "human",
      "description": "End user viewing their portfolio data"
    },
    {
      "id": "financial_advisor",
      "name": "Financial Advisor",
      "type": "human",
      "description": "Advisor managing client portfolios"
    },
    {
      "id": "market_data_system",
      "name": "Market Data Provider",
      "type": "external",
      "description": "Market data provider systems offering valuations and pricing"
    },
    {
      "id": "crm_system",
      "name": "CRM System",
      "type": "external",
      "description": "CRM system storing account and client relationships"
    }
  ],
  "fields": [
    {
      "name": "account_number",
      "type": "text",
      "required": true,
      "label": "Account Number"
    },
    {
      "name": "market_value",
      "type": "number",
      "required": true,
      "label": "Market Value"
    },
    {
      "name": "valuation_date",
      "type": "date",
      "required": true,
      "label": "Valuation Date"
    },
    {
      "name": "previous_market_value",
      "type": "number",
      "required": false,
      "label": "Previous Market Value"
    },
    {
      "name": "previous_valuation_date",
      "type": "date",
      "required": false,
      "label": "Previous Valuation Date"
    },
    {
      "name": "portfolio_movement",
      "type": "number",
      "required": false,
      "label": "Portfolio Movement"
    },
    {
      "name": "total_cash",
      "type": "number",
      "required": true,
      "label": "Total Cash"
    },
    {
      "name": "unsettled_positions",
      "type": "number",
      "required": false,
      "label": "Unsettled Positions"
    },
    {
      "name": "reporting_currency",
      "type": "text",
      "required": true,
      "label": "Reporting Currency"
    },
    {
      "name": "last_update_date",
      "type": "datetime",
      "required": true,
      "label": "Last Update Date"
    },
    {
      "name": "valuation_message",
      "type": "text",
      "required": false,
      "label": "Valuation Message"
    },
    {
      "name": "instrument_code",
      "type": "text",
      "required": true,
      "label": "Instrument Code"
    },
    {
      "name": "instrument_name",
      "type": "text",
      "required": true,
      "label": "Instrument Name"
    },
    {
      "name": "instrument_type",
      "type": "text",
      "required": true,
      "label": "Instrument Type"
    },
    {
      "name": "instrument_isin",
      "type": "text",
      "required": false,
      "label": "Instrument Isin"
    },
    {
      "name": "quantity",
      "type": "number",
      "required": true,
      "label": "Quantity"
    },
    {
      "name": "instrument_price",
      "type": "number",
      "required": true,
      "label": "Instrument Price"
    },
    {
      "name": "cost_price",
      "type": "number",
      "required": true,
      "label": "Cost Price"
    },
    {
      "name": "position_market_value",
      "type": "number",
      "required": true,
      "label": "Position Market Value"
    },
    {
      "name": "position_weighting",
      "type": "number",
      "required": true,
      "label": "Position Weighting"
    },
    {
      "name": "unrealised_pnl",
      "type": "number",
      "required": true,
      "label": "Unrealised Pnl"
    },
    {
      "name": "asset_class",
      "type": "text",
      "required": false,
      "label": "Asset Class"
    },
    {
      "name": "sector",
      "type": "text",
      "required": false,
      "label": "Sector"
    },
    {
      "name": "sub_sector",
      "type": "text",
      "required": false,
      "label": "Sub Sector"
    },
    {
      "name": "currency",
      "type": "text",
      "required": true,
      "label": "Currency"
    },
    {
      "name": "fx_rate",
      "type": "number",
      "required": true,
      "label": "Fx Rate"
    },
    {
      "name": "reporting_market_value",
      "type": "number",
      "required": true,
      "label": "Reporting Market Value"
    },
    {
      "name": "share_traded_today",
      "type": "boolean",
      "required": false,
      "label": "Share Traded Today"
    },
    {
      "name": "is_live_data",
      "type": "boolean",
      "required": false,
      "label": "Is Live Data"
    },
    {
      "name": "feed_source",
      "type": "text",
      "required": false,
      "label": "Feed Source"
    },
    {
      "name": "external_account",
      "type": "text",
      "required": false,
      "label": "External Account"
    },
    {
      "name": "transaction_date",
      "type": "date",
      "required": true,
      "label": "Transaction Date"
    },
    {
      "name": "transaction_type",
      "type": "text",
      "required": true,
      "label": "Transaction Type"
    },
    {
      "name": "transaction_amount",
      "type": "number",
      "required": true,
      "label": "Transaction Amount"
    },
    {
      "name": "transaction_quantity",
      "type": "number",
      "required": false,
      "label": "Transaction Quantity"
    }
  ],
  "states": {
    "field": "portfolio_status",
    "values": [
      {
        "name": "active",
        "initial": true,
        "description": "Portfolio is active and receiving valuations"
      },
      {
        "name": "inactive",
        "description": "Portfolio has been closed or suspended"
      },
      {
        "name": "pending_valuation",
        "description": "Waiting for end-of-day valuation data"
      }
    ]
  },
  "rules": {
    "security": {
      "authentication": [
        "All portfolio endpoints require JWT authentication via [Authorize] decorator",
        "User must be authenticated to view portfolio data"
      ]
    },
    "access": [
      "User can only view portfolios linked to their CRM account",
      "CRM relationship determines which accounts are visible to which advisors/clients"
    ],
    "business": [
      "Valuation date must be a valid business day (no future dates)",
      "Market value must equal sum of all position values (quality check)",
      "Position weighting must sum to approximately 100% (accounting for cash)",
      "Unrealised PnL = position_market_value - cost_price (validation rule)",
      "If share_traded_today = false, use delayed pricing (not real-time)",
      "Unsettled positions excluded from main portfolio movement calculation",
      "Cash holdings included in total market value",
      "Currency conversion uses FX rate as of valuation_date"
    ]
  },
  "sla": {
    "portfolio_valuation_staleness": {
      "metric": "Portfolio valuation staleness",
      "max_duration": "24h",
      "reason": "Portfolio data should not be more than 24 hours old; if older, display 'data delayed' message"
    },
    "dashboard_load_time": {
      "metric": "Dashboard load time",
      "max_duration": "5s",
      "reason": "Dashboard should load within 5 seconds even for large portfolios"
    }
  },
  "outcomes": {
    "dashboard_retrieved": {
      "priority": 1,
      "given": [
        "user is authenticated",
        "user has an active CRM relationship"
      ],
      "then": [
        {
          "action": "fetch_record",
          "source": "crm_system",
          "query": "client dashboard summary",
          "fields": [
            "total_market_value",
            "portfolio_movement",
            "cash_holdings",
            "valuation_date"
          ]
        }
      ],
      "result": "Dashboard displays summary metrics: total value, movement, cash, last update time"
    },
    "portfolio_retrieved": {
      "priority": 2,
      "given": [
        "user is authenticated",
        "account_number is provided",
        "account belongs to authenticated user"
      ],
      "then": [
        {
          "action": "fetch_record",
          "source": "market_data_system",
          "query": "portfolio positions for account",
          "fields": [
            "instrument_code",
            "quantity",
            "price",
            "market_value"
          ]
        },
        {
          "action": "compute_field",
          "field": "position_weighting",
          "formula": "(position_market_value / total_market_value) * 100"
        },
        {
          "action": "compute_field",
          "field": "unrealised_pnl",
          "formula": "position_market_value - cost_price"
        },
        {
          "action": "set_field",
          "target": "last_update_date",
          "value": "current_timestamp"
        }
      ],
      "result": "Client receives list of holdings with current valuations, weightings, and P&L"
    },
    "portfolio_exported_to_excel": {
      "priority": 3,
      "given": [
        "user is authenticated",
        "portfolio_retrieved outcome succeeded"
      ],
      "then": [
        {
          "action": "export_data",
          "format": "xlsx",
          "fields": [
            "instrument_code",
            "instrument_name",
            "quantity",
            "price",
            "cost_price",
            "market_value",
            "weighting",
            "unrealised_pnl",
            "asset_class",
            "sector",
            "currency"
          ],
          "style": "professional report format with headers and formatting"
        },
        {
          "action": "emit_event",
          "event": "portfolio.exported",
          "payload": [
            "account_number",
            "export_date",
            "export_user_id",
            "file_size"
          ]
        }
      ],
      "result": "Excel file generated and ready for download",
      "error": "PORTFOLIO_EXPORT_FAILURE"
    },
    "mobile_portfolio_retrieved": {
      "priority": 4,
      "given": [
        "user is authenticated",
        "client is accessing from mobile app",
        "account_number is provided",
        "currency is provided (mobile clients can select currency)"
      ],
      "then": [
        {
          "action": "fetch_record",
          "source": "market_data_system",
          "query": "portfolio for mobile (smaller dataset)",
          "fields": [
            "instrument_code",
            "quantity",
            "position_market_value",
            "weighting",
            "unrealised_pnl"
          ]
        },
        {
          "action": "filter_field",
          "field": "position_market_value",
          "condition": "> 0"
        }
      ],
      "result": "Optimized mobile view with essential position data only"
    },
    "transaction_history_retrieved": {
      "priority": 5,
      "given": [
        "user is authenticated",
        "account_number is provided",
        "start_date and end_date are provided",
        "date_range <= 5 years"
      ],
      "then": [
        {
          "action": "fetch_record",
          "source": "market_data_system",
          "query": "transactions for account in date range",
          "fields": [
            "transaction_date",
            "transaction_type",
            "instrument_code",
            "quantity",
            "price",
            "amount"
          ]
        },
        {
          "action": "sort_data",
          "by": "transaction_date",
          "order": "descending"
        }
      ],
      "result": "Chronological transaction list for the requested period"
    },
    "market_values_updated": {
      "priority": 6,
      "given": [
        "market_data_system has new pricing",
        "end-of-day valuation cycle triggered"
      ],
      "then": [
        {
          "action": "set_field",
          "target": "instrument_price",
          "source": "market_data_system"
        },
        {
          "action": "compute_field",
          "field": "position_market_value",
          "formula": "quantity * instrument_price * fx_rate"
        },
        {
          "action": "compute_field",
          "field": "market_value",
          "formula": "sum(all position_market_values) + total_cash"
        },
        {
          "action": "compute_field",
          "field": "portfolio_movement",
          "formula": "market_value - previous_market_value"
        },
        {
          "action": "set_field",
          "target": "last_update_date",
          "value": "current_timestamp"
        },
        {
          "action": "emit_event",
          "event": "portfolio.valuation_updated",
          "payload": [
            "account_number",
            "market_value",
            "valuation_date",
            "portfolio_movement"
          ]
        }
      ],
      "result": "All portfolios updated with latest market prices"
    },
    "access_denied": {
      "priority": 1,
      "error": "PORTFOLIO_ACCESS_DENIED",
      "given": [
        {
          "field": "user_id",
          "source": "session",
          "operator": "not_exists"
        }
      ],
      "result": "401 Unauthorized - user must authenticate first"
    },
    "account_not_found": {
      "priority": 2,
      "error": "PORTFOLIO_ACCOUNT_NOT_FOUND",
      "given": [
        {
          "field": "account_number",
          "source": "input",
          "operator": "not_exists"
        }
      ],
      "result": "404 Not Found - account does not exist"
    },
    "portfolio_data_stale": {
      "priority": 3,
      "given": [
        {
          "field": "last_update_date",
          "source": "db",
          "operator": "lt",
          "value": "now - 24h"
        }
      ],
      "then": [
        {
          "action": "set_field",
          "target": "valuation_message",
          "value": "Data is delayed. Last update: {last_update_date}"
        }
      ],
      "result": "Portfolio returned with warning message about data freshness"
    },
    "invalid_valuation_date": {
      "priority": 4,
      "error": "PORTFOLIO_FUTURE_DATE_INVALID",
      "given": [
        {
          "field": "valuation_date",
          "source": "input",
          "operator": "gt",
          "value": "today"
        }
      ],
      "result": "400 Bad Request - valuation date cannot be in the future"
    },
    "date_range_too_large": {
      "priority": 5,
      "error": "PORTFOLIO_DATE_RANGE_EXCEEDED",
      "given": [
        {
          "field": "date_range",
          "source": "computed",
          "operator": "gt",
          "value": "5 years"
        }
      ],
      "result": "400 Bad Request - transaction history limited to 5-year lookback"
    }
  },
  "errors": [
    {
      "code": "PORTFOLIO_ACCESS_DENIED",
      "status": 401,
      "message": "You do not have permission to view this portfolio. Please verify the account number and your access rights."
    },
    {
      "code": "PORTFOLIO_ACCOUNT_NOT_FOUND",
      "status": 404,
      "message": "The requested account could not be found. Please verify the account number."
    },
    {
      "code": "PORTFOLIO_FUTURE_DATE_INVALID",
      "status": 400,
      "message": "Valuation date cannot be in the future. Please provide a current or past date."
    },
    {
      "code": "PORTFOLIO_DATE_RANGE_EXCEEDED",
      "status": 400,
      "message": "Transaction history is limited to a 5-year lookback period. Please adjust your date range."
    },
    {
      "code": "PORTFOLIO_VALUATION_FAILURE",
      "status": 500,
      "message": "Unable to retrieve current portfolio valuation. Please try again later."
    },
    {
      "code": "PORTFOLIO_EXPORT_FAILURE",
      "status": 500,
      "message": "Unable to export portfolio to Excel. Please try again later."
    }
  ],
  "events": [
    {
      "name": "portfolio.dashboard_viewed",
      "description": "User viewed dashboard summary",
      "payload": [
        "account_number",
        "user_id",
        "view_date",
        "device_type"
      ]
    },
    {
      "name": "portfolio.positions_retrieved",
      "description": "Full position list retrieved",
      "payload": [
        "account_number",
        "position_count",
        "total_value",
        "retrieval_date"
      ]
    },
    {
      "name": "portfolio.exported",
      "description": "Portfolio exported to Excel",
      "payload": [
        "account_number",
        "export_date",
        "export_user_id",
        "file_size"
      ]
    },
    {
      "name": "portfolio.valuation_updated",
      "description": "Portfolio valuation refreshed with new market prices",
      "payload": [
        "account_number",
        "market_value",
        "valuation_date",
        "portfolio_movement",
        "position_count"
      ]
    },
    {
      "name": "portfolio.transaction_retrieved",
      "description": "Transaction history retrieved",
      "payload": [
        "account_number",
        "start_date",
        "end_date",
        "transaction_count"
      ]
    }
  ],
  "related": [
    {
      "feature": "market-data-feeds",
      "type": "required",
      "reason": "Requires real-time and EOD pricing data"
    },
    {
      "feature": "reference-data-lookup",
      "type": "recommended",
      "reason": "Instrument classification and corporate action data"
    }
  ],
  "agi": {
    "goals": [
      {
        "id": "reliable_portfolio_management",
        "description": "Retrieve, manage, and report on investment portfolio holdings, positions, valuations, and transaction history",
        "success_metrics": [
          {
            "metric": "data_accuracy",
            "target": "100%",
            "measurement": "Records matching source of truth"
          },
          {
            "metric": "duplicate_rate",
            "target": "0%",
            "measurement": "Duplicate records detected post-creation"
          }
        ],
        "constraints": [
          {
            "type": "performance",
            "description": "Data consistency must be maintained across concurrent operations",
            "negotiable": false
          }
        ]
      }
    ],
    "autonomy": {
      "level": "supervised",
      "human_checkpoints": [
        "before transitioning to a terminal state"
      ],
      "escalation_triggers": [
        "error_rate > 5"
      ]
    },
    "safety": {
      "action_permissions": [
        {
          "action": "dashboard_retrieved",
          "permission": "autonomous"
        },
        {
          "action": "portfolio_retrieved",
          "permission": "autonomous"
        },
        {
          "action": "portfolio_exported_to_excel",
          "permission": "autonomous"
        },
        {
          "action": "mobile_portfolio_retrieved",
          "permission": "autonomous"
        },
        {
          "action": "transaction_history_retrieved",
          "permission": "autonomous"
        },
        {
          "action": "market_values_updated",
          "permission": "supervised"
        },
        {
          "action": "access_denied",
          "permission": "autonomous"
        },
        {
          "action": "account_not_found",
          "permission": "autonomous"
        },
        {
          "action": "portfolio_data_stale",
          "permission": "autonomous"
        },
        {
          "action": "invalid_valuation_date",
          "permission": "autonomous"
        },
        {
          "action": "date_range_too_large",
          "permission": "autonomous"
        }
      ]
    },
    "tradeoffs": [
      {
        "prefer": "data_integrity",
        "over": "performance",
        "reason": "data consistency must be maintained across all operations"
      }
    ],
    "coordination": {
      "protocol": "orchestrated",
      "consumes": [
        {
          "capability": "market_data_feeds",
          "from": "market-data-feeds",
          "fallback": "degrade"
        }
      ]
    }
  },
  "extensions": {
    "tech_stack": {
      "language": "C#",
      "framework": "ASP.NET Core 5+",
      "database": "SQL Server (market data provider schemas)",
      "orm": "Entity Framework Core"
    },
    "source": {
      "repo": "Reference implementation",
      "project": "Wealth Management Platform",
      "entry_points": [
        "Wealth.Api/Controllers/PortfolioController.cs",
        "Framework/Entities/PortfolioResult.cs",
        "Framework/Entities/PositionResult.cs"
      ]
    }
  }
}