diff --git a/robosystems/models/api/entity_graph.py b/robosystems/models/api/entity_graph.py index c2ef5aca..9072bf56 100644 --- a/robosystems/models/api/entity_graph.py +++ b/robosystems/models/api/entity_graph.py @@ -121,6 +121,10 @@ class EntityWithGraphResponse(BaseModel): class AvailableExtension(BaseModel): name: str + # The name to show a user. `name` is the schema slug the create path takes + # ("roboledger"), which is not what a tier or extension picker should be + # rendering — clients fall back to the slug only when this is absent. + display_name: str | None = None description: str enabled: bool = False diff --git a/robosystems/routers/graphs/main.py b/robosystems/routers/graphs/main.py index 519fb7fa..7dff03e3 100644 --- a/robosystems/routers/graphs/main.py +++ b/robosystems/routers/graphs/main.py @@ -54,8 +54,14 @@ router = APIRouter(prefix="/v1/graphs", tags=["Graphs"]) # Surfaced to prospects at graph-creation time, so these describe only what is -# built. Both are read twice below — the schema-loading path and its fallback — -# and single-sourcing them here is what keeps the two copies from diverging. +# built. All three are read twice below — the schema-loading path and its +# fallback — and single-sourcing them here is what keeps the copies from +# diverging. +_DISPLAY_NAMES = { + "roboledger": "RoboLedger - Accounting & Financial Reporting", + "roboinvestor": "RoboInvestor - Investment Management", +} + _ROBOLEDGER_DESCRIPTION = ( "Complete accounting system with XBRL reporting and GL transactions. " "Context-aware: SEC repositories get reporting-only tables, " @@ -538,12 +544,6 @@ async def get_available_extensions( f"Extension {ext_info['name']}: available={ext_info.get('available', False)}" ) if ext_info["available"]: - # Get display names for extensions - display_names = { - "roboledger": "RoboLedger - Accounting & Financial Reporting", - "roboinvestor": "RoboInvestor - Investment Management", - } - # Try to get actual node/relationship counts try: from robosystems.schemas.loader import ( @@ -577,7 +577,7 @@ async def get_available_extensions( available_extensions.append( { "name": ext_info["name"], - "display_name": display_names.get( + "display_name": _DISPLAY_NAMES.get( ext_info["name"], ext_info["name"].title() ), "description": description, # Use the correctly set description @@ -589,7 +589,10 @@ async def get_available_extensions( # Convert dictionaries to AvailableExtension objects extension_objects = [ AvailableExtension( - name=str(ext["name"]), description=str(ext["description"]), enabled=False + name=str(ext["name"]), + display_name=str(ext["display_name"]), + description=str(ext["description"]), + enabled=False, ) for ext in available_extensions ] @@ -605,11 +608,13 @@ async def get_available_extensions( extensions=[ AvailableExtension( name="roboledger", + display_name=_DISPLAY_NAMES["roboledger"], description=_ROBOLEDGER_DESCRIPTION, enabled=False, ), AvailableExtension( name="roboinvestor", + display_name=_DISPLAY_NAMES["roboinvestor"], description=_ROBOINVESTOR_DESCRIPTION, enabled=False, ), diff --git a/tests/routers/graphs/test_schema_integration.py b/tests/routers/graphs/test_schema_integration.py index b0c3b974..35b62f73 100644 --- a/tests/routers/graphs/test_schema_integration.py +++ b/tests/routers/graphs/test_schema_integration.py @@ -360,6 +360,19 @@ async def test_schema_compatibility_workflow( extensions = extensions_response.json() assert len(extensions["extensions"]) == 2 + # `name` is the slug the create path takes; `display_name` is what a + # picker renders. The display name used to be computed and then dropped + # when the response model was built, leaving clients with "roboledger". + by_name = {ext["name"]: ext for ext in extensions["extensions"]} + assert ( + by_name["roboledger"]["display_name"] + == "RoboLedger - Accounting & Financial Reporting" + ) + assert ( + by_name["roboinvestor"]["display_name"] + == "RoboInvestor - Investment Management" + ) + # Now create a schema that should be compatible with roboledger financial_schema = { "name": "custom_financial",