aeat.adapters.outbound.aeat.sede._groi_check module

AEAT GROI Spanish-ROI consult browser driver.

Drives the public AEAT-hosted GROI servlet that confirms whether a given Spanish NIF is registered as an intra-community operator (Registro de Operadores Intracomunitarios). The form accepts a single 9-character Spanish NIF, posts to the same servlet path, and renders a verdict text such as CONSTA UN OPERADOR INTRACOMUNITARIO (registered) or no es un NIF válido (input-format error).

This adapter is the SPANISH-counterparty sibling of the IXVI _nif_iva_check adapter (foreign-EU VIES proxy). Live probing on 2026-05-07 confirmed:

  • The GROI servlet at www2 is reachable under cl@ve-movil auth.

  • The IXVI servlet at www1 requires a stricter auth tier than cl@ve-movil and is currently blocked.

The two surfaces serve different verification needs:

  • GROI (this adapter): “is this Spanish NIF registered as an intra-community operator in Spain?” — used to confirm Spanish counterparties on modelo 349 are ROI-registered.

  • IXVI (_nif_iva_check): “is this foreign EU IVA-ID valid per VIES?” — used to confirm foreign EU counterparties on modelo 349 hold a valid IVA identifier.

The driver reaches the form servlet directly via authenticated BrowserSession (caller is responsible for loading the cl@ve-movil / certificate storage state). Form selectors are verified against captured live HTML (form action ConsultaOperadorSedeGroiServlet; input id=nif name=nif maxlength=9; submit id=enviar name=enviar); verdict markers are derived from real AEAT response samples captured 2026-05-07.

class GroiNifVerdict(**data)[source]

Bases: _SedeCheckerModel

One observation per declared Spanish NIF after live navigation.

verdict is the AEAT-rendered ROI-registration status: valid when AEAT reports the NIF as a registered intra-community operator, invalid when AEAT either says the NIF is unregistered or that the input format is malformed, unknown when the response text is structurally unanswerable.

Parameters:
  • nif (str)

  • verdict (Literal['valid', 'invalid', 'unknown'])

  • raw_evidence_locator (str | None)

nif: str
verdict: Literal['valid', 'invalid', 'unknown']
raw_evidence_locator: str | None
class GroiResult(**data)[source]

Bases: _SedeCheckerModel

Aggregate live-driver result across every declared Spanish NIF.

Parameters:

observations (tuple[GroiNifVerdict, ...])

observations: tuple[GroiNifVerdict, ...]
class GroiSedeDriver(*, settings=None)[source]

Bases: object

Live AEAT GROI driver backed by the central BrowserSession surface.

The driver navigates directly to the form servlet (the surface is reachable post cl@ve-movil auth without an intermediate sede entry page) and queries each declared NIF in alphabetical order. Verdict parsing keys off the AEAT certification phrases captured live.

Parameters:

settings (Settings | None)

property mode: Literal['live']

Always "live" — the driver requires a real Playwright session.

planned_operations(payload, *, expected)[source]

Return the ordered list of RemoteOperation entries planned for this driver run.

Builds the sequence: GET the GROI URL, open the form, then one check-nif-<NIF> browser action per declared NIF (sorted alphabetically), and finally discard-session. At least one entry in expected is required; raises RegistryValidationError otherwise.

Return type:

tuple[RemoteOperation, ...]

Parameters:
collect(payload, *, expected, timeout_ms=30000)[source]

Run the async GROI driver synchronously and return a GroiResult.

Wraps collect_async() in asyncio.run. SedeError, SiteHealthError, and BrowserError are re-raised as RegistryValidationError for the registry oracle layer.

Return type:

GroiResult

Parameters:
async collect_async(payload, *, expected, timeout_ms=30000)[source]

Async entry point returning GroiResult — delegates to collect_groi_observations().

Return type:

GroiResult

Parameters:
collect_observation(payload, *, expected)[source]

Return a GroiObservation by adapting the per-NIF result into the registry-Protocol shape.

Drives the live GROI form via collect(), then collapses the per-NIF observations into a flat {nif: verdict} mapping that the GROI oracle wrapper compares against the caller’s expected verdicts.

Return type:

GroiObservation

Parameters:
async collect_groi_observations(payload, *, expected, settings=None, timeout_ms=30000, browser_session_factory=None)[source]

Drive the GROI form per declared NIF and return a GroiResult with one observation each.

Return type:

GroiResult

Parameters:
extract_verdict_from_response_text(body_text)[source]

Parse the AEAT GROI verdict from the response body text.

Markers verified against live AEAT response samples captured 2026-05-07. Negative markers are checked first so explicit rejection cannot be misclassified by a generic positive token.

Return type:

Literal['valid', 'invalid', 'unknown']

Parameters:

body_text (str)