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

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.py — ExternalJob (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.py — location_file dict gains source, external_provider, audit_url (additive)
  • portfolios/migrations/0011 — new portfolio fields
  • settings/base.py — EXTERNAL_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

This branch has not been deployed

No deployments
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)

1 participant