s+: touch up conversion to shareabletext + tests

This commit is contained in:
Mark Joshwel 2023-09-02 09:35:33 +00:00
parent f62c31d685
commit cc90b5e196
3 changed files with 344 additions and 159 deletions

View file

@ -115,7 +115,7 @@
},
{
"cell_type": "code",
"execution_count": 4,
"execution_count": 3,
"metadata": {},
"outputs": [
{
@ -124,7 +124,7 @@
"Result(value=Latlong(latitude=1.3336875, longitude=103.7746875), error=None)"
]
},
"execution_count": 4,
"execution_count": 3,
"metadata": {},
"output_type": "execute_result"
}
@ -135,7 +135,7 @@
},
{
"cell_type": "code",
"execution_count": 5,
"execution_count": 4,
"metadata": {},
"outputs": [
{
@ -144,7 +144,7 @@
"Result(value=Latlong(latitude=1.3336875, longitude=103.7746875), error=None)"
]
},
"execution_count": 5,
"execution_count": 4,
"metadata": {},
"output_type": "execute_result"
}
@ -159,7 +159,7 @@
},
{
"cell_type": "code",
"execution_count": 6,
"execution_count": 5,
"metadata": {},
"outputs": [
{
@ -168,7 +168,7 @@
"Result(value=Latlong(latitude=1.3336875, longitude=103.7746875), error=None)"
]
},
"execution_count": 6,
"execution_count": 5,
"metadata": {},
"output_type": "execute_result"
}
@ -181,7 +181,7 @@
},
{
"cell_type": "code",
"execution_count": 7,
"execution_count": 6,
"metadata": {},
"outputs": [
{
@ -190,7 +190,7 @@
"Result(value=Latlong(latitude=1.33318835, longitude=103.77461234638255), error=None)"
]
},
"execution_count": 7,
"execution_count": 6,
"metadata": {},
"output_type": "execute_result"
}
@ -203,7 +203,7 @@
},
{
"cell_type": "code",
"execution_count": 2,
"execution_count": 7,
"metadata": {},
"outputs": [
{
@ -212,7 +212,7 @@
"Result(value=Latlong(latitude=1.33318835, longitude=103.77461234638255), error=None)"
]
},
"execution_count": 2,
"execution_count": 7,
"metadata": {},
"output_type": "execute_result"
}
@ -225,7 +225,13 @@
"cell_type": "markdown",
"metadata": {},
"source": [
"## Reverser Dictionary Output"
"## return dictionary of `reverser` function\n",
"\n",
"all the necessary keys (see `SHAREABLE_TEXT_LINE_*` constants) should be at the top-level of the dictionary.\n",
"these keys will be casted into strings for safety guarantee when shareable text lines are being generated.\n",
"\n",
"while not necessary, consider keeping the original response dict under the \"raw\" key.\n",
"helps with debugging using `-d/--debug`!"
]
},
{
@ -234,21 +240,116 @@
"metadata": {},
"outputs": [],
"source": [
"{\n",
" \"amenity\": \"\",\n",
" \"house_number\": \"\",\n",
" \"road\": \"\",\n",
" \"suburb\": \"\",\n",
" \"city\": \"\",\n",
" \"county\": \"\",\n",
" \"ISO3166-2-lvl6\": \"\",\n",
" \"postcode\": \"\",\n",
" \"country\": \"\",\n",
" \"country_code\": \"\",\n",
" \"original response\": \"{'place_id': 297946059, 'licence': 'Data © OpenStreetMap contributors, ODbL 1.0. http://osm.org/copyright', 'osm_type': 'relation', 'osm_id': 2535118, 'lat': '1.33318835', 'lon': '103.77461234638255', 'class': 'amenity', 'type': 'university', 'place_rank': 30, 'importance': 0.34662169301918117, 'addresstype': 'amenity', 'name': 'Ngee Ann Polytechnic', 'display_name': 'Ngee Ann Polytechnic, 535, Clementi Road, Bukit Timah, Singapore, Northwest, 599489, Singapore', 'address': {'amenity': 'Ngee Ann Polytechnic', 'house_number': '535', 'road': 'Clementi Road', 'suburb': 'Bukit Timah', 'city': 'Singapore', 'county': 'Northwest', 'ISO3166-2-lvl6': 'SG-03', 'postcode': '599489', 'country': 'Singapore', 'country_code': 'sg'}, 'boundingbox': ['1.3289692', '1.3372184', '103.7701481', '103.7783945']}\",\n",
" \"latitude\": \"1.33318835\",\n",
"reverser_return = {\n",
" \"amenity\": \"Ngee Ann Polytechnic\",\n",
" \"house_number\": \"535\",\n",
" \"road\": \"Clementi Road\",\n",
" \"suburb\": \"Bukit Timah\",\n",
" \"city\": \"Singapore\",\n",
" \"county\": \"Northwest\",\n",
" \"ISO3166-2-lvl6\": \"SG-03\",\n",
" \"postcode\": \"599489\",\n",
" \"country\": \"Singapore\",\n",
" \"country_code\": \"sg\",\n",
" \"raw\": {\n",
" \"place_id\": 297946059,\n",
" \"licence\": \"Data © OpenStreetMap contributors, ODbL 1.0. http://osm.org/copyright\",\n",
" \"osm_type\": \"relation\",\n",
" \"osm_id\": 2535118,\n",
" \"lat\": \"1.33318835\",\n",
" \"lon\": \"103.77461234638255\",\n",
" \"class\": \"amenity\",\n",
" \"type\": \"university\",\n",
" \"place_rank\": 30,\n",
" \"importance\": 0.34662169301918117,\n",
" \"addresstype\": \"amenity\",\n",
" \"name\": \"Ngee Ann Polytechnic\",\n",
" \"display_name\": \"Ngee Ann Polytechnic, 535, Clementi Road, Bukit Timah, Singapore, Northwest, 599489, Singapore\",\n",
" \"address\": {\n",
" \"amenity\": \"Ngee Ann Polytechnic\",\n",
" \"house_number\": \"535\",\n",
" \"road\": \"Clementi Road\",\n",
" \"suburb\": \"Bukit Timah\",\n",
" \"city\": \"Singapore\",\n",
" \"county\": \"Northwest\",\n",
" \"ISO3166-2-lvl6\": \"SG-03\",\n",
" \"postcode\": \"599489\",\n",
" \"country\": \"Singapore\",\n",
" \"country_code\": \"sg\",\n",
" },\n",
" \"boundingbox\": [\"1.3289692\", \"1.3372184\", \"103.7701481\", \"103.7783945\"],\n",
" },\n",
" \"latitude\": 1.33318835,\n",
" \"longitude\": 103.77461234638255,\n",
"}"
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"{'ISO3166-2-lvl6': 'SG-03',\n",
" 'amenity': 'Ngee Ann Polytechnic',\n",
" 'city': 'Singapore',\n",
" 'country': 'Singapore',\n",
" 'country_code': 'sg',\n",
" 'county': 'Northwest',\n",
" 'house_number': '535',\n",
" 'latitude': 1.33318835,\n",
" 'longitude': 103.77461234638255,\n",
" 'postcode': '599489',\n",
" 'raw': {'address': {'ISO3166-2-lvl6': 'SG-03',\n",
" 'amenity': 'Ngee Ann Polytechnic',\n",
" 'city': 'Singapore',\n",
" 'country': 'Singapore',\n",
" 'country_code': 'sg',\n",
" 'county': 'Northwest',\n",
" 'house_number': '535',\n",
" 'postcode': '599489',\n",
" 'road': 'Clementi Road',\n",
" 'suburb': 'Bukit Timah'},\n",
" 'addresstype': 'amenity',\n",
" 'boundingbox': ['1.3289692',\n",
" '1.3372184',\n",
" '103.7701481',\n",
" '103.7783945'],\n",
" 'class': 'amenity',\n",
" 'display_name': 'Ngee Ann Polytechnic, 535, Clementi Road, Bukit '\n",
" 'Timah, Singapore, Northwest, 599489, Singapore',\n",
" 'importance': 0.34662169301918117,\n",
" 'lat': '1.33318835',\n",
" 'licence': 'Data © OpenStreetMap contributors, ODbL 1.0. '\n",
" 'http://osm.org/copyright',\n",
" 'lon': '103.77461234638255',\n",
" 'name': 'Ngee Ann Polytechnic',\n",
" 'osm_id': 2535118,\n",
" 'osm_type': 'relation',\n",
" 'place_id': 297946059,\n",
" 'place_rank': 30,\n",
" 'type': 'university'},\n",
" 'road': 'Clementi Road',\n",
" 'suburb': 'Bukit Timah'}\n"
]
}
],
"source": [
"import pprint\n",
"\n",
"latlong = LocalCodeQuery(code=\"8QMF+FV\", locality=\"Singapore\").to_lat_long_coord(\n",
" default_geocoder\n",
")\n",
"if not latlong:\n",
" latlong.cry()\n",
"\n",
"else:\n",
" location = default_reverser(latlong.get())\n",
" pprint.pprint(location)"
]
}
],
"metadata": {

View file

@ -58,7 +58,7 @@ from pluscodes.openlocationcode import ( # type: ignore # isort: skip
VERSION: Final[tuple[int, int, int]] = (2, 0, 0)
USER_AGENT: Final[str] = "surplus"
OUTPUT_LINE_0_KEYS: Final[tuple[str, ...]] = (
SHAREABLE_TEXT_LINE_0_KEYS: Final[tuple[str, ...]] = (
"emergency",
"historic",
"military",
@ -82,15 +82,14 @@ OUTPUT_LINE_0_KEYS: Final[tuple[str, ...]] = (
"tunnel",
"waterway",
)
OUTPUT_LINE_1_KEYS: Final[tuple[str, ...]] = ("building",)
OUTPUT_LINE_2_KEYS: Final[tuple[str, ...]] = ("highway",)
OUTPUT_LINE_3_NAME: Final[tuple[str, ...]] = ("house_name", "road")
OUTPUT_LINE_3_KEYS: Final[tuple[str, ...]] = (
SHAREABLE_TEXT_LINE_1_KEYS: Final[tuple[str, ...]] = ("building",)
SHAREABLE_TEXT_LINE_2_KEYS: Final[tuple[str, ...]] = ("highway",)
SHAREABLE_TEXT_LINE_3_KEYS: Final[tuple[str, ...]] = (
"house_number",
"house_name",
"road",
)
OUTPUT_LINE_4_KEYS: Final[tuple[str, ...]] = (
SHAREABLE_TEXT_LINE_4_KEYS: Final[tuple[str, ...]] = (
"residential",
"neighbourhood",
"allotments",
@ -105,8 +104,8 @@ OUTPUT_LINE_4_KEYS: Final[tuple[str, ...]] = (
"town",
"village",
)
OUTPUT_LINE_5_KEYS: Final[tuple[str, ...]] = ("postcode",)
OUTPUT_LINE_6_KEYS: Final[tuple[str, ...]] = (
SHAREABLE_TEXT_LINE_5_KEYS: Final[tuple[str, ...]] = ("postcode",)
SHAREABLE_TEXT_LINE_6_KEYS: Final[tuple[str, ...]] = (
"region",
"county",
"state",
@ -114,6 +113,12 @@ OUTPUT_LINE_6_KEYS: Final[tuple[str, ...]] = (
"country",
"continent",
)
SHAREABLE_TEXT_NAMES: Final[tuple[str, ...]] = (
SHAREABLE_TEXT_LINE_0_KEYS
+ SHAREABLE_TEXT_LINE_1_KEYS
+ SHAREABLE_TEXT_LINE_2_KEYS
+ ("house_name", "road")
)
# exceptions
@ -276,6 +281,7 @@ class PlusCodeQuery(NamedTuple):
return Result[Latlong](
EMPTY_LATLONG,
error=IncompletePlusCodeError(
"PlusCodeQuery.to_lat_long_coord: "
"Plus Code is not full-length (e.g., 6PH58QMF+FX)"
),
)
@ -435,7 +441,7 @@ def default_geocoder(place: str) -> Latlong:
)
def default_reverser(latlong: Latlong) -> dict[str, str]:
def default_reverser(latlong: Latlong) -> dict[str, Any]:
"""default geocoder for surplus, uses OpenStreetMap Nominatim"""
location: _geopy_Location | None = _geopy_Nominatim(user_agent=USER_AGENT).reverse(
str(latlong)
@ -444,21 +450,14 @@ def default_reverser(latlong: Latlong) -> dict[str, str]:
if location is None:
raise NoSuitableLocationError(f"could not reverse '{str(latlong)}'")
location_dict: dict[str, str] = {}
location_dict: dict[str, Any] = {}
for key in (address := location.raw.get("address", {})):
location_dict[key] = str(address.get(key, ""))
location_dict[key] = address.get(key, "")
_bounding_box_default: list[float] = [0.0] * 4
location_dict["original response"] = str(location.raw)
location_dict["latitude"] = str(location.longitude)
location_dict["latitude"] = str(location.latitude)
# location_dict["boundingbox1"] = str(location.raw.get("boundingbox", _bounding_box_default)[0])
# location_dict["boundingbox2"] = str(location.raw.get("boundingbox", _bounding_box_default)[1])
# location_dict["boundingbox3"] = str(location.raw.get("boundingbox", _bounding_box_default)[2])
# location_dict["boundingbox4"] = str(location.raw.get("boundingbox", _bounding_box_default)[3])
location_dict["raw"] = location.raw
location_dict["latitude"] = location.latitude
location_dict["longitude"] = location.longitude
return location_dict
@ -468,12 +467,13 @@ class Behaviour(NamedTuple):
typing.NamedTuple representing expected behaviour of surplus
arguments
query: list[str]
original user-passed query string split by spaces
query: str | list[str]
str: original user-passed query string
list[str]: original user-passed query string split by spaces
geocoder: Callable[[str], Latlong]
name string to location function, must take in a string and return a Latlong.
exceptions are handled by the caller.
reverser: Callable[[str], dict[str, str]]
reverser: Callable[[str], dict[str, Any]]
Latlong object to dictionary function, must take in a string and return a
dict. exceptions are handled by the caller.
stderr: TextIO = stderr
@ -488,9 +488,9 @@ class Behaviour(NamedTuple):
what type to convert query to
"""
query: list[str]
query: str | list[str]
geocoder: Callable[[str], Latlong] = default_geocoder
reverser: Callable[[Latlong], dict[str, str]] = default_reverser
reverser: Callable[[Latlong], dict[str, Any]] = default_reverser
stderr: TextIO = stderr
stdout: TextIO = stdout
debug: bool = False
@ -527,7 +527,14 @@ def parse_query(
validator = _PlusCode_Validator()
portion_plus_code: str = ""
portion_locality: str = ""
original_query: str = ""
split_query: list[str] = []
if isinstance(behaviour.query, str):
original_query = behaviour.query
split_query = behaviour.query.split(" ")
else:
original_query = " ".join(behaviour.query)
split_query = behaviour.query
@ -547,19 +554,19 @@ def parse_query(
error="unable to find a Plus Code",
)
# did find plus code, but not full-length. :(
if not validator.is_full(portion_plus_code):
return Result[Query](
LatlongQuery(EMPTY_LATLONG),
error=IncompletePlusCodeError(
"Plus Code is not full-length (e.g., 6PH58QMF+FX)"
),
)
# found a plus code!
portion_locality = original_query.replace(portion_plus_code, "")
portion_locality = portion_locality.strip().strip(",").strip()
# did find plus code, but not full-length. :(
if (portion_locality == "") and (not validator.is_full(portion_plus_code)):
return Result[Query](
LatlongQuery(EMPTY_LATLONG),
error=IncompletePlusCodeError(
"_match_plus_code: Plus Code is not full-length (e.g., 6PH58QMF+FX)"
),
)
if behaviour.debug:
behaviour.stderr.write(f"debug: {portion_plus_code=}, {portion_locality=}\n")
@ -589,7 +596,7 @@ def parse_query(
behaviour.stderr.write(f"debug: {behaviour.query=}\n")
# check if empty
if behaviour.query == []:
if (behaviour.query == []) or (behaviour.query == ""):
return Result[Query](
LatlongQuery(EMPTY_LATLONG),
error="query is empty",
@ -604,28 +611,43 @@ def parse_query(
if isinstance(mpc_result.error, IncompletePlusCodeError):
return mpc_result # propagate back up to caller
# handle query
original_query: str = ""
split_query: list[str] = []
if isinstance(behaviour.query, str):
original_query = behaviour.query
split_query = behaviour.query.split(" ")
else:
original_query = " ".join(behaviour.query)
split_query = behaviour.query
if behaviour.debug:
behaviour.stderr.write(f"debug: {split_query=}\ndebug: {original_query=}\n")
# not a plus/local code, try to match for latlong or string query
match behaviour.query:
match split_query:
case [single]:
# possibly a:
# comma-seperated single-word-long latlong coord
# (fallback) single word string query
if "," not in single: # no comma, not a latlong coord
return Result[Query](StringQuery(" ".join(behaviour.query)))
return Result[Query](StringQuery(original_query))
else: # has comma, possibly a latlong coord
split_query: list[str] = single.split(",")
comma_split_single: list[str] = single.split(",")
if len(split_query) > 2:
if len(comma_split_single) > 2:
return Result[Query](
LatlongQuery(EMPTY_LATLONG),
error="unable to parse latlong coord",
)
try: # try to type cast query
latitude = float(split_query[0].strip(","))
longitude = float(split_query[-1].strip(","))
latitude = float(comma_split_single[0].strip(","))
longitude = float(comma_split_single[-1].strip(","))
except ValueError: # not a latlong coord, fallback
return Result[Query](StringQuery(single))
@ -650,7 +672,7 @@ def parse_query(
longitude = float(right_single.strip(","))
except ValueError: # not a latlong coord, fallback
return Result[Query](StringQuery(" ".join(behaviour.query)))
return Result[Query](StringQuery(original_query))
else: # are floats, so is a latlong coord
return Result[Query](
@ -661,7 +683,7 @@ def parse_query(
# possibly a:
# (fallback) space-seperated string query
return Result[Query](StringQuery(" ".join(behaviour.query)))
return Result[Query](StringQuery(original_query))
def handle_args() -> Behaviour:
@ -736,15 +758,16 @@ def _unique(l: Sequence[str]) -> list[str]:
return list(unique.keys())
def _generate_text(location: dict[str, str], debug: bool = False) -> str:
def _generate_text(
location: dict[str, Any], behaviour: Behaviour, debug: bool = False
) -> str:
"""(internal function) generate shareable text from location dict"""
def _generate_text_line(
line_number: int,
line_keys: Sequence[str],
seperator: str = ", ",
element_check: Callable[[str], bool] = lambda e: True,
debug: bool = False,
filter: Callable[[str], list[bool]] = lambda e: [True],
) -> str:
"""
(internal function) generate a line of shareable text from a list of keys
@ -754,11 +777,10 @@ def _generate_text(location: dict[str, str], debug: bool = False) -> str:
line number to prefix with
line_keys: Sequence[str]
list of keys to .get() from location dict
element_check: Callable[[str], bool] = lambda e: True
function that takes in a string and returns a bool, used to filter
elements from line_keys
debug: bool = False
whether to prefix line with line_number
filter: Callable[[str], list[bool]] = lambda e: True
function that takes in a string and returns a list of bools, used to
filter elements from line_keys. list will be passed to all(). if all
returns True, then the element is kept.
returns str
"""
@ -766,60 +788,93 @@ def _generate_text(location: dict[str, str], debug: bool = False) -> str:
line_prefix: str = f"{line_number}\t" if debug else ""
basket: list[str] = []
for detail in _unique([location.get(detail, "") for detail in line_keys]):
for detail in _unique([str(location.get(detail, "")) for detail in line_keys]):
if detail == "":
continue
if element_check(detail):
# filtering: if all(filter(detail)) returns True,
# then the element is kept/added to 'basket'
if filter_status := all(detail_check := filter(detail)) is True:
if debug:
behaviour.stderr.write(
"debug: _generate_line_text: "
f"{str(detail_check):<20} -> {str(filter_status):<5} "
f"-------- '{detail}'\n"
)
basket.append(detail)
line = line_prefix + seperator.join(basket)
else: # filter function returned False, so element is filtered/skipped
if debug:
behaviour.stderr.write(
"debug: _generate_line_text: "
f"{str(detail_check):<20} -> {str(filter_status):<5}"
f" filtered '{detail}'\n"
)
continue
line = line_prefix + seperator.join(basket)
return (line + "\n") if (line != "") else ""
# iso3166-2 handling: this allows surplus to have special key arrangements for a
# specific iso3166-2 code for edge cases
# (https://en.wikipedia.org/wiki/ISO_3166-2)
# get iso3166-2 before doing anything
iso3166_2: str = ""
for key in location:
if key.startswith("iso3166"):
iso3166_2 = location.get(key, "")
# skeleton code to allow for changing keys based on iso3166-2 code
st_line0_keys = SHAREABLE_TEXT_LINE_0_KEYS
st_line1_keys = SHAREABLE_TEXT_LINE_1_KEYS
st_line2_keys = SHAREABLE_TEXT_LINE_2_KEYS
st_line3_keys = SHAREABLE_TEXT_LINE_3_KEYS
st_line4_keys = SHAREABLE_TEXT_LINE_4_KEYS
st_line5_keys = SHAREABLE_TEXT_LINE_5_KEYS
st_line6_keys = SHAREABLE_TEXT_LINE_6_KEYS
st_names = SHAREABLE_TEXT_NAMES
match iso3166_2.split("-"):
case _:
pass
# start generating text
text: list[str] = []
seen_names: list[str] = [
detail
for detail in _unique(
[
location.get(location_key, "")
for location_key in (
OUTPUT_LINE_0_KEYS
+ OUTPUT_LINE_1_KEYS
+ OUTPUT_LINE_2_KEYS
+ OUTPUT_LINE_3_NAME
)
]
[str(location.get(location_key, "")) for location_key in st_names]
)
if detail != ""
]
general_global_info: list[str] = [
location.get(detail, "") for detail in OUTPUT_LINE_6_KEYS
str(location.get(detail, "")) for detail in st_line6_keys
]
text.append(_generate_text_line(0, OUTPUT_LINE_0_KEYS, debug=debug))
text.append(_generate_text_line(1, OUTPUT_LINE_1_KEYS, debug=debug))
text.append(_generate_text_line(2, OUTPUT_LINE_2_KEYS, debug=debug))
text.append(_generate_text_line(3, OUTPUT_LINE_3_KEYS, seperator=" ", debug=debug))
text.append(_generate_text_line(0, st_line0_keys))
text.append(_generate_text_line(1, st_line1_keys))
text.append(_generate_text_line(2, st_line2_keys))
text.append(_generate_text_line(3, st_line3_keys, seperator=" "))
text.append(
_generate_text_line(
4,
OUTPUT_LINE_4_KEYS,
element_check=lambda ak: all(
[
st_line4_keys,
filter=lambda ak: [
# everything here should be True if the element is to be kept
ak not in general_global_info,
any(True if (ak not in sn) else False for sn in seen_names),
]
),
debug=debug,
not any(True if (ak in sn) else False for sn in seen_names),
],
)
)
text.append(_generate_text_line(5, OUTPUT_LINE_5_KEYS, debug=debug))
text.append(_generate_text_line(6, OUTPUT_LINE_6_KEYS, debug=debug))
text.append(_generate_text_line(5, st_line5_keys))
text.append(_generate_text_line(6, st_line6_keys))
return "".join(text)
return "".join(_unique(text)).rstrip()
def surplus(
@ -852,8 +907,9 @@ def surplus(
if behaviour.debug:
behaviour.stderr.write(f"debug: {latlong.get()=}\n")
# reverse location and handle result
try:
location: dict[str, str] = behaviour.reverser(latlong.get())
location: dict[str, Any] = behaviour.reverser(latlong.get())
except Exception as exc:
return Result[str]("", error=exc)
@ -861,17 +917,21 @@ def surplus(
if behaviour.debug:
behaviour.stderr.write(f"debug: {location=}\n")
# generate text
if behaviour.debug:
behaviour.stderr.write(
_generate_text(
location=location,
behaviour=behaviour,
debug=behaviour.debug,
)
+ "\n"
)
text = _generate_text(
location=location,
).rstrip()
behaviour=behaviour,
)
return Result[str](text)
@ -918,13 +978,13 @@ def cli() -> int:
# parse query and handle result
query = parse_query(behaviour=behaviour)
if behaviour.debug:
behaviour.stderr.write(f"debug: {query=}\n")
if not query:
behaviour.stderr.write(f"error: {query.cry(string=not behaviour.debug)}\n")
return -1
if behaviour.debug:
behaviour.stderr.write(f"debug: {query.get()=}\n")
# run surplus
text = surplus(
query=query.get(),

78
test.py
View file

@ -39,6 +39,7 @@ from typing import Final, NamedTuple
import surplus
INDENT: Final[int] = 3
MINIMUM_PASS_RATE: Final[float] = 0.7 # because results can be flaky
class ContinuityTest(NamedTuple):
@ -54,10 +55,8 @@ class TestFailure(NamedTuple):
tests: list[ContinuityTest] = [
ContinuityTest(
query="8RPQ+JW Singapore",
expected=(
"Caldecott Stn Exit 4\n" "Toa Payoh Link\n" "298106\n" "Central, Singapore"
),
query="8R3M+F8 Singapore",
expected=("Wisma Atria\n" "435 Orchard Road\n" "238877\n" "Central, Singapore"),
),
ContinuityTest(
query="9R3J+R9 Singapore",
@ -73,18 +72,32 @@ tests: list[ContinuityTest] = [
query="3RQ3+HW3 Pemping, Batam City, Riau Islands, Indonesia",
expected=("Batam\n" "Kepulauan Riau, Indonesia"),
),
# ContinuityTest(
# query="CQPP+QM9 Singapore",
# expected=(
# "Woodlands Integrated Transport Hub\n" "738343\n" "Northwest, Singapore"
# ),
# ),
ContinuityTest(
query="8RRX+75Q Singapore",
query="St Lucia, Queensland, Australia G227+XF",
expected=(
"Braddell Station/Blk 106\n"
"Lorong 1 Toa Payoh\n"
"319758\n"
"The University of Queensland\n"
"Macquarie Street\n"
"St Lucia, Greater Brisbane\n"
"4072\n"
"Queensland, Australia"
),
),
ContinuityTest(
query="Ngee Ann Polytechnic, Singapore",
expected=(
"Ngee Ann Polytechnic\n"
"535 Clementi Road\n"
"Bukit Timah\n"
"599489\n"
"Northwest, Singapore"
),
),
ContinuityTest(
query="1.3521, 103.8198",
expected=(
"MacRitchie Nature Trail"
"Central Water Catchment\n"
"574325\n"
"Central, Singapore"
),
),
@ -108,29 +121,29 @@ def main() -> int:
for idx, test in enumerate(tests, start=1):
print(f"[{idx}/{len(tests)}] {test.query}")
output: str = ""
behaviour = surplus.Behaviour(test.query)
try:
query = surplus.parse_query(query=test.query)
query = surplus.parse_query(behaviour)
if query[0] is False:
raise QueryParseFailure(f"test query parse result returned False")
if not query:
raise QueryParseFailure(query.cry())
result = surplus.surplus(query=query[1])
result = surplus.surplus(query.get(), behaviour)
if result[0] is False:
raise SurplusFailure(result[1])
if not result:
raise SurplusFailure(result.cry())
output = result[1]
print(indent(text=output, prefix=INDENT * " "))
output = result.get()
if output != test.expected:
raise ContinuityFailure(f"test did not match output")
raise ContinuityFailure("did not match output")
except Exception as exc:
failures.append(TestFailure(test=test, exception=exc, output=output))
stderr.write(indent(text="(fail)", prefix=INDENT * " ") + "\n")
stderr.write(indent(text="(fail)", prefix=INDENT * " ") + "\n\n")
else:
stderr.write(indent(text="(pass)", prefix=INDENT * " ") + "\n\n")
@ -153,9 +166,20 @@ def main() -> int:
+ (indent(text=fail.output, prefix=(2 * INDENT) * " "))
)
print(f"\ncomplete: {len(tests) - len(failures)} passed, {len(failures)} failed")
passes = len(tests) - len(failures)
pass_rate = round(passes / len(tests), 2)
return len(failures)
print(
f"complete: {passes} passed, {len(failures)} failed "
f"({pass_rate * 100:.0f}%/{pass_rate * 100:.0f}%)"
)
if passes < MINIMUM_PASS_RATE:
print("continuity pass rate is under minimum, test suite failed ;<")
return 1
print("continuity tests passed :>")
return 0
if __name__ == "__main__":