Skip to content

feat: external exposure-provider integration, GXM adapter phase 1 - #1386

Draft
benhayes21 wants to merge 1 commit into
mainfrom
feature/gxm-adapter
Draft

feat: external exposure-provider integration, GXM adapter phase 1#1386
benhayes21 wants to merge 1 commit into
mainfrom
feature/gxm-adapter

Conversation

@benhayes21

Copy link
Copy Markdown
Contributor

Closes #1385

Summary

  • New Django app src/server/oasisapi/external_providers/ implementing a provider-agnostic external exposure-data layer
  • GXM (Global Exposure Model) is the first concrete provider
  • Two use cases: full country-level OED retrieval (UC1) and attribute enrichment of an existing location file (UC2)
  • All endpoints gated behind EXTERNAL_PROVIDERS_ENABLED (default false) — zero change to existing behaviour when off

What changed

New app (external_providers/)

  • adapters/base.py — abstract ExposureProvider interface
  • adapters/gxm.py — GXM OAuth2 client-credentials + chunked HTTP streaming (CSV + Parquet)
  • models.pyExternalJob (UUID PK, tracks status/task_id/result/audit) + ExternalProviderSettings (admin-editable creds)
  • services.py — orchestration, non-destructive merge for UC2, iniconf-compatible settings fallback
  • tasks.py — Celery task wrappers dispatched to oasis-internal-worker
  • views.py / urls.py — DRF views with drf-spectacular annotations; {provider} is a URL segment, not a slug
  • migrations/0001_initial.py
  • tests/ — unit tests for merge logic, service flows, view layer (flag-off/202/validation/admin); GXM adapter integration tests auto-skip without OASIS_GXM_BASE_URL

Modified

  • portfolios/models.py — adds location_file_source / location_file_external_provider / location_file_audit
  • portfolios/v2_api/serializers.pylocation_file dict gains source, external_provider, audit_url (additive)
  • portfolios/migrations/0011 — new portfolio fields
  • settings/base.pyEXTERNAL_PROVIDERS_ENABLED + GXM_BASE_URL/CLIENT_ID/CLIENT_SECRET
  • swagger.py — registers v2-external-providers URL namespace

Test plan

  • EXTERNAL_PROVIDERS_ENABLED=false (default): pytest src/server/oasisapi/ — no regressions, all existing tests pass
  • GET /portfolios/{id}/ with feature off: location_file.source == "user_upload", audit_url == null (backward-compat regression test in test_views.py)
  • Unit tests: pytest src/server/oasisapi/external_providers/tests/test_services.py -k "not django_db" — pure merge/utility logic, no DB needed
  • With dummy GXM up (OASIS_GXM_BASE_URL=http://localhost:8000): full UC1 and UC2 end-to-end via curl (see external_providers/README.md)

Known gaps (tracked in #1385)

  1. OED validation before persisting provider responses
  2. Dummy GXM service integration tests (tests are written; need the container)
  3. Reset location_file_source when user manually re-uploads via existing endpoint

🤖 Generated with Claude Code

Adds a provider-agnostic external exposure-data layer to the Platform 2
API, with GXM as the first concrete provider.  Two use cases are
implemented:

- UC1 (full exposure retrieval): POST providers/{p}/portfolios/{id}/external_location_file/
  fetches a country-level (or bbox-filtered) OED location file from the
  named provider and attaches it to the portfolio.
- UC2 (attribute enrichment): POST providers/{p}/portfolios/{id}/external_enrich/
  fills blank OED fields (OccupancyCode, ConstructionCode, etc.) from a
  per-point GXM lookup without overwriting user-supplied values.

Both endpoints are async (202 + job_id); progress is polled via
GET providers/{p}/portfolios/{id}/external_jobs/{job_id}/.
An admin-only GET/PUT external_provider_settings/{provider}/ endpoint
stores credentials.

All new endpoints are gated behind EXTERNAL_PROVIDERS_ENABLED (default
false), so this can be merged to main without activating the feature.

New app: src/server/oasisapi/external_providers/
  adapters/base.py       – abstract ExposureProvider interface
  adapters/gxm.py        – GXM OAuth2 client + streaming HTTP
  models.py              – ExternalJob (UUID PK) + ExternalProviderSettings
  services.py            – orchestration, non-destructive merge, settings fallback
  tasks.py               – Celery task wrappers (oasis-internal-worker queue)
  views.py / urls.py     – DRF views with drf-spectacular annotations
  migrations/0001        – creates both models
  tests/                 – unit tests (merge logic, service flows, view layer)

Modified:
  portfolios/models.py          – location_file_source / _external_provider / _audit
  portfolios/v2_api/serializers – location_file dict gains source/external_provider/audit_url
  portfolios/migrations/0011    – adds three new portfolio fields
  settings/base.py              – EXTERNAL_PROVIDERS_ENABLED + GXM_* settings
  swagger.py                    – registers v2-external-providers URL namespace
  CLAUDE.md                     – project documentation

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@benhayes21
benhayes21 marked this pull request as draft April 22, 2026 10:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: No status

Development

Successfully merging this pull request may close these issues.

feat: External exposure-provider integration (GXM phase 1)

2 participants