Skip to content

Commit eeeb530

Browse files
authored
Merge pull request #40 from OpenSPP/feat/dci-demo-module
feat(spp_dci_demo): add DCI birth verification demo module and Conditional Child Grant
2 parents 322c56e + a9a731d commit eeeb530

29 files changed

Lines changed: 2710 additions & 87 deletions

spp_dci/schemas/constants.py

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,17 @@
44

55

66
class RegistryType(StrEnum):
7-
"""DCI Registry types."""
7+
"""DCI Registry types (SPDCI spec compliant).
88
9-
SOCIAL_REGISTRY = "SOCIAL_REGISTRY"
10-
IBR = "IBR"
11-
CRVS = "CRVS"
12-
DISABILITY_REGISTRY = "DR"
13-
FUNCTIONAL_REGISTRY = "FR"
9+
Values use the namespaced format as specified in SPDCI API Standards.
10+
Reference: src/registry/*/RegistryType.yaml
11+
"""
12+
13+
SOCIAL_REGISTRY = "ns:org:RegistryType:Social"
14+
CRVS = "ns:org:RegistryType:Civil"
15+
IBR = "ns:org:RegistryType:IBR"
16+
DISABILITY_REGISTRY = "ns:org:RegistryType:DR"
17+
FUNCTIONAL_REGISTRY = "ns:org:RegistryType:FR"
1418

1519

1620
class RegistryEventType(StrEnum):

spp_dci_client/services/client.py

Lines changed: 121 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,7 @@ def search_by_id(
169169
page: int = 1,
170170
page_size: int = 10,
171171
async_mode: bool = False,
172+
registry_event_type: str | None = None,
172173
) -> dict:
173174
"""Search by identifier type and value (convenience method).
174175
@@ -179,6 +180,7 @@ def search_by_id(
179180
page: Page number (1-indexed)
180181
page_size: Records per page
181182
async_mode: If True, use async endpoint
183+
registry_event_type: Event type filter (BIRTH, DEATH, etc.)
182184
183185
Returns:
184186
SearchResponse or ACK dict
@@ -191,8 +193,56 @@ def search_by_id(
191193
record_type=record_type,
192194
page=page,
193195
page_size=page_size,
196+
registry_event_type=registry_event_type,
194197
)
195198

199+
def search_by_id_opencrvs(
200+
self,
201+
identifier_type: str,
202+
identifier_value: str,
203+
event_type: str = "birth",
204+
page: int = 1,
205+
page_size: int = 10,
206+
async_mode: bool = False,
207+
) -> dict:
208+
"""Search by identifier using OpenCRVS's non-standard format.
209+
210+
OpenCRVS doesn't support the standard DCI idtype-value query format.
211+
Instead, it requires an expression query with the identifier nested.
212+
213+
Args:
214+
identifier_type: Identifier type (UIN, BRN, MRN, DRN, etc.)
215+
identifier_value: Identifier value
216+
event_type: Registry event type (birth, death, etc.) - lowercase
217+
page: Page number (1-indexed)
218+
page_size: Records per page
219+
async_mode: If True, use async endpoint
220+
221+
Returns:
222+
SearchResponse or ACK dict
223+
"""
224+
# Build OpenCRVS-style query for ID lookup
225+
# Format: { type: "BRN"|"UIN"|"DRN", value: "<identifier>" }
226+
query = {
227+
"type": identifier_type,
228+
"value": identifier_value,
229+
}
230+
231+
# Build envelope in OpenCRVS format
232+
envelope = self._build_search_envelope_opencrvs(
233+
query=query,
234+
query_type="idtype-value",
235+
event_type=event_type,
236+
page=page,
237+
page_size=page_size,
238+
)
239+
240+
if async_mode:
241+
return self._make_request(ENDPOINT_ASYNC_SEARCH, envelope)
242+
else:
243+
endpoint = self.data_source.search_endpoint or ENDPOINT_SYNC_SEARCH
244+
return self._make_request(endpoint, envelope)
245+
196246
def search_by_predicate(
197247
self,
198248
predicate: str,
@@ -231,49 +281,57 @@ def search_by_expression(
231281
registry_type: str | None = None,
232282
registry_event_type: str | None = None,
233283
async_mode: bool = False,
284+
use_opencrvs_format: bool = False,
234285
) -> dict:
235286
"""Search using expression query (e.g., date range filters).
236287
237-
This supports DCI-compliant expression queries using ExpPredicate format.
288+
This supports both DCI-compliant expression queries and OpenCRVS's
289+
non-standard format.
238290
239291
Args:
240-
expression: DCI ExpPredicateWithCondition list, e.g.:
241-
[
242-
{
243-
"seq_num": 1,
244-
"expression1": {
245-
"attribute_name": "dateOfEvent",
246-
"operator": "ge",
247-
"attribute_value": "2020-01-01"
248-
},
249-
"condition": "and",
250-
"expression2": {
251-
"attribute_name": "dateOfEvent",
252-
"operator": "le",
253-
"attribute_value": "2026-02-10"
254-
}
255-
}
256-
]
292+
expression: Query expression. For standard DCI, a list of
293+
ExpPredicateWithCondition dicts. For OpenCRVS format, a dict
294+
of attribute filters (e.g., {"birthDate": {"type": "range", ...}}).
257295
record_type: PERSON, GROUP, etc.
258296
page: Page number (1-indexed)
259297
page_size: Records per page
260298
registry_type: Registry type (defaults to data source registry type)
261299
registry_event_type: Optional event type filter (e.g., "BIRTH", "DEATH")
262300
async_mode: If True, use async endpoint
301+
use_opencrvs_format: If True, wrap expression in OpenCRVS query
302+
structure and use OpenCRVS envelope format.
263303
264304
Returns:
265305
SearchResponse or ACK dict
266306
"""
267-
# Build envelope directly with expression query (bypass _parse_query)
268-
envelope = self._build_search_envelope(
269-
query_type=QueryType.EXPRESSION,
270-
query=expression,
271-
registry_type=registry_type or self._get_registry_type(),
272-
registry_event_type=registry_event_type,
273-
record_type=record_type,
274-
page=page,
275-
page_size=page_size,
276-
)
307+
if use_opencrvs_format:
308+
# Wrap the caller's expression in OpenCRVS query structure
309+
opencrvs_query = {
310+
"type": "ns:org:QueryType:expression",
311+
"value": {
312+
"expression": {
313+
"query": expression,
314+
}
315+
},
316+
}
317+
envelope = self._build_search_envelope_opencrvs(
318+
query=opencrvs_query,
319+
query_type="expression",
320+
event_type=registry_event_type,
321+
page=page,
322+
page_size=page_size,
323+
)
324+
else:
325+
# Standard DCI path (unchanged)
326+
envelope = self._build_search_envelope(
327+
query_type=QueryType.EXPRESSION,
328+
query=expression,
329+
registry_type=registry_type or self._get_registry_type(),
330+
registry_event_type=registry_event_type,
331+
record_type=record_type,
332+
page=page,
333+
page_size=page_size,
334+
)
277335

278336
if async_mode:
279337
return self._make_request(ENDPOINT_ASYNC_SEARCH, envelope)
@@ -379,14 +437,10 @@ def _search_by_date_range_opencrvs(
379437
) -> dict:
380438
"""Search by date range using OpenCRVS's non-standard envelope format.
381439
382-
OpenCRVS uses a custom format that differs from DCI spec:
383-
- reg_type: lowercase event type (e.g., "birth") instead of registry type
384-
- No reg_event_type field
385-
- Requires consent object and locale field
386-
- Expression query format: { type: "ns:org:QueryType:expression", value: { <attr>: { type: "range", ... } } }
440+
Builds an expression query with the correct OpenCRVS nesting:
441+
{ type, value: { expression: { query: { <attr>: { type: "range", ... } } } } }
387442
388-
This method exists for interoperability with OpenCRVS servers that don't
389-
follow the standard DCI envelope format.
443+
Then delegates to _build_search_envelope_opencrvs for the envelope wrapper.
390444
391445
Args:
392446
start_date: Start date in ISO format (YYYY-MM-DD or YYYY-MM-DDTHH:MM:SSZ)
@@ -401,11 +455,26 @@ def _search_by_date_range_opencrvs(
401455
Returns:
402456
SearchResponse or ACK dict
403457
"""
404-
# Build envelope directly in OpenCRVS format (bypassing standard _build_search_envelope)
458+
# Build OpenCRVS-style expression query with correct nesting
459+
expression_query = {
460+
"type": "ns:org:QueryType:expression",
461+
"value": {
462+
"expression": {
463+
"query": {
464+
attribute_name: {
465+
"type": "range",
466+
"gte": start_date,
467+
"lte": end_date,
468+
}
469+
}
470+
}
471+
},
472+
}
473+
474+
# Build envelope in OpenCRVS format (bypassing standard _build_search_envelope)
405475
envelope = self._build_search_envelope_opencrvs(
406-
start_date=start_date,
407-
end_date=end_date,
408-
attribute_name=attribute_name,
476+
query=expression_query,
477+
query_type="expression",
409478
event_type=event_type,
410479
page=page,
411480
page_size=page_size,
@@ -419,59 +488,43 @@ def _search_by_date_range_opencrvs(
419488

420489
def _build_search_envelope_opencrvs(
421490
self,
422-
start_date: str,
423-
end_date: str,
424-
attribute_name: str,
491+
query: dict,
492+
query_type: str,
425493
event_type: str | None,
426494
page: int,
427495
page_size: int,
428496
) -> dict:
429497
"""Build search envelope in OpenCRVS's non-standard format.
430498
431499
OpenCRVS expects:
432-
- reg_type: lowercase event type (e.g., "birth"), NOT registry type
433-
- No reg_event_type field
500+
- reg_type: "ns:org:RegistryType:Civil"
501+
- reg_event_type: lowercase event type (e.g., "birth")
434502
- Required consent object
435503
- Required locale field
436-
- Expression query with range syntax
504+
- Pre-built query object (e.g., expression with nested query structure)
437505
438506
Args:
439-
start_date: Start date
440-
end_date: End date
441-
attribute_name: Attribute to filter on
507+
query: Pre-built query object (e.g., expression query dict)
508+
query_type: Query type string (e.g., "expression", "idtype-value")
442509
event_type: Event type (BIRTH, DEATH, etc.)
443510
page: Page number
444511
page_size: Page size
445512
446513
Returns:
447-
Envelope dict in OpenCRVS format
514+
Envelope dict in OpenCRVS format (no signature wrapper)
448515
"""
449516
now = datetime.now(UTC)
450517
transaction_id = str(uuid.uuid4())
451518
reference_id = str(uuid.uuid4())
452519
message_id = str(uuid.uuid4())
453520

454-
# OpenCRVS uses lowercase event type for reg_type (e.g., "birth" not "BIRTH" or "CRVS")
455-
reg_type = (event_type or "birth").lower()
456-
457-
# Build OpenCRVS-style expression query
458-
expression_query = {
459-
"type": "ns:org:QueryType:expression",
460-
"value": {
461-
attribute_name: {
462-
"type": "range",
463-
"gte": start_date,
464-
"lte": end_date,
465-
}
466-
},
467-
}
468-
469-
# Build search criteria in OpenCRVS format (no reg_event_type)
521+
# Build search criteria in OpenCRVS format
470522
search_criteria = {
471523
"version": "1.0.0",
472-
"reg_type": reg_type,
473-
"query_type": "expression",
474-
"query": expression_query,
524+
"reg_type": RegistryType.CRVS.value,
525+
"reg_event_type": (event_type or "birth").lower(),
526+
"query_type": query_type,
527+
"query": query,
475528
"sort": [
476529
{
477530
"attribute_name": "createdAt",

0 commit comments

Comments
 (0)