🇳🇵 Nepal Entity Service

Open Source, open data, and open API for Nepali public entities

Search Service Guide

This guide covers the Search Service, which provides read-optimized search capabilities for querying entities, relationships, and version history.

Table of Contents

  1. Overview
  2. Getting Started
  3. Service API
  4. CLI Commands
  5. Common Use Cases
  6. Query Patterns
  7. Best Practices

Overview

The Search Service provides:

Key Features


Getting Started

Installation

pip install nepal-entity-service

Basic Usage

from nes.database.file_database import FileDatabase
from nes.services.search import SearchService

# Initialize database and service
db = FileDatabase(base_path="nes-db/v2")
search = SearchService(database=db)

# Search for entities
results = await search.search_entities(
    query="ram",
    entity_type="person"
)

for entity in results:
    print(f"{entity.names[0].en.full} ({entity.id})")

Service API

Initialization

from nes.database.file_database import FileDatabase
from nes.services.search import SearchService

# Initialize with database
db = FileDatabase(base_path="nes-db/v2")
search = SearchService(database=db)

Search Entities

Search entities with text query and filters:

# Basic text search
results = await search.search_entities(query="ram")

# Search with type filter
results = await search.search_entities(
    query="poudel",
    entity_type="person"
)

# Search with subtype filter
results = await search.search_entities(
    query="congress",
    entity_type="organization",
    sub_type="political_party"
)

# Search with attribute filter
results = await search.search_entities(
    attributes={"party": "nepali-congress"}
)

# Combine filters
results = await search.search_entities(
    query="minister",
    entity_type="person",
    sub_type="politician",
    attributes={"party": "nepali-congress"}
)

# Paginated search
page1 = await search.search_entities(
    query="politician",
    limit=20,
    offset=0
)
page2 = await search.search_entities(
    query="politician",
    limit=20,
    offset=20
)

Parameters:
- query (str, optional): Text to search in entity names
- entity_type (str, optional): Filter by type (person, organization, location)
- sub_type (str, optional): Filter by subtype
- attributes (dict, optional): Filter by attributes (AND logic)
- limit (int): Maximum results (default: 100)
- offset (int): Skip N results (default: 0)

Returns: List of Entity objects

Search Relationships

Search relationships with filters and temporal queries:

# Search by relationship type
results = await search.search_relationships(
    relationship_type="MEMBER_OF"
)

# Search by source entity
results = await search.search_relationships(
    source_entity_id="entity:person/ram-chandra-poudel"
)

# Search by target entity
results = await search.search_relationships(
    target_entity_id="entity:organization/political_party/nepali-congress"
)

# Search for currently active relationships
results = await search.search_relationships(
    source_entity_id="entity:person/ram-chandra-poudel",
    currently_active=True
)

# Search for relationships active on a specific date
from datetime import date

results = await search.search_relationships(
    source_entity_id="entity:person/ram-chandra-poudel",
    active_on=date(2021, 6, 1)
)

# Combine filters
results = await search.search_relationships(
    relationship_type="MEMBER_OF",
    source_entity_id="entity:person/ram-chandra-poudel",
    currently_active=True
)

Parameters:
- relationship_type (str, optional): Filter by type (MEMBER_OF, HELD_POSITION, etc.)
- source_entity_id (str, optional): Filter by source entity
- target_entity_id (str, optional): Filter by target entity
- active_on (date, optional): Filter for relationships active on date
- currently_active (bool, optional): Filter for relationships with no end date
- limit (int): Maximum results (default: 100)
- offset (int): Skip N results (default: 0)

Returns: List of Relationship objects

Get Entity by ID

Retrieve a specific entity:

entity = await search.get_entity("entity:person/ram-chandra-poudel")

if entity:
    print(f"Name: {entity.names[0].en.full}")
    print(f"Type: {entity.type}/{entity.sub_type}")
    print(f"Attributes: {entity.attributes}")
else:
    print("Entity not found")

Parameters:
- entity_id (str): The entity ID

Returns: Entity object or None

Get Relationship by ID

Retrieve a specific relationship:

relationship = await search.get_relationship("relationship:abc123")

if relationship:
    print(f"Type: {relationship.relationship_type}")
    print(f"Source: {relationship.source_entity_id}")
    print(f"Target: {relationship.target_entity_id}")

Parameters:
- relationship_id (str): The relationship ID

Returns: Relationship object or None

Get Entity Versions

Retrieve version history for an entity:

versions = await search.get_entity_versions(
    "entity:person/ram-chandra-poudel"
)

for version in versions:
    print(f"Version {version.version_number}")
    print(f"  Created: {version.created_at}")
    print(f"  Author: {version.author.slug}")
    print(f"  Description: {version.change_description}")

Parameters:
- entity_id (str): The entity ID

Returns: List of Version objects

Get Relationship Versions

Retrieve version history for a relationship:

versions = await search.get_relationship_versions("relationship:abc123")

for version in versions:
    print(f"Version {version.version_number}")
    print(f"  Created: {version.created_at}")
    print(f"  Changes: {version.change_description}")

Parameters:
- relationship_id (str): The relationship ID

Returns: List of Version objects

Find Entity by Name

Find entity by exact or partial name match:

# Find by full name
entity = await search.find_entity_by_name(
    name="Ram Chandra Poudel",
    entity_type="person"
)

# Find by partial name
entity = await search.find_entity_by_name(
    name="Poudel",
    entity_type="person"
)

if entity:
    print(f"Found: {entity.id}")

Parameters:
- name (str): Name to search for
- entity_type (str, optional): Filter by type

Returns: First matching Entity or None


CLI Commands

Search Entities

# Basic search
nes search entities "ram"

# Search with type filter
nes search entities "poudel" --type person

# Search with subtype filter
nes search entities "congress" --type organization --subtype political_party

# Search with attribute filter
nes search entities --attributes party=nepali-congress

# Paginated search
nes search entities "politician" --limit 20 --offset 0

# Output as JSON
nes search entities "ram" --format json

Search Relationships

# Search by type
nes search relationships --type MEMBER_OF

# Search by source entity
nes search relationships --source entity:person/ram-chandra-poudel

# Search by target entity
nes search relationships --target entity:organization/political_party/nepali-congress

# Search currently active
nes search relationships --source entity:person/ram-chandra-poudel --active

# Search active on date
nes search relationships --source entity:person/ram-chandra-poudel --active-on 2021-06-01

Get Entity

# Get entity by ID
nes get entity entity:person/ram-chandra-poudel

# Output as JSON
nes get entity entity:person/ram-chandra-poudel --format json

# Include relationships
nes get entity entity:person/ram-chandra-poudel --include-relationships

Get Versions

# Get entity versions
nes get versions entity:person/ram-chandra-poudel

# Get relationship versions
nes get versions relationship:abc123

# Output as JSON
nes get versions entity:person/ram-chandra-poudel --format json

Common Use Cases

Use Case 1: Find All Politicians from a Party

async def find_party_members(party_slug: str):
    """Find all politicians from a specific party."""

    # Search for entities with party attribute
    results = await search.search_entities(
        entity_type="person",
        sub_type="politician",
        attributes={"party": party_slug}
    )

    print(f"Found {len(results)} members of {party_slug}")

    for entity in results:
        print(f"  - {entity.names[0].en.full}")

    return results

# Usage
members = await find_party_members("nepali-congress")

Use Case 2: Find Current Government Ministers

async def find_current_ministers():
    """Find all current government ministers."""

    # Search for HOLDS_POSITION relationships that are currently active
    relationships = await search.search_relationships(
        relationship_type="HOLDS_POSITION",
        currently_active=True
    )

    ministers = []
    for rel in relationships:
        # Get the person entity
        person = await search.get_entity(rel.source_entity_id)
        if person:
            ministers.append({
                "name": person.names[0].en.full,
                "position": rel.attributes.get("position", "Unknown"),
                "since": rel.start_date
            })

    return ministers

# Usage
ministers = await find_current_ministers()
for minister in ministers:
    print(f"{minister['name']}: {minister['position']} (since {minister['since']})")

Use Case 3: Track Entity Changes Over Time

async def track_entity_changes(entity_id: str):
    """Track all changes to an entity over time."""

    versions = await search.get_entity_versions(entity_id)

    print(f"Change history for {entity_id}:")
    print(f"Total versions: {len(versions)}\n")

    for version in versions:
        print(f"Version {version.version_number}")
        print(f"  Date: {version.created_at}")
        print(f"  Author: {version.author.slug}")
        print(f"  Changes: {version.change_description}")
        print()

    return versions

# Usage
history = await track_entity_changes("entity:person/ram-chandra-poudel")

Use Case 4: Find Entities by Multiple Criteria

async def find_entities_advanced(
    query: str,
    entity_type: str,
    attributes: dict,
    limit: int = 100
):
    """Advanced entity search with multiple criteria."""

    results = await search.search_entities(
        query=query,
        entity_type=entity_type,
        attributes=attributes,
        limit=limit
    )

    # Further filter results in Python if needed
    filtered = []
    for entity in results:
        # Custom filtering logic
        if entity.attributes.get("verified"):
            filtered.append(entity)

    return filtered

# Usage
results = await find_entities_advanced(
    query="minister",
    entity_type="person",
    attributes={"party": "nepali-congress"},
    limit=50
)

Use Case 5: Build Entity Network

async def build_entity_network(entity_id: str, depth: int = 1):
    """Build a network of related entities."""

    network = {"entities": {}, "relationships": []}
    visited = set()

    async def explore(current_id: str, current_depth: int):
        if current_depth > depth or current_id in visited:
            return

        visited.add(current_id)

        # Get entity
        entity = await search.get_entity(current_id)
        if entity:
            network["entities"][current_id] = entity

        # Get relationships
        relationships = await search.search_relationships(
            source_entity_id=current_id
        )

        for rel in relationships:
            network["relationships"].append(rel)

            # Explore target entity
            if current_depth < depth:
                await explore(rel.target_entity_id, current_depth + 1)

    await explore(entity_id, 0)

    return network

# Usage
network = await build_entity_network(
    "entity:person/ram-chandra-poudel",
    depth=2
)
print(f"Network: {len(network['entities'])} entities, {len(network['relationships'])} relationships")

Query Patterns

# Search for entities with similar names
async def fuzzy_name_search(name: str):
    # Split name into parts
    parts = name.lower().split()

    results = []
    for part in parts:
        entities = await search.search_entities(query=part)
        results.extend(entities)

    # Remove duplicates
    unique = {e.id: e for e in results}
    return list(unique.values())

Pattern 2: Temporal Relationship Query

# Find who held a position during a specific period
async def who_held_position_during(
    position: str,
    start_date: date,
    end_date: date
):
    results = []

    # Check each date in range
    current = start_date
    while current <= end_date:
        rels = await search.search_relationships(
            relationship_type="HOLDS_POSITION",
            active_on=current
        )

        for rel in rels:
            if rel.attributes.get("position") == position:
                results.append(rel)

        # Move to next month
        current = current.replace(month=current.month + 1)

    return results

Pattern 3: Hierarchical Entity Query

# Find all entities in a hierarchy
async def find_hierarchy(root_id: str):
    hierarchy = []

    # Find all PART_OF relationships
    rels = await search.search_relationships(
        target_entity_id=root_id,
        relationship_type="PART_OF"
    )

    for rel in rels:
        child = await search.get_entity(rel.source_entity_id)
        if child:
            hierarchy.append(child)

            # Recursively find children
            sub_hierarchy = await find_hierarchy(child.id)
            hierarchy.extend(sub_hierarchy)

    return hierarchy

Pattern 4: Attribute-Based Filtering

# Complex attribute filtering
async def filter_by_attributes(
    entity_type: str,
    required_attrs: dict,
    optional_attrs: dict = None
):
    # Search with required attributes
    results = await search.search_entities(
        entity_type=entity_type,
        attributes=required_attrs
    )

    # Filter by optional attributes in Python
    if optional_attrs:
        filtered = []
        for entity in results:
            matches = any(
                entity.attributes.get(k) == v
                for k, v in optional_attrs.items()
            )
            if matches:
                filtered.append(entity)
        return filtered

    return results

Best Practices

1. Use Specific Filters

# Good: Specific filters reduce result set
results = await search.search_entities(
    query="ram",
    entity_type="person",
    sub_type="politician"
)

# Avoid: Too broad, returns many results
results = await search.search_entities(query="ram")

2. Paginate Large Result Sets

# Good: Paginate for large queries
page_size = 50
offset = 0

while True:
    results = await search.search_entities(
        entity_type="person",
        limit=page_size,
        offset=offset
    )

    if not results:
        break

    # Process results
    for entity in results:
        process(entity)

    offset += page_size

3. Cache Frequently Accessed Entities

# Simple cache
entity_cache = {}

async def get_entity_cached(entity_id: str):
    if entity_id not in entity_cache:
        entity_cache[entity_id] = await search.get_entity(entity_id)
    return entity_cache[entity_id]

4. Use Temporal Queries Efficiently

# Good: Single query for current state
current_rels = await search.search_relationships(
    source_entity_id=entity_id,
    currently_active=True
)

# Avoid: Multiple queries for date range
# (unless you need the full history)

5. Handle Missing Entities

entity = await search.get_entity(entity_id)

if entity is None:
    print(f"Entity not found: {entity_id}")
    return None

# Continue processing

6. Combine Filters for Precision

# Combine multiple filters for precise results
results = await search.search_entities(
    query="minister",
    entity_type="person",
    sub_type="politician",
    attributes={
        "party": "nepali-congress",
        "verified": True
    }
)

Additional Resources


Last Updated: 2024
Version: 2.0