Skip to content

Resolve geos via typeahead; drop dead targeting/payload helpers - #26

Merged
th0rz05 merged 1 commit into
mainfrom
refactor/geo-typeahead-and-dead-code
Jun 3, 2026
Merged

Resolve geos via typeahead; drop dead targeting/payload helpers#26
th0rz05 merged 1 commit into
mainfrom
refactor/geo-typeahead-and-dead-code

Conversation

@th0rz05

@th0rz05 th0rz05 commented Jun 3, 2026

Copy link
Copy Markdown
Collaborator

Summary

Fixes the silent geo-drop gap and removes dead code uncovered while unifying geo resolution.

Geo resolution (the PT bug): previously geos were matched against a hardcoded ~15-country ISO_TO_LINKEDIN_GEO_URN map, so any country outside it (e.g. PT) was silently dropped from targeting. Now geos use the same typeahead path as the other open facets:

  1. expand each ISO 3166-1 alpha-2 code to a country name via pycountry (prefers common_name, e.g. "South Korea"),
  2. resolve that name through the locations typeahead finder,
  3. surface codes that don't resolve under unresolved["geos"] instead of dropping them.

This also de-duplicates geo logic — it previously lived in both mapping.audience_to_targeting and targeting._geo_urns; now it lives only in TargetingResolver. Any country works, not just a shortlist.

Dead code removed (all confirmed zero-reference):

  • linkedin/mapping.py: audience_to_targeting + line_item_payload (only the dead payload helper used the geo-only baseline; the publish flow uses TargetingResolver) and the now-unused ISO_TO_LINKEDIN_GEO_URN map.
  • meta/mapping.py: line_item_payload (never called).
  • campaign_setup: the write-only rejection_reason AgentState field (the reason is already captured in the audit entry).

line_item_locale now validates the country via pycountry instead of the deleted map.

Net effect

+96 / −114 lines. Adds pycountry to the linkedin extra.

Verification

  • 43 tests pass; new tests pin geo-by-name resolution (US+PT → both URNs) and the no-guess contract for unknown codes (ZZunresolved["geos"], falls back to default location).
  • Read-only live probe confirmed "Portugal"urn:li:geo:100364837 (exact match over "Porto, Portugal"), and "United States"/"United Kingdom" resolve to the same URNs the old map hardcoded.
  • New code is ruff-clean. (Pre-existing repo-wide UP017/E501 warnings are untouched — a dedicated lint-clean PR is the next cleanup item.)

Test plan

  • pytest -q green (43)
  • ruff check clean on changed code
  • Read-only live probe confirms country-name → geo URN

Geo targeting previously relied on a hardcoded ~15-country ISO->URN map, so
any country outside it (e.g. PT) was silently dropped. Replace it with the
same typeahead path the other open facets use: expand each ISO 3166-1 alpha-2
code to a country name via pycountry, resolve it through the locations
typeahead, and surface codes that don't resolve under `unresolved["geos"]`
instead of dropping them. Geo logic now lives only in TargetingResolver.

Also remove confirmed dead code uncovered while unifying this:
  * linkedin mapping.audience_to_targeting + line_item_payload (only the dead
    payload helper used the geo-only baseline; the publish flow uses
    TargetingResolver) and the now-unused ISO_TO_LINKEDIN_GEO_URN map;
  * meta mapping.line_item_payload (never called);
  * the write-only `rejection_reason` AgentState field (the reason is already
    captured in the audit entry).

line_item_locale now validates the country via pycountry instead of the
deleted map.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings June 3, 2026 11:08
@th0rz05
th0rz05 merged commit 1d2a90c into main Jun 3, 2026
1 check passed
@th0rz05
th0rz05 deleted the refactor/geo-typeahead-and-dead-code branch June 3, 2026 11:10

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes LinkedIn geo targeting silently dropping non-hardcoded ISO country codes by resolving geos through the same locations typeahead flow used for other open facets (ISO code → country name via pycountry → typeahead → URN), and removes dead/zero-reference targeting/payload helpers uncovered during the cleanup.

Changes:

  • Resolve LinkedIn Audience.geos via locations typeahead (with a US fallback URN) and surface unknown/invalid codes under unresolved["geos"].
  • Remove dead mapping/payload helpers (LinkedIn + Meta) and drop the hardcoded LinkedIn ISO→geo URN map; remove the write-only rejection_reason from campaign setup state.
  • Add pycountry to the linkedin optional dependency extra and extend integration tests to pin the new geo-resolution behavior.

Reviewed changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
tests/integrations/test_linkedin_targeting.py Adds coverage for geo resolution via typeahead by country name and for surfacing unknown ISO codes as unresolved.
tests/integrations/test_linkedin_publish.py Updates the publish test client stub to support typeahead targeting lookups used by TargetingResolver.
src/yieldagent/integrations/meta/mapping.py Removes unused line_item_payload helper and cleans up imports.
src/yieldagent/integrations/linkedin/targeting.py Implements geo resolution via pycountry + locations typeahead, adds DEFAULT_GEO_URN, and reports unresolved geo codes.
src/yieldagent/integrations/linkedin/mapping.py Removes static geo map + dead payload helpers; updates line_item_locale to validate ISO codes via pycountry.
src/yieldagent/agents/campaign_setup/state.py Removes rejection_reason from AgentState (write-only field).
src/yieldagent/agents/campaign_setup/nodes.py Stops writing rejection_reason in human_gate output.
pyproject.toml Adds pycountry dependency to the linkedin extra.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +76 to 78
country = audience.geos[0].upper() if audience.geos else "US"
if pycountry.countries.get(alpha_2=country) is None:
country = "US"
Comment on lines +196 to +208
for code in audience.geos:
name = _country_name(code)
urn = None
if name:
hits = await self._client.typeahead_targeting_entities(
facet=FACET_LOCATIONS, query=name
)
urn = _best_typeahead_match(name, hits)
if urn:
urns.append(urn)
else:
unresolved.append(code)
return urns or [DEFAULT_GEO_URN], unresolved
th0rz05 added a commit that referenced this pull request Jun 3, 2026
Copilot flagged that geo handling normalized inconsistently:
  * line_item_locale uppercased but didn't strip, so a brief geo like "PT "
    failed the pycountry lookup and silently fell back to US, even though the
    targeting resolver (which strips) resolved it fine;
  * _resolve_geos recorded unresolved entries using the raw padded/lowercased
    input rather than the normalized code.

Normalize each geo code once with strip().upper() and use that form for the
lookup, the locale, and the unresolved report. Adds tests for whitespace/case
tolerance in both resolution and locale, and that unresolved codes surface
normalized.

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
th0rz05 added a commit that referenced this pull request Jun 9, 2026
Geo targeting previously relied on a hardcoded ~15-country ISO->URN map, so
any country outside it (e.g. PT) was silently dropped. Replace it with the
same typeahead path the other open facets use: expand each ISO 3166-1 alpha-2
code to a country name via pycountry, resolve it through the locations
typeahead, and surface codes that don't resolve under `unresolved["geos"]`
instead of dropping them. Geo logic now lives only in TargetingResolver.

Also remove confirmed dead code uncovered while unifying this:
  * linkedin mapping.audience_to_targeting + line_item_payload (only the dead
    payload helper used the geo-only baseline; the publish flow uses
    TargetingResolver) and the now-unused ISO_TO_LINKEDIN_GEO_URN map;
  * meta mapping.line_item_payload (never called);
  * the write-only `rejection_reason` AgentState field (the reason is already
    captured in the audit entry).

line_item_locale now validates the country via pycountry instead of the
deleted map.
th0rz05 added a commit that referenced this pull request Jun 9, 2026
Copilot flagged that geo handling normalized inconsistently:
  * line_item_locale uppercased but didn't strip, so a brief geo like "PT "
    failed the pycountry lookup and silently fell back to US, even though the
    targeting resolver (which strips) resolved it fine;
  * _resolve_geos recorded unresolved entries using the raw padded/lowercased
    input rather than the normalized code.

Normalize each geo code once with strip().upper() and use that form for the
lookup, the locale, and the unresolved report. Adds tests for whitespace/case
tolerance in both resolution and locale, and that unresolved codes surface
normalized.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants