CRM portfolio read strategies (Dynamics 365 Sales Hub)

Read-only harvest for Dynamics 365 account list views (portfolio) and account record enrich (form + related subgrids). Cockpit orchestrates; LCDL provides governed tasks.

Updated

Entry

  • Edge tab on *.crm*.dynamics.com with pagetype=entitylist&etn=account (list phase).
  • Operator view: Accounts → My Active Accounts (or any saved account list view).
  • Record enrich: navigate to pagetype=entityrecord&etn=account&id={account_id} (read-only).
  • Host: softserveinc.crm4.dynamics.com (tenant-specific).

CDP policy

  • Reuse the operator's existing Dynamics tab (pick_crm_portfolio_cdp_page_target for list; same tab for record enrich).
  • Attach with selective CDP (connect_owa_page_via_selective_cdp) — same bridge as OWA/Org Explorer; focus emulation enabled on connect when OS focus steal is off.
  • Read the main Dynamics frame via Page.createIsolatedWorld + Runtime.evaluate.
  • Read-only: DOM evaluate, .ag-body-viewport scroll, and navigation to entityrecord URLs only.
  • Background-tab grid harvest may stall without focus emulation — see EDGE-CDP-ATTACH.md.

Allowed

Action Phase
List grid scroll + evaluate List
Navigate to pagetype=entityrecord&etn=account&id=… Enrich
Read form fields (description, phone, industry, …) Enrich
Subgrid scroll + evaluate (Contacts, Opportunities) Enrich
Allowlisted click on Contacts / Opportunities related tab only (loads subgrid) Enrich
Optional: read GET OData responses on record page (no token export) Enrich

Forbidden

Action
New, Refresh, Share, Email a Link, Run Report (toolbar)
Save, Save & Close, Assign, Delete, Qualify, Close as Won/Lost
Row actions on contacts or opportunities (compose, send, assign)
Opening records via row double-click when harvest can use URL navigation instead

Tasks

Task Purpose
crm_probe_portfolio_health Account grid visible, portfolio URL
crm_probe_list_view Assert current view_name matches expected (guided multi-view)
crm_list_portfolio_accounts_viewport Visible virtualized rows only
crm_list_portfolio_accounts_scroll Scroll harvest with dedupe on row-id
crm_harvest_account_record Navigate entityrecord URL; form + Contacts/Opportunities subgrids

Input: { "session_ref": "…" } where the session page is a CdpOwaPageShim on a Dynamics tab.

List scroll optional input: { "max_scroll_rounds": 80 }.

Record enrich input: { "session_ref": "…", "account_id": "…", "record_url_template": "…" }.

Wire shapes

List harvest

{
  "ok": true,
  "read_only": true,
  "view_name": "My Active Accounts",
  "page_url": "https://…/main.aspx?pagetype=entitylist&etn=account…",
  "record_url_template": "https://…/main.aspx?pagetype=entityrecord&etn=account&id={account_id}",
  "accounts_seen": 42,
  "accounts": [
    {
      "account_id": "45efa899-441a-ec11-b6e6-6045bd8de0ad",
      "name": "Reltio, Inc.",
      "relationship_type": "Customer",
      "business_unit": "NA-SH2-4",
      "delivery_unit": "Delivery E1-3",
      "owner": "Iurii Liashenko (Busy)",
      "hq_territory": "OR",
      "new_logo_model": null,
      "tal_status": null
    }
  ]
}

account_id comes from the grid row attribute row-id (Dynamics GUID).

Record enrich

{
  "ok": true,
  "read_only": true,
  "account_id": "45efa899-441a-ec11-b6e6-6045bd8de0ad",
  "name": "Reltio, Inc.",
  "description": "…",
  "website": "https://example.com",
  "main_phone": "+1 …",
  "industry": "Software",
  "contacts": [
    {
      "contact_id": "…",
      "full_name": "Jane Doe",
      "email": "jane@example.com",
      "job_title": "CTO"
    }
  ],
  "opportunities": [
    {
      "opportunity_id": "…",
      "name": "FY25 Expansion",
      "stage": "Qualify",
      "amount": "$100,000"
    }
  ]
}

Consumer

forge-cockpit-webcockpit_server/crm/portfolio_harvest.py, enrich_harvest.py, guided_harvest.py, ingest.py. Operator flow: crm-portfolio-harvest.md. Dual architecture: CRM-PORTFOLIO-DUAL-ARCHITECTURE.mdcrm-portfolio-dual-architecture.md.