App engine architecture¶
Overview¶
The application is structured around three composable layers that deliberately keep async search logic, personalization, and widget rendering as separate concerns.
For route-specific layering (RouteEngine vs RouteController), see
Route engine and widget controller architecture.
OneBoxCore ← async search engine (headless)
│
UserProfileMixin ← composable personalization side-class
│
SearchHead (Protocol) ← explicit contract for response rendering
│
OneBoxMap(UserProfileMixin, OneBoxCore) ← full ipyleaflet/ipywidgets UI
OneBoxCore¶
File: src/here_search_demo/base.py
The invariant, headless async search engine. It has no dependency on any widget library, credential system, or user profile concept.
Responsibilities:
Async event loop lifecycle (
run()/stop())asyncio.Queue-based intent consumptionTransient-text coalescing (debounce without a timer)
Intent triage: resolves a
SearchIntentinto a typedSearchEvent, a handler, and anEndpointConfigvia theTRIAGESdispatch tableHTTPSessionmanagement and postprocess callback chain_get_context()— produces a bareRequestContextfromsearch_center+preferred_languageNo-op response stubs (
handle_suggestion_list,handle_result_list, …) that satisfy theSearchHeadProtocol by default
Usage (headless, e.g. in tests or API servers):
from here_search_demo.base import OneBoxCore
app = OneBoxCore()
app.queue.put_nowait(intent)
intent_out, event, resp = await app.handle_search_event(session)
UserProfileMixin¶
File: src/here_search_demo/base.py
A composable mixin that binds a UserProfile to any OneBoxCore subclass. It is a
side concern — it adds personalization without coupling to any specific rendering head.
Responsibilities:
Seeds
OneBoxCorewith position and language from the profile at construction timeEnriches
_get_context()withshare_experienceanduser_id(via cooperativesuper())Hooks
handle_search_eventto calladapt_language()after full-text or taxonomy searchesLanguage adaptation: detects the dominant country code in a result set and switches
preferred_languageto match the user’s country preferenceset_search_center()utility
Cooperative MRO usage:
class MyApp(UserProfileMixin, OneBoxCore):
...
# MRO: MyApp → UserProfileMixin → OneBoxCore
# Every super() call chains correctly without explicit class references.
Isolation: UserProfileMixin can be tested by mixing it with a minimal OneBoxCore
subclass; it has no ipywidget or credential dependency.
SearchHead Protocol¶
File: src/here_search_demo/base.py
A typing.Protocol that documents the override contract for any class acting as a
search result head. OneBoxCore already satisfies it via its no-op stubs — structural
subtyping means no explicit registration is needed.
from here_search_demo.base import SearchHead
Protocol methods:
Method |
Called when |
|---|---|
|
Autosuggest results arrive |
|
Full-text / taxonomy results arrive |
|
Lookup details arrive |
|
Empty query submitted |
|
User clicks a result item |
|
Before the event loop starts |
Building an alternative head:
class TerminalHead(OneBoxCore):
"""Rich/Textual-based rendering head."""
def handle_suggestion_list(self, intent, response):
rich.print(response.data)
def handle_result_list(self, intent, response):
rich.print(response.data)
...
class TerminalApp(UserProfileMixin, TerminalHead):
...
OneBoxMap¶
File: src/here_search_demo/widgets/app.py
The full interactive demo widget. Inherits from both UserProfileMixin and OneBoxCore,
wiring ipyleaflet map rendering and ipywidgets UI into the search engine.
MRO: OneBoxMap → UserProfileMixin → OneBoxCore
Responsibilities (pure UI/rendering):
Credential and
APIconstructionWidget instantiation:
ResponseMap,SubmittableTextBox,PlaceTaxonomyButtons,SearchResultButtons,SearchResultJson,TableLogWidgetWidget layout composition (VBox / HBox / WidgetControl)
Full
SearchHeadmethod implementations (render suggestions, results, details on the map)Routing / detour integration
Recommendation reranking
CORS /
X-User-IDmanagementSignals lifecycle (
search_events_preprocess,stop)
Construction:
from here_search_demo.widgets.app import OneBoxMap
app = OneBoxMap(map_only=True, on_map=True)
app.show()
app.run()
Separation of concerns summary¶
Concern |
Class |
|---|---|
Async event loop, intent queue, triage |
|
UserProfile, language adaptation, context enrichment |
|
Response handler contract |
|
Widget rendering, map, credentials, layout |
|
Adding a new head¶
To build a non-map head (e.g. a terminal UI or a REST API responder):
Create a class that inherits
(UserProfileMixin, OneBoxCore)(or justOneBoxCoreif personalization is not needed).Override the
SearchHeadmethods to render results in your medium.Optionally override
search_events_preprocessfor startup logic.
No changes to OneBoxCore or UserProfileMixin are required.