Skip to content

Commit

Permalink
add type suggestion
Browse files Browse the repository at this point in the history
  • Loading branch information
drkane committed Jan 31, 2024
1 parent 8cd1a4a commit 89bd74e
Show file tree
Hide file tree
Showing 6 changed files with 121 additions and 31 deletions.
48 changes: 40 additions & 8 deletions reconcile/api/base.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import urllib.parse
from typing import Dict, List, Literal, Optional, Union

from django.db.models import Q
from django.http import Http404
from django.shortcuts import reverse

Expand All @@ -22,7 +23,8 @@ class Reconcile:
name = "Find that Charity Reconciliation API"
view_url = "orgid_html"
view_url_args = {"org_id": "{{id}}"}
suggest = True
suggest_entity = True
suggest_type = True
extend = True
preview = True

Expand Down Expand Up @@ -58,7 +60,9 @@ def get_service_spec(
orgtypes = self._get_orgtypes_from_str(orgtypes)
if not defaultTypes:
if not orgtypes or orgtypes == "all":
defaultTypes = [{"id": "/Organization", "name": "Organisation"}]
defaultTypes = [
{"id": "registered-charity", "name": "Registered Charity"}
]
elif isinstance(orgtypes, list):
defaultTypes = [{"id": o.slug, "name": o.title} for o in orgtypes]

Expand Down Expand Up @@ -92,13 +96,18 @@ def get_service_spec(
},
"property_settings": [],
}
if self.suggest:
spec["suggest"] = {
"entity": {
if self.suggest_entity or self.suggest_type:
spec["suggest"] = {}
if self.suggest_entity:
spec["suggest"]["entity"] = {
"service_url": request_path,
"service_path": "/suggest/entity",
},
}
}
if self.suggest_type:
spec["suggest"]["type"] = {
"service_url": request_path,
"service_path": "/suggest/type",
}
return spec

def reconcile(
Expand Down Expand Up @@ -139,7 +148,6 @@ def suggest_entity(
orgtypes = self._get_orgtypes_from_str(orgtypes)
SUGGEST_NAME = "name_complete"

# cursor = request.GET.get("cursor")
if not prefix:
raise Http404("Prefix must be supplied")
q = OrganisationGroup.search()
Expand Down Expand Up @@ -176,6 +184,30 @@ def suggest_entity(
]
}

def suggest_type(
self,
request,
prefix: str,
cursor: int = 0,
):
if not prefix:
raise Http404("Prefix must be supplied")

results = OrganisationType.objects.filter(
Q(title__icontains=prefix) | Q(slug__icontains=prefix)
)[cursor : cursor + 10]

return {
"result": [
{
"id": r.slug,
"name": r.title,
"notable": [],
}
for r in results
]
}

def propose_properties(self, request, type_, limit=500):
if type_ != "Organization":
raise Http404("type must be Organization")
Expand Down
10 changes: 8 additions & 2 deletions reconcile/api/reconcile_all.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,9 @@
ReconciliationQueryBatchForm,
ReconciliationResult,
ServiceSpec,
SuggestQuery,
SuggestEntityQuery,
SuggestResponse,
SuggestTypeQuery,
)

api = Router(tags=["Reconciliation (nonprofits)"])
Expand Down Expand Up @@ -64,12 +65,17 @@ def preview(request, id: str, response: HttpResponse):


@api.get("/suggest/entity", response={200: SuggestResponse}, exclude_none=True)
def suggest_entity(request, query: Query[SuggestQuery]):
def suggest_entity(request, query: Query[SuggestEntityQuery]):
return reconcile.suggest_entity(
request, query.prefix, query.cursor, orgtypes=query.type
)


@api.get("/suggest/type", response={200: SuggestResponse}, exclude_none=True)
def suggest_type(request, query: Query[SuggestTypeQuery]):
return reconcile.suggest_type(request, query.prefix, query.cursor)


@api.get(
"/extend/propose",
response={200: DataExtensionPropertyProposalResponse},
Expand Down
3 changes: 2 additions & 1 deletion reconcile/api/reconcile_company.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ class CompanyReconcile(Reconcile):
name = "Find that Charity Company Reconciliation API"
view_url = "company_detail"
view_url_args = {"company_number": "{{id}}"}
suggest = False
suggest_entity = False
suggest_type = False
extend = False
preview = False

Expand Down
7 changes: 6 additions & 1 deletion reconcile/api/schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -201,12 +201,17 @@ class ReconciliationResultBatch(RootModel[Dict[str, Dict]], Schema):
root: Dict[str, ReconciliationResult]


class SuggestQuery(Schema):
class SuggestEntityQuery(Schema):
prefix: str
cursor: int = 0
type: Optional[str] = Field(None, alias="type")


class SuggestTypeQuery(Schema):
prefix: str
cursor: int = 0


class SuggestResult(Schema):
id: str
name: str
Expand Down
9 changes: 0 additions & 9 deletions reconcile/tests/test_company_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ def setUp(self):
super().setUp()
self.registry = Registry(retrieve=retrieve_schema_from_filesystem)

# GET request to /api/v1/reconcile/company should return the service spec
def test_get_company_service_spec(self):
for schema_version, schema in get_schema("manifest.json").items():
with self.subTest(schema_version):
Expand Down Expand Up @@ -55,9 +54,7 @@ def test_get_company_service_spec(self):
registry=self.registry,
)

# POST request to /api/v1/reconcile/company should return a list of candidates
def test_company_reconcile_post(self):
# attach RECON RESPONSE to the search() method of self.mock_es
self.mock_es.return_value.search.return_value = RECON_RESPONSE

for schema_version, schema in get_schema(
Expand All @@ -83,9 +80,7 @@ def test_company_reconcile_post(self):
registry=self.registry,
)

# POST request to /api/v1/reconcile/company should return a list of candidates
def test_company_reconcile_get(self):
# attach RECON RESPONSE to the search() method of self.mock_es
self.mock_es.return_value.search.return_value = RECON_RESPONSE

for schema_version, schema in get_schema(
Expand All @@ -111,9 +106,7 @@ def test_company_reconcile_get(self):
registry=self.registry,
)

# POST request to /api/v1/reconcile should return a list of candidates
def test_reconcile_empty_post(self):
# attach RECON RESPONSE to the search() method of self.mock_es
self.mock_es.return_value.search.return_value = EMPTY_RESPONSE

for schema_version, schema in get_schema(
Expand All @@ -138,9 +131,7 @@ def test_reconcile_empty_post(self):
registry=self.registry,
)

# POST request to /api/v1/reconcile should return a list of candidates
def test_reconcile_empty_get(self):
# attach RECON RESPONSE to the search() method of self.mock_es
self.mock_es.return_value.search.return_value = EMPTY_RESPONSE

for schema_version, schema in get_schema(
Expand Down
75 changes: 65 additions & 10 deletions reconcile/tests/test_reconcile_all_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@

RECON_BASE_URLS: list[Tuple[str, list[str]]] = [
("/api/v1/reconcile/", ["0.2"]),
# ("/api/v1/reconcile/local-authority", ["0.2"]),
("/reconcile", ["0.1"]),
("/reconcile/local-authority", ["0.1"]),
]
Expand All @@ -49,7 +48,6 @@ def setUp(self):
super().setUp()
self.registry = Registry(retrieve=retrieve_schema_from_filesystem)

# GET request to /api/v1/reconcile should return the service spec
def test_get_service_spec(self):
for base_url, schema_version, schema in get_test_cases("manifest.json"):
with self.subTest((base_url, schema_version)):
Expand All @@ -71,7 +69,6 @@ def test_get_service_spec(self):
)

def test_reconcile_post(self):
# attach RECON RESPONSE to the search() method of self.mock_es
self.mock_es.return_value.search.return_value = RECON_RESPONSE

for base_url, schema_version, schema in get_test_cases(
Expand All @@ -98,7 +95,6 @@ def test_reconcile_post(self):
)

def test_reconcile_get(self):
# attach RECON RESPONSE to the search() method of self.mock_es
self.mock_es.return_value.search.return_value = RECON_RESPONSE

for base_url, schema_version, schema in get_test_cases(
Expand All @@ -125,7 +121,6 @@ def test_reconcile_get(self):
)

def test_reconcile_with_type_post(self):
# attach RECON RESPONSE to the search() method of self.mock_es
self.mock_es.return_value.search.return_value = RECON_RESPONSE

for base_url, schema_version, schema in get_test_cases(
Expand Down Expand Up @@ -165,7 +160,6 @@ def test_reconcile_with_type_post(self):
)

def test_reconcile_with_type_get(self):
# attach RECON RESPONSE to the search() method of self.mock_es
self.mock_es.return_value.search.return_value = RECON_RESPONSE

for base_url, schema_version, schema in get_test_cases(
Expand Down Expand Up @@ -205,7 +199,6 @@ def test_reconcile_with_type_get(self):
)

def test_reconcile_empty(self):
# attach RECON RESPONSE to the search() method of self.mock_es
self.mock_es.return_value.search.return_value = EMPTY_RESPONSE

for base_url, schema_version, schema in get_test_cases(
Expand All @@ -230,7 +223,6 @@ def test_reconcile_empty(self):
registry=self.registry,
)

# POST request to /api/v1/reconcile/suggest/entity should return a list of candidates
def test_reconcile_suggest_entity(self):
self.mock_es.return_value.search.return_value = SUGGEST_RESPONSE

Expand Down Expand Up @@ -265,7 +257,6 @@ def test_reconcile_suggest_entity(self):
registry=self.registry,
)

# POST request to /api/v1/reconcile/suggest/entity should return a list of candidates
def test_reconcile_suggest_entity_type(self):
self.mock_es.return_value.search.return_value = SUGGEST_RESPONSE

Expand Down Expand Up @@ -301,6 +292,71 @@ def test_reconcile_suggest_entity_type(self):
registry=self.registry,
)

def test_reconcile_suggest_type(self):
self.mock_es.return_value.search.return_value = SUGGEST_RESPONSE

for base_url, schema_version, schema in get_test_cases(
"suggest-types-response.json"
):
with self.subTest((base_url, schema_version)):
service_spec = self.client.get(base_url).json()
if "type" not in service_spec["suggest"]:
continue
suggest_url = "".join(list(service_spec["suggest"]["type"].values()))

response = self.client.get(
suggest_url,
{
"prefix": "Charity",
},
)
self.assertEqual(response.status_code, 200)
data = response.json()
self.assertEqual(list(data.keys()), ["result"])
self.assertEqual(len(data["result"]), 2)
self.assertEqual(
list(data["result"][0].keys()), ["id", "name", "notable"]
)
self.assertEqual(data["result"][0]["id"], "registered-charity")
self.assertEqual(len(data["result"][0]["notable"]), 0)

jsonschema.validate(
instance=data,
schema=schema,
cls=jsonschema.Draft7Validator,
registry=self.registry,
)

def test_reconcile_suggest_type_empty(self):
self.mock_es.return_value.search.return_value = SUGGEST_RESPONSE

for base_url, schema_version, schema in get_test_cases(
"suggest-types-response.json"
):
with self.subTest((base_url, schema_version)):
service_spec = self.client.get(base_url).json()
if "type" not in service_spec["suggest"]:
continue
suggest_url = "".join(list(service_spec["suggest"]["type"].values()))

response = self.client.get(
suggest_url,
{
"prefix": "BLAH",
},
)
self.assertEqual(response.status_code, 200)
data = response.json()
self.assertEqual(list(data.keys()), ["result"])
self.assertEqual(len(data["result"]), 0)

jsonschema.validate(
instance=data,
schema=schema,
cls=jsonschema.Draft7Validator,
registry=self.registry,
)

def test_reconcile_extend_post(self):
self.mock_es.return_value.search.return_value = SUGGEST_RESPONSE

Expand Down Expand Up @@ -391,7 +447,6 @@ def test_reconcile_extend_get(self):
registry=self.registry,
)

# GET request to /api/v1/reconcile/suggest/entity should return a list of candidates
def test_reconcile_propose_properties(self):
self.mock_es.return_value.search.return_value = SUGGEST_RESPONSE

Expand Down

0 comments on commit 89bd74e

Please sign in to comment.