mainsequence 3.11.2__tar.gz → 3.12.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {mainsequence-3.11.2 → mainsequence-3.12.2}/PKG-INFO +1 -1
- mainsequence-3.12.2/mainsequence/ai/__init__.py +1 -0
- mainsequence-3.12.2/mainsequence/ai/base.py +314 -0
- mainsequence-3.12.2/mainsequence/ai/cli.py +54 -0
- {mainsequence-3.11.2 → mainsequence-3.12.2}/mainsequence/client/models_tdag.py +19 -0
- mainsequence-3.12.2/mainsequence/dashboards/streamlit/components/asset_select.py +158 -0
- mainsequence-3.12.2/mainsequence/dashboards/streamlit/components/date_settings.py +50 -0
- mainsequence-3.12.2/mainsequence/virtualfundbuilder/resource_factory/__init__.py +0 -0
- {mainsequence-3.11.2 → mainsequence-3.12.2}/mainsequence/virtualfundbuilder/resource_factory/base_factory.py +1 -1
- {mainsequence-3.11.2 → mainsequence-3.12.2}/mainsequence/virtualfundbuilder/resource_factory/signal_factory.py +1 -2
- {mainsequence-3.11.2 → mainsequence-3.12.2}/mainsequence.egg-info/PKG-INFO +1 -1
- {mainsequence-3.11.2 → mainsequence-3.12.2}/mainsequence.egg-info/SOURCES.txt +6 -3
- {mainsequence-3.11.2 → mainsequence-3.12.2}/pyproject.toml +1 -1
- mainsequence-3.11.2/mainsequence/virtualfundbuilder/agent_interface.py +0 -76
- mainsequence-3.11.2/mainsequence/virtualfundbuilder/contrib/apps/news_app.py +0 -493
- mainsequence-3.11.2/mainsequence/virtualfundbuilder/resource_factory/app_factory.py +0 -181
- {mainsequence-3.11.2 → mainsequence-3.12.2}/LICENSE +0 -0
- {mainsequence-3.11.2 → mainsequence-3.12.2}/README.md +0 -0
- {mainsequence-3.11.2 → mainsequence-3.12.2}/mainsequence/__init__.py +0 -0
- {mainsequence-3.11.2 → mainsequence-3.12.2}/mainsequence/__main__.py +0 -0
- {mainsequence-3.11.2 → mainsequence-3.12.2}/mainsequence/cli/__init__.py +0 -0
- {mainsequence-3.11.2 → mainsequence-3.12.2}/mainsequence/cli/api.py +0 -0
- {mainsequence-3.11.2 → mainsequence-3.12.2}/mainsequence/cli/cli.py +0 -0
- {mainsequence-3.11.2 → mainsequence-3.12.2}/mainsequence/cli/config.py +0 -0
- {mainsequence-3.11.2 → mainsequence-3.12.2}/mainsequence/cli/ssh_utils.py +0 -0
- {mainsequence-3.11.2 → mainsequence-3.12.2}/mainsequence/client/__init__.py +0 -0
- {mainsequence-3.11.2 → mainsequence-3.12.2}/mainsequence/client/base.py +0 -0
- {mainsequence-3.11.2 → mainsequence-3.12.2}/mainsequence/client/data_sources_interfaces/__init__.py +0 -0
- {mainsequence-3.11.2 → mainsequence-3.12.2}/mainsequence/client/data_sources_interfaces/duckdb.py +0 -0
- {mainsequence-3.11.2 → mainsequence-3.12.2}/mainsequence/client/data_sources_interfaces/timescale.py +0 -0
- {mainsequence-3.11.2 → mainsequence-3.12.2}/mainsequence/client/exceptions.py +0 -0
- {mainsequence-3.11.2 → mainsequence-3.12.2}/mainsequence/client/models_helpers.py +0 -0
- {mainsequence-3.11.2 → mainsequence-3.12.2}/mainsequence/client/models_vam.py +0 -0
- {mainsequence-3.11.2 → mainsequence-3.12.2}/mainsequence/client/utils.py +0 -0
- {mainsequence-3.11.2 → mainsequence-3.12.2}/mainsequence/dashboards/__init__.py +0 -0
- {mainsequence-3.11.2 → mainsequence-3.12.2}/mainsequence/dashboards/streamlit/__init__.py +0 -0
- {mainsequence-3.11.2 → mainsequence-3.12.2}/mainsequence/dashboards/streamlit/assets/config.toml +0 -0
- {mainsequence-3.11.2 → mainsequence-3.12.2}/mainsequence/dashboards/streamlit/assets/favicon.png +0 -0
- {mainsequence-3.11.2 → mainsequence-3.12.2}/mainsequence/dashboards/streamlit/assets/image_1_base64.txt +0 -0
- {mainsequence-3.11.2 → mainsequence-3.12.2}/mainsequence/dashboards/streamlit/assets/image_2_base64.txt +0 -0
- {mainsequence-3.11.2 → mainsequence-3.12.2}/mainsequence/dashboards/streamlit/assets/image_3_base64.txt +0 -0
- {mainsequence-3.11.2 → mainsequence-3.12.2}/mainsequence/dashboards/streamlit/assets/image_4_base64.txt +0 -0
- {mainsequence-3.11.2 → mainsequence-3.12.2}/mainsequence/dashboards/streamlit/assets/image_5_base64.txt +0 -0
- {mainsequence-3.11.2 → mainsequence-3.12.2}/mainsequence/dashboards/streamlit/assets/logo.png +0 -0
- {mainsequence-3.11.2/mainsequence/dashboards/streamlit/core → mainsequence-3.12.2/mainsequence/dashboards/streamlit/components}/__init__.py +0 -0
- {mainsequence-3.11.2/mainsequence/dashboards/streamlit/pages → mainsequence-3.12.2/mainsequence/dashboards/streamlit/core}/__init__.py +0 -0
- {mainsequence-3.11.2 → mainsequence-3.12.2}/mainsequence/dashboards/streamlit/core/theme.py +0 -0
- {mainsequence-3.11.2/mainsequence/instruments/interest_rates → mainsequence-3.12.2/mainsequence/dashboards/streamlit/pages}/__init__.py +0 -0
- {mainsequence-3.11.2 → mainsequence-3.12.2}/mainsequence/dashboards/streamlit/scaffold.py +0 -0
- {mainsequence-3.11.2 → mainsequence-3.12.2}/mainsequence/instrumentation/__init__.py +0 -0
- {mainsequence-3.11.2 → mainsequence-3.12.2}/mainsequence/instrumentation/utils.py +0 -0
- {mainsequence-3.11.2 → mainsequence-3.12.2}/mainsequence/instruments/__init__.py +0 -0
- {mainsequence-3.11.2 → mainsequence-3.12.2}/mainsequence/instruments/data_interface/__init__.py +0 -0
- {mainsequence-3.11.2 → mainsequence-3.12.2}/mainsequence/instruments/data_interface/data_interface.py +0 -0
- {mainsequence-3.11.2 → mainsequence-3.12.2}/mainsequence/instruments/instruments/__init__.py +0 -0
- {mainsequence-3.11.2 → mainsequence-3.12.2}/mainsequence/instruments/instruments/base_instrument.py +0 -0
- {mainsequence-3.11.2 → mainsequence-3.12.2}/mainsequence/instruments/instruments/bond.py +0 -0
- {mainsequence-3.11.2 → mainsequence-3.12.2}/mainsequence/instruments/instruments/european_option.py +0 -0
- {mainsequence-3.11.2 → mainsequence-3.12.2}/mainsequence/instruments/instruments/interest_rate_swap.py +0 -0
- {mainsequence-3.11.2 → mainsequence-3.12.2}/mainsequence/instruments/instruments/json_codec.py +0 -0
- {mainsequence-3.11.2 → mainsequence-3.12.2}/mainsequence/instruments/instruments/knockout_fx_option.py +0 -0
- {mainsequence-3.11.2 → mainsequence-3.12.2}/mainsequence/instruments/instruments/position.py +0 -0
- {mainsequence-3.11.2 → mainsequence-3.12.2}/mainsequence/instruments/instruments/ql_fields.py +0 -0
- {mainsequence-3.11.2 → mainsequence-3.12.2}/mainsequence/instruments/instruments/vanilla_fx_option.py +0 -0
- {mainsequence-3.11.2/mainsequence/instruments/interest_rates/etl → mainsequence-3.12.2/mainsequence/instruments/interest_rates}/__init__.py +0 -0
- {mainsequence-3.11.2/mainsequence/instruments/pricing_models → mainsequence-3.12.2/mainsequence/instruments/interest_rates/etl}/__init__.py +0 -0
- {mainsequence-3.11.2 → mainsequence-3.12.2}/mainsequence/instruments/interest_rates/etl/curve_codec.py +0 -0
- {mainsequence-3.11.2 → mainsequence-3.12.2}/mainsequence/instruments/interest_rates/etl/nodes.py +0 -0
- {mainsequence-3.11.2 → mainsequence-3.12.2}/mainsequence/instruments/interest_rates/etl/registry.py +0 -0
- {mainsequence-3.11.2/mainsequence/virtualfundbuilder/contrib → mainsequence-3.12.2/mainsequence/instruments/pricing_models}/__init__.py +0 -0
- {mainsequence-3.11.2 → mainsequence-3.12.2}/mainsequence/instruments/pricing_models/black_scholes.py +0 -0
- {mainsequence-3.11.2 → mainsequence-3.12.2}/mainsequence/instruments/pricing_models/bond_pricer.py +0 -0
- {mainsequence-3.11.2 → mainsequence-3.12.2}/mainsequence/instruments/pricing_models/fx_option_pricer.py +0 -0
- {mainsequence-3.11.2 → mainsequence-3.12.2}/mainsequence/instruments/pricing_models/indices.py +0 -0
- {mainsequence-3.11.2 → mainsequence-3.12.2}/mainsequence/instruments/pricing_models/indices_builders.py +0 -0
- {mainsequence-3.11.2 → mainsequence-3.12.2}/mainsequence/instruments/pricing_models/knockout_fx_pricer.py +0 -0
- {mainsequence-3.11.2 → mainsequence-3.12.2}/mainsequence/instruments/pricing_models/swap_pricer.py +0 -0
- {mainsequence-3.11.2 → mainsequence-3.12.2}/mainsequence/instruments/settings.py +0 -0
- {mainsequence-3.11.2 → mainsequence-3.12.2}/mainsequence/instruments/utils.py +0 -0
- {mainsequence-3.11.2 → mainsequence-3.12.2}/mainsequence/logconf.py +0 -0
- {mainsequence-3.11.2 → mainsequence-3.12.2}/mainsequence/tdag/__init__.py +0 -0
- {mainsequence-3.11.2 → mainsequence-3.12.2}/mainsequence/tdag/__main__.py +0 -0
- {mainsequence-3.11.2 → mainsequence-3.12.2}/mainsequence/tdag/config.py +0 -0
- {mainsequence-3.11.2 → mainsequence-3.12.2}/mainsequence/tdag/data_nodes/__init__.py +0 -0
- {mainsequence-3.11.2 → mainsequence-3.12.2}/mainsequence/tdag/data_nodes/build_operations.py +0 -0
- {mainsequence-3.11.2 → mainsequence-3.12.2}/mainsequence/tdag/data_nodes/data_nodes.py +0 -0
- {mainsequence-3.11.2 → mainsequence-3.12.2}/mainsequence/tdag/data_nodes/persist_managers.py +0 -0
- {mainsequence-3.11.2 → mainsequence-3.12.2}/mainsequence/tdag/data_nodes/run_operations.py +0 -0
- {mainsequence-3.11.2 → mainsequence-3.12.2}/mainsequence/tdag/data_nodes/utils.py +0 -0
- {mainsequence-3.11.2 → mainsequence-3.12.2}/mainsequence/tdag/future_registry.py +0 -0
- {mainsequence-3.11.2 → mainsequence-3.12.2}/mainsequence/tdag/utils.py +0 -0
- {mainsequence-3.11.2 → mainsequence-3.12.2}/mainsequence/virtualfundbuilder/__init__.py +0 -0
- {mainsequence-3.11.2 → mainsequence-3.12.2}/mainsequence/virtualfundbuilder/config_handling.py +0 -0
- {mainsequence-3.11.2/mainsequence/virtualfundbuilder/contrib/apps/report_styles → mainsequence-3.12.2/mainsequence/virtualfundbuilder/contrib}/__init__.py +0 -0
- {mainsequence-3.11.2 → mainsequence-3.12.2}/mainsequence/virtualfundbuilder/contrib/apps/__init__.py +0 -0
- {mainsequence-3.11.2 → mainsequence-3.12.2}/mainsequence/virtualfundbuilder/contrib/apps/etf_replicator_app.py +0 -0
- {mainsequence-3.11.2 → mainsequence-3.12.2}/mainsequence/virtualfundbuilder/contrib/apps/generate_report.py +0 -0
- {mainsequence-3.11.2 → mainsequence-3.12.2}/mainsequence/virtualfundbuilder/contrib/apps/load_external_portfolio.py +0 -0
- {mainsequence-3.11.2 → mainsequence-3.12.2}/mainsequence/virtualfundbuilder/contrib/apps/portfolio_report_app.py +0 -0
- {mainsequence-3.11.2 → mainsequence-3.12.2}/mainsequence/virtualfundbuilder/contrib/apps/portfolio_table.py +0 -0
- {mainsequence-3.11.2/mainsequence/virtualfundbuilder/resource_factory → mainsequence-3.12.2/mainsequence/virtualfundbuilder/contrib/apps/report_styles}/__init__.py +0 -0
- {mainsequence-3.11.2 → mainsequence-3.12.2}/mainsequence/virtualfundbuilder/contrib/apps/report_styles/models.py +0 -0
- {mainsequence-3.11.2 → mainsequence-3.12.2}/mainsequence/virtualfundbuilder/contrib/apps/report_styles/utils.py +0 -0
- {mainsequence-3.11.2 → mainsequence-3.12.2}/mainsequence/virtualfundbuilder/contrib/apps/run_named_portfolio.py +0 -0
- {mainsequence-3.11.2 → mainsequence-3.12.2}/mainsequence/virtualfundbuilder/contrib/apps/run_portfolio.py +0 -0
- {mainsequence-3.11.2 → mainsequence-3.12.2}/mainsequence/virtualfundbuilder/contrib/apps/templates/base.html +0 -0
- {mainsequence-3.11.2 → mainsequence-3.12.2}/mainsequence/virtualfundbuilder/contrib/apps/templates/report.html +0 -0
- {mainsequence-3.11.2 → mainsequence-3.12.2}/mainsequence/virtualfundbuilder/contrib/data_nodes/__init__.py +0 -0
- {mainsequence-3.11.2 → mainsequence-3.12.2}/mainsequence/virtualfundbuilder/contrib/data_nodes/external_weights.py +0 -0
- {mainsequence-3.11.2 → mainsequence-3.12.2}/mainsequence/virtualfundbuilder/contrib/data_nodes/intraday_trend.py +0 -0
- {mainsequence-3.11.2 → mainsequence-3.12.2}/mainsequence/virtualfundbuilder/contrib/data_nodes/market_cap.py +0 -0
- {mainsequence-3.11.2 → mainsequence-3.12.2}/mainsequence/virtualfundbuilder/contrib/data_nodes/mock_signal.py +0 -0
- {mainsequence-3.11.2 → mainsequence-3.12.2}/mainsequence/virtualfundbuilder/contrib/data_nodes/portfolio_replicator.py +0 -0
- {mainsequence-3.11.2 → mainsequence-3.12.2}/mainsequence/virtualfundbuilder/contrib/prices/__init__.py +0 -0
- {mainsequence-3.11.2 → mainsequence-3.12.2}/mainsequence/virtualfundbuilder/contrib/prices/data_nodes.py +0 -0
- {mainsequence-3.11.2 → mainsequence-3.12.2}/mainsequence/virtualfundbuilder/contrib/prices/utils.py +0 -0
- {mainsequence-3.11.2 → mainsequence-3.12.2}/mainsequence/virtualfundbuilder/contrib/rebalance_strategies/__init__.py +0 -0
- {mainsequence-3.11.2 → mainsequence-3.12.2}/mainsequence/virtualfundbuilder/contrib/rebalance_strategies/rebalance_strategies.py +0 -0
- {mainsequence-3.11.2 → mainsequence-3.12.2}/mainsequence/virtualfundbuilder/data_nodes.py +0 -0
- {mainsequence-3.11.2 → mainsequence-3.12.2}/mainsequence/virtualfundbuilder/enums.py +0 -0
- {mainsequence-3.11.2 → mainsequence-3.12.2}/mainsequence/virtualfundbuilder/models.py +0 -0
- {mainsequence-3.11.2 → mainsequence-3.12.2}/mainsequence/virtualfundbuilder/notebook_handling.py +0 -0
- {mainsequence-3.11.2 → mainsequence-3.12.2}/mainsequence/virtualfundbuilder/portfolio_interface.py +0 -0
- {mainsequence-3.11.2 → mainsequence-3.12.2}/mainsequence/virtualfundbuilder/resource_factory/rebalance_factory.py +0 -0
- {mainsequence-3.11.2 → mainsequence-3.12.2}/mainsequence/virtualfundbuilder/utils.py +0 -0
- {mainsequence-3.11.2 → mainsequence-3.12.2}/mainsequence.egg-info/dependency_links.txt +0 -0
- {mainsequence-3.11.2 → mainsequence-3.12.2}/mainsequence.egg-info/entry_points.txt +0 -0
- {mainsequence-3.11.2 → mainsequence-3.12.2}/mainsequence.egg-info/requires.txt +0 -0
- {mainsequence-3.11.2 → mainsequence-3.12.2}/mainsequence.egg-info/top_level.txt +0 -0
- {mainsequence-3.11.2 → mainsequence-3.12.2}/setup.cfg +0 -0
- {mainsequence-3.11.2 → mainsequence-3.12.2}/tests/test_agent.py +0 -0
- {mainsequence-3.11.2 → mainsequence-3.12.2}/tests/test_portfolio.py +0 -0
- {mainsequence-3.11.2 → mainsequence-3.12.2}/tests/test_replicator.py +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .base import BaseAgentTool
|
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
"""
|
|
2
|
+
base.py
|
|
3
|
+
|
|
4
|
+
Minimal, production-oriented client-side base for Agent Tools.
|
|
5
|
+
|
|
6
|
+
Requirements from Jose:
|
|
7
|
+
- Single public class: BaseAgentTool
|
|
8
|
+
- No registries / decorators / factories
|
|
9
|
+
- No HtmlApp
|
|
10
|
+
- No Jupyter/notebook helpers
|
|
11
|
+
- Keep _send_agent_tool_to_backend(payload) for AgentTool upsert
|
|
12
|
+
- Provide introspection for:
|
|
13
|
+
- input_schema
|
|
14
|
+
- output_schema (defined envelope with status/response/error)
|
|
15
|
+
- config_schema
|
|
16
|
+
- Provide canonical runtime entrypoint:
|
|
17
|
+
- run_and_response(): calls user run(), wraps into stable response envelope, then send_to_backend()
|
|
18
|
+
|
|
19
|
+
This file intentionally avoids any backend design advice.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
from __future__ import annotations
|
|
23
|
+
|
|
24
|
+
import os
|
|
25
|
+
import traceback
|
|
26
|
+
from abc import ABC, abstractmethod
|
|
27
|
+
from typing import Any
|
|
28
|
+
|
|
29
|
+
from pydantic import BaseModel
|
|
30
|
+
|
|
31
|
+
import mainsequence.client as msc
|
|
32
|
+
from mainsequence import logger
|
|
33
|
+
|
|
34
|
+
_REF_TEMPLATE = "#/$defs/{model}"
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class BaseAgentTool(ABC):
|
|
40
|
+
"""
|
|
41
|
+
Minimal base class for client-side Agent Tools.
|
|
42
|
+
|
|
43
|
+
Tool authors implement:
|
|
44
|
+
def run(self, *args, **kwargs): ...
|
|
45
|
+
|
|
46
|
+
Runners should call:
|
|
47
|
+
tool.run_and_response(*args, **kwargs)
|
|
48
|
+
|
|
49
|
+
The response is ALWAYS a dict:
|
|
50
|
+
{
|
|
51
|
+
"status": "OK" | "ERROR",
|
|
52
|
+
"response": <any JSON-serializable>,
|
|
53
|
+
"error": { ... } | None
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
This class also provides introspection helpers to build:
|
|
57
|
+
- input_schema
|
|
58
|
+
- output_schema (stable response envelope)
|
|
59
|
+
- config_schema
|
|
60
|
+
to match your backend AgentTool model fields.
|
|
61
|
+
"""
|
|
62
|
+
|
|
63
|
+
# ---- metadata overrides ----
|
|
64
|
+
tool_description: str
|
|
65
|
+
|
|
66
|
+
# ---- config model (Pydantic) ----
|
|
67
|
+
configuration_class: type[BaseModel] #necessary on runtime
|
|
68
|
+
|
|
69
|
+
# ---- Optional strict IO models ----
|
|
70
|
+
|
|
71
|
+
output_model: type[BaseModel] | None = None
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def __init__(self, configuration: Any | None = None):
|
|
76
|
+
"""
|
|
77
|
+
Accepts:
|
|
78
|
+
- configuration as instance of configuration_class
|
|
79
|
+
- configuration as dict (will be parsed into configuration_class)
|
|
80
|
+
- configuration=None (will try configuration_class() if available)
|
|
81
|
+
|
|
82
|
+
If you don't use configuration, you can ignore it.
|
|
83
|
+
"""
|
|
84
|
+
cfg_cls =self.__class__.configuration_class
|
|
85
|
+
|
|
86
|
+
if cfg_cls and isinstance(cfg_cls, type) and issubclass(cfg_cls, BaseModel):
|
|
87
|
+
if configuration is None:
|
|
88
|
+
# attempt default construction (will raise if required fields exist)
|
|
89
|
+
self.configuration = cfg_cls()
|
|
90
|
+
elif isinstance(configuration, cfg_cls):
|
|
91
|
+
self.configuration = configuration
|
|
92
|
+
elif isinstance(configuration, dict):
|
|
93
|
+
self.configuration = cfg_cls(**configuration)
|
|
94
|
+
else:
|
|
95
|
+
raise TypeError(
|
|
96
|
+
f"configuration must be {cfg_cls.__name__}, dict, or None; got {type(configuration)}"
|
|
97
|
+
)
|
|
98
|
+
else:
|
|
99
|
+
# No declared configuration class. Allow None or any BaseModel.
|
|
100
|
+
if configuration is not None and not isinstance(configuration, BaseModel):
|
|
101
|
+
raise TypeError(
|
|
102
|
+
"configuration must be a pydantic BaseModel instance or None "
|
|
103
|
+
"(or set configuration_class on the tool)."
|
|
104
|
+
)
|
|
105
|
+
self.configuration = configuration
|
|
106
|
+
|
|
107
|
+
# -----------------------
|
|
108
|
+
# Tool author implements
|
|
109
|
+
# -----------------------
|
|
110
|
+
@abstractmethod
|
|
111
|
+
def run(self, *args, **kwargs) -> Any:
|
|
112
|
+
raise NotImplementedError
|
|
113
|
+
|
|
114
|
+
# -----------------------
|
|
115
|
+
# Canonical runtime entrypoint
|
|
116
|
+
# -----------------------
|
|
117
|
+
def run_and_response(self, *args, **kwargs) -> dict[str, Any]:
|
|
118
|
+
"""
|
|
119
|
+
Execute user run() and ALWAYS return a stable response envelope.
|
|
120
|
+
|
|
121
|
+
This is the method your runner should call.
|
|
122
|
+
"""
|
|
123
|
+
try:
|
|
124
|
+
raw = self.run(*args, **kwargs)
|
|
125
|
+
envelope = self._normalize_user_return(raw)
|
|
126
|
+
except Exception as e:
|
|
127
|
+
envelope = {
|
|
128
|
+
"status": "ERROR",
|
|
129
|
+
"response": None,
|
|
130
|
+
"error": {
|
|
131
|
+
"message": str(e),
|
|
132
|
+
"error_type": e.__class__.__name__,
|
|
133
|
+
"details": None,
|
|
134
|
+
"traceback": traceback.format_exc(),
|
|
135
|
+
},
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
# Best-effort persistence hook (no hard assumptions)
|
|
139
|
+
try:
|
|
140
|
+
self.send_to_backend(envelope)
|
|
141
|
+
except Exception as e:
|
|
142
|
+
logger.warning(
|
|
143
|
+
"[%s] send_to_backend failed: %s", self.__class__.__name__, str(e), exc_info=True
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
return envelope
|
|
147
|
+
|
|
148
|
+
def _normalize_user_return(self, raw: Any) -> dict[str, Any]:
|
|
149
|
+
"""
|
|
150
|
+
Normalize various user return types into the stable response envelope.
|
|
151
|
+
|
|
152
|
+
Supported:
|
|
153
|
+
- dict with 'status' and 'response'
|
|
154
|
+
- pydantic BaseModel -> becomes response (OK)
|
|
155
|
+
- any other value -> becomes response (OK)
|
|
156
|
+
"""
|
|
157
|
+
if isinstance(raw, dict) and "status" in raw and "response" in raw:
|
|
158
|
+
status = raw.get("status", "OK")
|
|
159
|
+
if status not in ("OK", "ERROR"):
|
|
160
|
+
status = "OK"
|
|
161
|
+
response = self._to_jsonable(raw.get("response"))
|
|
162
|
+
err = raw.get("error")
|
|
163
|
+
err = self._to_jsonable(err) if err is not None else None
|
|
164
|
+
return {"status": status, "response": response, "error": err}
|
|
165
|
+
|
|
166
|
+
# If they returned a pydantic model, dump it
|
|
167
|
+
if isinstance(raw, BaseModel):
|
|
168
|
+
return {"status": "OK", "response": raw.model_dump(mode="json"), "error": None}
|
|
169
|
+
|
|
170
|
+
return {"status": "OK", "response": self._to_jsonable(raw), "error": None}
|
|
171
|
+
|
|
172
|
+
@staticmethod
|
|
173
|
+
def _to_jsonable(value: Any) -> Any:
|
|
174
|
+
"""
|
|
175
|
+
Convert common Python/Pydantic objects into JSON-serializable structures.
|
|
176
|
+
"""
|
|
177
|
+
if value is None:
|
|
178
|
+
return None
|
|
179
|
+
if isinstance(value, BaseModel):
|
|
180
|
+
return value.model_dump(mode="json")
|
|
181
|
+
if isinstance(value, dict):
|
|
182
|
+
return {str(k): BaseAgentTool._to_jsonable(v) for k, v in value.items()}
|
|
183
|
+
if isinstance(value, (list, tuple)):
|
|
184
|
+
return [BaseAgentTool._to_jsonable(v) for v in value]
|
|
185
|
+
if isinstance(value, set):
|
|
186
|
+
return [BaseAgentTool._to_jsonable(v) for v in sorted(value, key=lambda x: str(x))]
|
|
187
|
+
# Common "jsonable" objects
|
|
188
|
+
if hasattr(value, "isoformat"):
|
|
189
|
+
try:
|
|
190
|
+
return value.isoformat()
|
|
191
|
+
except Exception as e:
|
|
192
|
+
logger.exception(f"value {value} is not jsonable:")
|
|
193
|
+
raise e
|
|
194
|
+
return value
|
|
195
|
+
|
|
196
|
+
# -----------------------
|
|
197
|
+
# Persistence hook (override if you want)
|
|
198
|
+
# -----------------------
|
|
199
|
+
def send_to_backend(self, envelope: dict[str, Any]) -> None:
|
|
200
|
+
"""
|
|
201
|
+
Best-effort hook to persist the tool response.
|
|
202
|
+
|
|
203
|
+
Default behavior:
|
|
204
|
+
- If SDK helper exists: mainsequence.client.models_tdag.add_tool_response_to_jobrun -> use it
|
|
205
|
+
- Else: store envelope as JSON artifact and attach via add_output()
|
|
206
|
+
|
|
207
|
+
You can override this if you have a preferred mechanism.
|
|
208
|
+
"""
|
|
209
|
+
|
|
210
|
+
# If your SDK provides a direct helper, prefer it.
|
|
211
|
+
|
|
212
|
+
job_run_id=os.getenv("JOB_RUN_ID", None)
|
|
213
|
+
if job_run_id is None:
|
|
214
|
+
raise Exception("JOB_RUN_ID environment variable not set")
|
|
215
|
+
|
|
216
|
+
msc.AgentTool.add_tool_response_to_jobrun(job_run_id=job_run_id, response=envelope) # type: ignore
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
# -----------------------
|
|
220
|
+
# Tool metadata + schema introspection
|
|
221
|
+
# -----------------------
|
|
222
|
+
@classmethod
|
|
223
|
+
def agent_tool_payload(cls, attributes: dict[str, Any] | None = None) -> dict[str, Any]:
|
|
224
|
+
"""
|
|
225
|
+
Build a dict that matches your backend AgentTool model fields:
|
|
226
|
+
slug, name, description, entrypoint,
|
|
227
|
+
input_schema, output_schema, config_schema,
|
|
228
|
+
attributes
|
|
229
|
+
"""
|
|
230
|
+
return {
|
|
231
|
+
"description": cls.get_tool_description(),
|
|
232
|
+
"output_schema": cls.get_output_schema(),
|
|
233
|
+
"config_schema": cls.get_config_schema(),
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
@classmethod
|
|
237
|
+
def register_to_backend(cls, attributes: dict[str, Any] | None = None) -> None:
|
|
238
|
+
"""
|
|
239
|
+
Explicit registration/upsert of this tool metadata.
|
|
240
|
+
(No registries, no automatic side effects.)
|
|
241
|
+
"""
|
|
242
|
+
payload=cls.agent_tool_payload(attributes=attributes)
|
|
243
|
+
msc.AgentTool.update_or_create(**payload) # type: ignore
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
@classmethod
|
|
248
|
+
def get_tool_name(cls) -> str:
|
|
249
|
+
if isinstance(getattr(cls, "tool_name", None), str) and cls.tool_name:
|
|
250
|
+
return cls.tool_name
|
|
251
|
+
return cls.__name__
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
@classmethod
|
|
258
|
+
def get_config_schema(cls) -> dict[str, Any] | None:
|
|
259
|
+
cfg_cls = getattr(cls, "configuration_class", None)
|
|
260
|
+
if cfg_cls and isinstance(cfg_cls, type) and issubclass(cfg_cls, BaseModel):
|
|
261
|
+
return cfg_cls.model_json_schema(ref_template=_REF_TEMPLATE)
|
|
262
|
+
return None
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
@classmethod
|
|
266
|
+
def get_output_schema(cls) -> dict[str, Any]:
|
|
267
|
+
"""
|
|
268
|
+
Output schema is always a stable response envelope:
|
|
269
|
+
{"status": "OK"|"ERROR", "response": ..., "error": ...}
|
|
270
|
+
|
|
271
|
+
If output_model is set, response is typed accordingly.
|
|
272
|
+
"""
|
|
273
|
+
defs: dict[str, Any] = {}
|
|
274
|
+
|
|
275
|
+
# Build "response" schema
|
|
276
|
+
out_model = getattr(cls, "output_model", None)
|
|
277
|
+
if out_model and isinstance(out_model, type) and issubclass(out_model, BaseModel):
|
|
278
|
+
response_schema = out_model.model_json_schema(ref_template=_REF_TEMPLATE)
|
|
279
|
+
if "$defs" in response_schema:
|
|
280
|
+
defs.update(response_schema.pop("$defs"))
|
|
281
|
+
else:
|
|
282
|
+
response_schema = {}
|
|
283
|
+
|
|
284
|
+
# error schema (always the same shape)
|
|
285
|
+
error_schema: dict[str, Any] = {
|
|
286
|
+
"title": "ToolError",
|
|
287
|
+
"type": "object",
|
|
288
|
+
"properties": {
|
|
289
|
+
"message": {"type": "string"},
|
|
290
|
+
"error_type": {"type": ["string", "null"]},
|
|
291
|
+
"details": {"type": ["object", "null"]},
|
|
292
|
+
"traceback": {"type": ["string", "null"]},
|
|
293
|
+
},
|
|
294
|
+
"required": ["message"],
|
|
295
|
+
"additionalProperties": True,
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
envelope: dict[str, Any] = {
|
|
299
|
+
"title": f"{cls.__name__}Output",
|
|
300
|
+
"type": "object",
|
|
301
|
+
"properties": {
|
|
302
|
+
"status": {"type": "string", "enum": ["OK", "ERROR"]},
|
|
303
|
+
"response": {"anyOf": [response_schema, {"type": "null"}]},
|
|
304
|
+
"error": {"anyOf": [error_schema, {"type": "null"}]},
|
|
305
|
+
},
|
|
306
|
+
"required": ["status", "response", "error"],
|
|
307
|
+
"additionalProperties": False,
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
if defs:
|
|
311
|
+
envelope["$defs"] = defs
|
|
312
|
+
|
|
313
|
+
return envelope
|
|
314
|
+
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# cli.py
|
|
2
|
+
import importlib
|
|
3
|
+
import json
|
|
4
|
+
import os
|
|
5
|
+
import sys
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def load_class(entry_point: str):
|
|
9
|
+
entry_point = entry_point.strip()
|
|
10
|
+
if ":" in entry_point: # preferred standard
|
|
11
|
+
module_path, class_name = entry_point.split(":", 1)
|
|
12
|
+
else: # also accept your requested dotted form
|
|
13
|
+
module_path, class_name = entry_point.rsplit(".", 1)
|
|
14
|
+
|
|
15
|
+
module = importlib.import_module(module_path)
|
|
16
|
+
return getattr(module, class_name)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def read_configuration() -> dict:
|
|
20
|
+
# Priority: argv[1] -> $CONFIGURATION -> stdin (if piped)
|
|
21
|
+
if len(sys.argv) >= 2 and sys.argv[1].strip():
|
|
22
|
+
raw = sys.argv[1]
|
|
23
|
+
else:
|
|
24
|
+
raw = os.getenv("TOOL_CONFIGURATION")
|
|
25
|
+
if (raw is None or not raw.strip()) and not sys.stdin.isatty():
|
|
26
|
+
raw = sys.stdin.read()
|
|
27
|
+
|
|
28
|
+
if raw is None or not raw.strip():
|
|
29
|
+
return {}
|
|
30
|
+
|
|
31
|
+
cfg = json.loads(raw)
|
|
32
|
+
if not isinstance(cfg, dict):
|
|
33
|
+
raise SystemExit("CONFIGURATION must be a JSON object (dict).")
|
|
34
|
+
return cfg
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def main() -> None:
|
|
38
|
+
entry_point = os.getenv("TOOL_ENTRY_POINT")
|
|
39
|
+
if not entry_point:
|
|
40
|
+
raise SystemExit("Missing TOOL_ENTRY_POINT env var.")
|
|
41
|
+
|
|
42
|
+
ToolClass = load_class(entry_point)
|
|
43
|
+
cfg = read_configuration()
|
|
44
|
+
|
|
45
|
+
# What you asked for:
|
|
46
|
+
|
|
47
|
+
tool = ToolClass(**cfg)
|
|
48
|
+
tool.run_and_response()
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
if __name__ == "__main__":
|
|
54
|
+
main()
|
|
@@ -2689,6 +2689,25 @@ class Constant(BasePydanticModel, BaseObjectOrm):
|
|
|
2689
2689
|
return created_constants
|
|
2690
2690
|
|
|
2691
2691
|
|
|
2692
|
+
|
|
2693
|
+
# AI Models
|
|
2694
|
+
|
|
2695
|
+
class AgentTool(BasePydanticModel, BaseObjectOrm):
|
|
2696
|
+
"""
|
|
2697
|
+
Represents the actual tool metadata that an agent needs to know.
|
|
2698
|
+
"""
|
|
2699
|
+
slug: str
|
|
2700
|
+
name: str = Field(max_length=255)
|
|
2701
|
+
description:str= None
|
|
2702
|
+
|
|
2703
|
+
entrypoint: str
|
|
2704
|
+
# JSONFields
|
|
2705
|
+
output_schema: dict[str, Any] | None = None
|
|
2706
|
+
config_schema: dict[str, Any] | None = None
|
|
2707
|
+
|
|
2708
|
+
|
|
2709
|
+
|
|
2710
|
+
|
|
2692
2711
|
SessionDataSource = PodDataSource()
|
|
2693
2712
|
SessionDataSource.set_remote_db()
|
|
2694
2713
|
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
# dashboards/streamlit/components/asset_select.py
|
|
2
|
+
"""
|
|
3
|
+
import mainsequence.dashboards.streamlit.components.asset_select
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
"""
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from typing import Any
|
|
10
|
+
|
|
11
|
+
import streamlit as st
|
|
12
|
+
|
|
13
|
+
import mainsequence.client as msc
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@st.cache_data(show_spinner=False)
|
|
17
|
+
def _search_assets(q: str) -> list[dict[str, Any]]:
|
|
18
|
+
"""
|
|
19
|
+
Search MainSequence assets by current_snapshot.name or unique_identifier.
|
|
20
|
+
Returns [{'id', 'label', 'instance'}] with unique IDs.
|
|
21
|
+
"""
|
|
22
|
+
if not q or len(q.strip()) < 3:
|
|
23
|
+
return []
|
|
24
|
+
out: list[dict[str, Any]] = []
|
|
25
|
+
seen = set()
|
|
26
|
+
|
|
27
|
+
try:
|
|
28
|
+
by_name = msc.Asset.filter(current_snapshot__name__contains=q.strip())
|
|
29
|
+
except Exception:
|
|
30
|
+
by_name = []
|
|
31
|
+
|
|
32
|
+
try:
|
|
33
|
+
by_uid = msc.Asset.filter(unique_identifier__contains=q.strip())
|
|
34
|
+
except Exception:
|
|
35
|
+
by_uid = []
|
|
36
|
+
|
|
37
|
+
for a in list(by_name or []) + list(by_uid or []):
|
|
38
|
+
try:
|
|
39
|
+
aid = getattr(a, "id", None)
|
|
40
|
+
if aid is None or aid in seen:
|
|
41
|
+
continue
|
|
42
|
+
uid = getattr(a, "unique_identifier", None)
|
|
43
|
+
snap = getattr(getattr(a, "current_snapshot", None), "name", None)
|
|
44
|
+
label = f"{uid or '—'} · {snap or '—'} (id={aid})"
|
|
45
|
+
out.append({"id": aid, "label": label, "instance": a})
|
|
46
|
+
seen.add(aid)
|
|
47
|
+
except Exception:
|
|
48
|
+
continue
|
|
49
|
+
return out
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def sidebar_asset_single_select(
|
|
53
|
+
*,
|
|
54
|
+
title: str = "Find asset",
|
|
55
|
+
key_prefix: str = "asset_select",
|
|
56
|
+
min_chars: int = 3,
|
|
57
|
+
) -> Any | None:
|
|
58
|
+
"""
|
|
59
|
+
Streamlit sidebar widget:
|
|
60
|
+
- Text search (cached)
|
|
61
|
+
- Single-select of results
|
|
62
|
+
- Returns the loaded Asset instance and keeps it in session until cleared.
|
|
63
|
+
- Only changes the loaded asset when "Load asset" is pressed.
|
|
64
|
+
Session keys:
|
|
65
|
+
• {key_prefix}_loaded_id
|
|
66
|
+
• {key_prefix}_loaded_instance
|
|
67
|
+
• {key_prefix}_q, {key_prefix}_results, {key_prefix}_sel (UI state)
|
|
68
|
+
"""
|
|
69
|
+
loaded_id_key = f"{key_prefix}_loaded_id"
|
|
70
|
+
loaded_inst_key = f"{key_prefix}_loaded_instance"
|
|
71
|
+
results_key = f"{key_prefix}_results"
|
|
72
|
+
select_key = f"{key_prefix}_sel"
|
|
73
|
+
query_key = f"{key_prefix}_q"
|
|
74
|
+
|
|
75
|
+
with st.sidebar:
|
|
76
|
+
st.markdown(f"### {title}")
|
|
77
|
+
st.caption(f"Type at least **{min_chars}** characters to search.")
|
|
78
|
+
|
|
79
|
+
# Show the currently loaded asset (if any)
|
|
80
|
+
loaded_id = st.session_state.get(loaded_id_key)
|
|
81
|
+
loaded_inst = st.session_state.get(loaded_inst_key)
|
|
82
|
+
if loaded_inst is not None:
|
|
83
|
+
try:
|
|
84
|
+
uid = getattr(loaded_inst, "unique_identifier", None)
|
|
85
|
+
snap = getattr(getattr(loaded_inst, "current_snapshot", None), "name", None)
|
|
86
|
+
st.caption(f"Loaded asset: **{uid or '—'} · {snap or '—'} (id={loaded_id})**")
|
|
87
|
+
except Exception:
|
|
88
|
+
st.caption(f"Loaded asset: id={loaded_id}")
|
|
89
|
+
|
|
90
|
+
# Search box
|
|
91
|
+
def _do_search_from_state(prefix: str, min_chars_local: int) -> None:
|
|
92
|
+
q_local = st.session_state.get(f"{prefix}_q", "")
|
|
93
|
+
if q_local and len(q_local.strip()) >= min_chars_local:
|
|
94
|
+
st.session_state[f"{prefix}_results"] = _search_assets(q_local.strip())
|
|
95
|
+
else:
|
|
96
|
+
st.session_state.pop(f"{prefix}_results", None)
|
|
97
|
+
|
|
98
|
+
q = st.text_input(
|
|
99
|
+
"Search assets",
|
|
100
|
+
placeholder="e.g. BONOS 2030 or MXN:... UID",
|
|
101
|
+
key=query_key,
|
|
102
|
+
on_change=_do_search_from_state,
|
|
103
|
+
args=(key_prefix, min_chars),
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
if st.button("Search", key=f"{key_prefix}_btn") and q and len(q.strip()) >= min_chars:
|
|
107
|
+
st.session_state[results_key] = _search_assets(q.strip())
|
|
108
|
+
|
|
109
|
+
assets = st.session_state.get(results_key, [])
|
|
110
|
+
if q and len(q.strip()) < min_chars:
|
|
111
|
+
st.caption(f"Keep typing… need **{min_chars}+** characters to search.")
|
|
112
|
+
|
|
113
|
+
if assets:
|
|
114
|
+
id_to_label = {a["id"]: a["label"] for a in assets}
|
|
115
|
+
id_to_inst = {a["id"]: a["instance"] for a in assets}
|
|
116
|
+
# Preselect currently loaded id if visible in the options
|
|
117
|
+
default_index = 0
|
|
118
|
+
options = list(id_to_label.keys())
|
|
119
|
+
if loaded_id in options:
|
|
120
|
+
default_index = options.index(loaded_id)
|
|
121
|
+
|
|
122
|
+
selected_id = st.selectbox(
|
|
123
|
+
"Select an asset",
|
|
124
|
+
options=options,
|
|
125
|
+
index=default_index if options else 0,
|
|
126
|
+
format_func=lambda x: id_to_label.get(x, str(x)),
|
|
127
|
+
key=select_key,
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
col_load, col_clear = st.columns([3, 2])
|
|
131
|
+
with col_load:
|
|
132
|
+
if st.button(
|
|
133
|
+
"Load asset", type="primary", width='stretch', key=f"{key_prefix}_load"
|
|
134
|
+
):
|
|
135
|
+
inst = id_to_inst.get(selected_id)
|
|
136
|
+
# Persist loaded selection
|
|
137
|
+
st.session_state[loaded_id_key] = (
|
|
138
|
+
int(selected_id) if selected_id is not None else None
|
|
139
|
+
)
|
|
140
|
+
st.session_state[loaded_inst_key] = inst
|
|
141
|
+
return inst
|
|
142
|
+
|
|
143
|
+
with col_clear:
|
|
144
|
+
if st.button("Clear", width='stretch', key=f"{key_prefix}_clear"):
|
|
145
|
+
# Clear UI + loaded asset
|
|
146
|
+
st.session_state.pop(query_key, None)
|
|
147
|
+
st.session_state.pop(results_key, None)
|
|
148
|
+
st.session_state.pop(select_key, None)
|
|
149
|
+
st.session_state.pop(loaded_id_key, None)
|
|
150
|
+
st.session_state.pop(loaded_inst_key, None)
|
|
151
|
+
st.rerun()
|
|
152
|
+
|
|
153
|
+
# Persisted behavior: if an asset is already loaded, return it on every rerun
|
|
154
|
+
loaded_inst = st.session_state.get(loaded_inst_key)
|
|
155
|
+
if loaded_inst is not None:
|
|
156
|
+
return loaded_inst
|
|
157
|
+
|
|
158
|
+
return None
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# dashboards/streamlit/components/date_settings.py
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import datetime as dt
|
|
5
|
+
import os
|
|
6
|
+
|
|
7
|
+
import streamlit as st
|
|
8
|
+
from dashboards.components.date_selector import date_selector
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def sidebar_date_settings(
|
|
12
|
+
*,
|
|
13
|
+
date_label: str = "Valuation date",
|
|
14
|
+
session_cfg_key: str = "position_cfg_mem",
|
|
15
|
+
cfg_field: str = "valuation_date",
|
|
16
|
+
date_key: str = "valuation_date_input",
|
|
17
|
+
date_help: str | None = None,
|
|
18
|
+
use_last_curve_label: str = "Use last available curve",
|
|
19
|
+
use_last_curve_key: str = "use_last_obs_curve",
|
|
20
|
+
use_last_curve_help: str | None = None,
|
|
21
|
+
env_var_key: str = "USE_LAST_OBSERVATION_MS_INSTRUMENT",
|
|
22
|
+
) -> tuple[dt.date, bool]:
|
|
23
|
+
"""
|
|
24
|
+
Sidebar date settings: valuation date selector + "use last available curve" toggle.
|
|
25
|
+
Persists date in session cfg and toggles env var for downstream curve construction.
|
|
26
|
+
Returns (valuation_date, use_last_curve).
|
|
27
|
+
"""
|
|
28
|
+
valuation_date = date_selector(
|
|
29
|
+
label=date_label,
|
|
30
|
+
session_cfg_key=session_cfg_key,
|
|
31
|
+
cfg_field=cfg_field,
|
|
32
|
+
key=date_key,
|
|
33
|
+
help=date_help,
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
default_use_last = st.session_state.get(
|
|
37
|
+
use_last_curve_key,
|
|
38
|
+
os.environ.get(env_var_key, "1").lower() in ("1", "true", "yes", "y", "on"),
|
|
39
|
+
)
|
|
40
|
+
use_last_curve = st.checkbox(
|
|
41
|
+
use_last_curve_label,
|
|
42
|
+
value=default_use_last,
|
|
43
|
+
key=use_last_curve_key,
|
|
44
|
+
help=use_last_curve_help,
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
# Set the process env var so downstream curve-building respects it in this run.
|
|
48
|
+
os.environ[env_var_key] = "True" if use_last_curve else "False"
|
|
49
|
+
|
|
50
|
+
return valuation_date, use_last_curve
|
|
File without changes
|
|
@@ -239,7 +239,7 @@ def send_resource_to_backend(resource_class, attributes: dict | None = None):
|
|
|
239
239
|
if merged_definitions:
|
|
240
240
|
final_json_schema["$defs"] = merged_definitions
|
|
241
241
|
|
|
242
|
-
resource_config = DynamicResource.
|
|
242
|
+
resource_config = DynamicResource.update_or_create(
|
|
243
243
|
name=resource_class.__name__,
|
|
244
244
|
type=resource_class.TYPE.value,
|
|
245
245
|
object_signature=final_json_schema,
|
|
@@ -166,8 +166,7 @@ def _get_class_source_code(cls):
|
|
|
166
166
|
return None
|
|
167
167
|
|
|
168
168
|
|
|
169
|
-
SIGNAL_CLASS_REGISTRY =
|
|
170
|
-
|
|
169
|
+
SIGNAL_CLASS_REGISTRY = globals().get("SIGNAL_CLASS_REGISTRY", {})
|
|
171
170
|
|
|
172
171
|
def register_signal_class(name=None, register_in_agent=True):
|
|
173
172
|
"""
|