ApiLogicServer 15.0.37__py3-none-any.whl → 15.0.38__py3-none-any.whl
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.
- api_logic_server_cli/api_logic_server.py +2 -2
- api_logic_server_cli/api_logic_server_info.yaml +3 -3
- api_logic_server_cli/genai/genai_svcs.py +4 -3
- api_logic_server_cli/prototypes/base/docs/training/react_map.prompt.md +13 -0
- api_logic_server_cli/prototypes/base/docs/training/react_tree.prompt.md +10 -0
- api_logic_server_cli/prototypes/manager/system/Manager_workspace.code-workspace +3 -3
- api_logic_server_cli/prototypes/nw/api/api_discovery/authentication_expose_api_models.py +53 -0
- api_logic_server_cli/prototypes/nw/api/api_discovery/auto_discovery.py +27 -0
- api_logic_server_cli/prototypes/nw/api/api_discovery/count_orders_by_month.html +76 -0
- api_logic_server_cli/prototypes/nw/api/api_discovery/count_orders_by_month.sql +1 -0
- api_logic_server_cli/prototypes/nw/api/api_discovery/dashboard_services.py +143 -0
- api_logic_server_cli/prototypes/nw/api/api_discovery/mcp_discovery.py +97 -0
- api_logic_server_cli/prototypes/nw/api/api_discovery/new_service.py +21 -0
- api_logic_server_cli/prototypes/nw/api/api_discovery/newer_service.py +21 -0
- api_logic_server_cli/prototypes/nw/api/api_discovery/number_of_sales_per_category.html +76 -0
- api_logic_server_cli/prototypes/nw/api/api_discovery/number_of_sales_per_category.sql +1 -0
- api_logic_server_cli/prototypes/nw/api/api_discovery/ontimize_api.py +495 -0
- api_logic_server_cli/prototypes/nw/api/api_discovery/sales_by_category.html +76 -0
- api_logic_server_cli/prototypes/nw/api/api_discovery/sales_by_category.sql +1 -0
- api_logic_server_cli/prototypes/nw/api/api_discovery/system.py +77 -0
- api_logic_server_cli/prototypes/nw/database/database_discovery/graphics_services.py +173 -0
- api_logic_server_cli/prototypes/nw/ui/admin/home.js +5 -0
- api_logic_server_cli/prototypes/nw/ui/reference_react_app/DEPARTMENT_TREE_VIEW.md +66 -0
- api_logic_server_cli/prototypes/nw/ui/reference_react_app/package.json +4 -0
- api_logic_server_cli/prototypes/nw/ui/reference_react_app/public/index.html +3 -0
- api_logic_server_cli/prototypes/nw/ui/reference_react_app/src/App.js +8 -1
- api_logic_server_cli/prototypes/nw/ui/reference_react_app/src/CustomLayout.js +20 -0
- api_logic_server_cli/prototypes/nw/ui/reference_react_app/src/Department.js +511 -24
- api_logic_server_cli/prototypes/nw/ui/reference_react_app/src/DepartmentTree.js +147 -0
- api_logic_server_cli/prototypes/nw/ui/reference_react_app/src/Employee.js +230 -18
- api_logic_server_cli/prototypes/nw/ui/reference_react_app/src/LandingPage.js +264 -0
- api_logic_server_cli/prototypes/nw/ui/reference_react_app/src/Supplier.js +359 -121
- api_logic_server_cli/prototypes/nw/ui/reference_react_app/src/index.js +1 -0
- {apilogicserver-15.0.37.dist-info → apilogicserver-15.0.38.dist-info}/METADATA +1 -1
- {apilogicserver-15.0.37.dist-info → apilogicserver-15.0.38.dist-info}/RECORD +39 -19
- api_logic_server_cli/prototypes/base/docs/training/admin_app_unused.md +0 -156
- {apilogicserver-15.0.37.dist-info → apilogicserver-15.0.38.dist-info}/WHEEL +0 -0
- {apilogicserver-15.0.37.dist-info → apilogicserver-15.0.38.dist-info}/entry_points.txt +0 -0
- {apilogicserver-15.0.37.dist-info → apilogicserver-15.0.38.dist-info}/licenses/LICENSE +0 -0
- {apilogicserver-15.0.37.dist-info → apilogicserver-15.0.38.dist-info}/top_level.txt +0 -0
|
@@ -12,10 +12,10 @@ ApiLogicServer CLI: given a database url, create [and run] customizable ApiLogic
|
|
|
12
12
|
Called from api_logic_server_cli.py, by instantiating the ProjectRun object.
|
|
13
13
|
'''
|
|
14
14
|
|
|
15
|
-
__version__ = "15.00.
|
|
15
|
+
__version__ = "15.00.38" # last public release: 15.00.36 (15.00.12)
|
|
16
16
|
recent_changes = \
|
|
17
17
|
f'\n\nRecent Changes:\n' +\
|
|
18
|
-
"\t07/
|
|
18
|
+
"\t07/05/2024 - 15.00.38: nw vibe, minor bug in mgr symlink creation, nw cards, mgr readme diagnostics, mcp printer, bug[98] \n"\
|
|
19
19
|
"\t06/30/2024 - 15.00.33: Tech Preview: genai-logic genai-add-app --vibe, bug [96, 97] \n"\
|
|
20
20
|
"\t06/10/2024 - 15.00.12: MCP Security, win fixes for readme, graphics quotes \n"\
|
|
21
21
|
"\t06/08/2024 - 15.00.10: MCP, optional shortening of stacktrace lines, bugfix[92] \n"\
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
last_created_date: July
|
|
2
|
-
last_created_project_name:
|
|
3
|
-
last_created_version: 15.00.
|
|
1
|
+
last_created_date: July 05, 2025 07:17:31
|
|
2
|
+
last_created_project_name: ../../../servers/NW_NoCust
|
|
3
|
+
last_created_version: 15.00.37
|
|
@@ -906,10 +906,11 @@ def call_chatgpt(messages: List[Dict[str, str]], api_version: str, using: str, r
|
|
|
906
906
|
request_path.parent.mkdir(parents=True, exist_ok=True)
|
|
907
907
|
return request_path
|
|
908
908
|
|
|
909
|
-
def string_to_lines(dict_long_string:
|
|
909
|
+
def string_to_lines(dict_long_string: list) -> dict:
|
|
910
910
|
result = dict_long_string
|
|
911
|
-
if
|
|
912
|
-
result[1]['content']
|
|
911
|
+
if len(result) > 1:
|
|
912
|
+
if isinstance(result[1]['content'], str):
|
|
913
|
+
result[1]['content'] = result[1]['content'].split('\n')
|
|
913
914
|
return result
|
|
914
915
|
|
|
915
916
|
try:
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
I need to enhance a React Admin supplier page to include a professional, interactive world map view that displays suppliers on a real map with proper geography. Requirements:
|
|
2
|
+
|
|
3
|
+
1. **Use a proper mapping library** (like Leaflet.js) - NOT custom SVG drawings
|
|
4
|
+
2. **Real world map** with accurate geography, countries, and coastlines
|
|
5
|
+
3. **Interactive controls**: pan, zoom, mouse wheel support
|
|
6
|
+
4. **Clickable supplier markers** that navigate to supplier detail pages
|
|
7
|
+
5. **Professional UI** with map legend and controls
|
|
8
|
+
6. **Toggle between list and map view**
|
|
9
|
+
7. **Position suppliers** by country with slight offsets to avoid overlap
|
|
10
|
+
|
|
11
|
+
The current supplier data has fields: Id, CompanyName, ContactName, City, Country, etc.
|
|
12
|
+
|
|
13
|
+
Please install the necessary mapping library dependencies and create a production-ready map component that looks professional like Google Maps, not a hand-drawn cartoon map.
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
Provide an option to see departments either as a list (as now), or as a tree. The tree expands to show related sub departments, as links. If I click on the dept name link, then open the department "show" to the right.
|
|
2
|
+
|
|
3
|
+
TECHNICAL CONSTRAINTS:
|
|
4
|
+
|
|
5
|
+
- Avoid recursion stack overflow: include proper termination conditions
|
|
6
|
+
- Handle mixed data types: use parseInt() for ID comparisons
|
|
7
|
+
- Prevent import conflicts: use aliases for Material-UI components
|
|
8
|
+
- Verify data relationships: check actual field names in backend
|
|
9
|
+
- Use incremental development: start simple, add complexity gradually
|
|
10
|
+
- Test each layer: tree logic → expansion → detail panel → tabs
|
|
@@ -5,11 +5,11 @@
|
|
|
5
5
|
}
|
|
6
6
|
],
|
|
7
7
|
"settings": {
|
|
8
|
-
"debug.console.
|
|
8
|
+
"debug.console.wornw graphsdWrap": false,
|
|
9
9
|
"workbench.editorAssociations": {
|
|
10
10
|
"*.md": "vscode.markdown.preview.editor"
|
|
11
11
|
},
|
|
12
|
-
"workbench.colorTheme": "Office Theme (
|
|
13
|
-
"workbench.preferredLightColorTheme": "Office Theme (
|
|
12
|
+
"workbench.colorTheme": "Office Theme (Excel)",
|
|
13
|
+
"workbench.preferredLightColorTheme": "Office Theme (Excel)"
|
|
14
14
|
}
|
|
15
15
|
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import inspect
|
|
2
|
+
import safrs
|
|
3
|
+
import importlib
|
|
4
|
+
import pathlib
|
|
5
|
+
import logging as logging
|
|
6
|
+
import flask_sqlalchemy
|
|
7
|
+
|
|
8
|
+
# use absolute path import for easier multi-{app,model,db} support
|
|
9
|
+
force_import = __import__("database.database_discovery.authentication_models")
|
|
10
|
+
database = __import__('database')
|
|
11
|
+
app_logger = logging.getLogger(__name__)
|
|
12
|
+
app_logger.debug("api/expose_api_models.py - endpoint for each table")
|
|
13
|
+
|
|
14
|
+
def add_check_sum(cls):
|
|
15
|
+
"""
|
|
16
|
+
Checksum decorator for each model
|
|
17
|
+
"""
|
|
18
|
+
@safrs.jsonapi_attr
|
|
19
|
+
def _check_sum_(self):
|
|
20
|
+
if isinstance(self, flask_sqlalchemy.model.DefaultMeta) or not hasattr(self, "_check_sum_property"): # property does not exist during initialization
|
|
21
|
+
return None
|
|
22
|
+
return self._check_sum_property
|
|
23
|
+
|
|
24
|
+
@_check_sum_.setter
|
|
25
|
+
def _check_sum_(self, value):
|
|
26
|
+
self._check_sum_property = value
|
|
27
|
+
|
|
28
|
+
cls.S_CheckSum = _check_sum_
|
|
29
|
+
return cls
|
|
30
|
+
|
|
31
|
+
def add_service(app, api, project_dir, swagger_host: str, PORT: str, method_decorators ):
|
|
32
|
+
""" Called by api_discovery to
|
|
33
|
+
* expose API for each model (using introspection)
|
|
34
|
+
|
|
35
|
+
Args:
|
|
36
|
+
app (_type_): _description_
|
|
37
|
+
api (_type_): _description_
|
|
38
|
+
project_dir (_type_): _description_
|
|
39
|
+
swagger_host (str): _description_
|
|
40
|
+
PORT (str): _description_
|
|
41
|
+
method_decorators (_type_): _description_
|
|
42
|
+
|
|
43
|
+
Returns:
|
|
44
|
+
_type_: _description_
|
|
45
|
+
"""
|
|
46
|
+
|
|
47
|
+
# Get all the subclasses of the Base class and expose them in the api
|
|
48
|
+
for name, obj in inspect.getmembers(database.database_discovery.authentication_models):
|
|
49
|
+
if inspect.isclass(obj) and issubclass(obj, database.models.SAFRSBaseX) and obj is not database.models.SAFRSBaseX:
|
|
50
|
+
app_logger.info(f"Exposing /{name}")
|
|
51
|
+
api.expose_object(add_check_sum(obj), method_decorators= method_decorators)
|
|
52
|
+
|
|
53
|
+
return api
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import importlib
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
import logging
|
|
4
|
+
|
|
5
|
+
app_logger = logging.getLogger(__name__)
|
|
6
|
+
|
|
7
|
+
def discover_services(app, api, project_dir, swagger_host: str, PORT: str):
|
|
8
|
+
"""
|
|
9
|
+
Discover services in the services directory
|
|
10
|
+
"""
|
|
11
|
+
import os
|
|
12
|
+
method_decorators : list = []
|
|
13
|
+
services = []
|
|
14
|
+
services_path = Path(__file__).parent
|
|
15
|
+
for root, dirs, files in os.walk(services_path):
|
|
16
|
+
for file in files:
|
|
17
|
+
if file.endswith(".py"):
|
|
18
|
+
spec = importlib.util.spec_from_file_location("module.name", services_path.joinpath(file))
|
|
19
|
+
if file.endswith("auto_discovery.py"):
|
|
20
|
+
pass
|
|
21
|
+
else:
|
|
22
|
+
services.append(file)
|
|
23
|
+
each_service = importlib.util.module_from_spec(spec)
|
|
24
|
+
spec.loader.exec_module(each_service) # runs "bare" module code (e.g., initialization)
|
|
25
|
+
each_service.add_service(app, api, project_dir, swagger_host, PORT, method_decorators) # invoke create function
|
|
26
|
+
app.logger.info(f"..discovered services: {services}")
|
|
27
|
+
return
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>Bar Chart Example</title>
|
|
7
|
+
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
|
8
|
+
</head>
|
|
9
|
+
<body>
|
|
10
|
+
<canvas id="myChart" width="400" height="200"></canvas>
|
|
11
|
+
<script>
|
|
12
|
+
// Input data
|
|
13
|
+
fetch('http://localhost:5656/api/GraphicsServices/count_orders_by_month', {
|
|
14
|
+
method: 'POST',
|
|
15
|
+
headers: {
|
|
16
|
+
'accept': 'application/vnd.api+json',
|
|
17
|
+
'Content-Type': 'application/json'
|
|
18
|
+
},
|
|
19
|
+
body: JSON.stringify({}) // Add any required payload here
|
|
20
|
+
})
|
|
21
|
+
.then(response => {
|
|
22
|
+
if (!response.ok) {
|
|
23
|
+
throw new Error(`HTTP error! status: ${response.status}`);
|
|
24
|
+
}
|
|
25
|
+
return response.json();
|
|
26
|
+
})
|
|
27
|
+
.then(result => {
|
|
28
|
+
const labels = [];
|
|
29
|
+
const data = [];
|
|
30
|
+
const type = result.meta.result.chart_type;
|
|
31
|
+
const title = result.meta.result.title;
|
|
32
|
+
const columns = result.meta.result.columns;
|
|
33
|
+
console.log(JSON.stringify(result));
|
|
34
|
+
result.meta.result.results.forEach(item => {
|
|
35
|
+
labels.push(item[columns[0]]);
|
|
36
|
+
data.push(parseFloat(item[columns[1]]));
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
// Create the bar chart
|
|
40
|
+
const ctx = document.getElementById('myChart').getContext('2d');
|
|
41
|
+
const myChart = new Chart(ctx, {
|
|
42
|
+
type: type,
|
|
43
|
+
data: {
|
|
44
|
+
labels: labels,
|
|
45
|
+
datasets: [{
|
|
46
|
+
label: title,
|
|
47
|
+
data: data,
|
|
48
|
+
backgroundColor: 'rgba(75, 192, 192, 0.2)',
|
|
49
|
+
borderColor: 'rgba(75, 192, 192, 1)',
|
|
50
|
+
borderWidth: 1
|
|
51
|
+
}]
|
|
52
|
+
},
|
|
53
|
+
options: {
|
|
54
|
+
responsive: true,
|
|
55
|
+
plugins: {
|
|
56
|
+
legend: {
|
|
57
|
+
position: 'top',
|
|
58
|
+
},
|
|
59
|
+
title: {
|
|
60
|
+
display: true,
|
|
61
|
+
text: title
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
scales: {
|
|
65
|
+
y: {
|
|
66
|
+
beginAtZero: true
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
})
|
|
72
|
+
.catch(error => console.error('Error fetching data:', error));
|
|
73
|
+
|
|
74
|
+
</script>
|
|
75
|
+
</body>
|
|
76
|
+
</html>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
SELECT strftime('%Y-%m', date(`Order`.OrderDate)) AS OrderMonth, COUNT(`Order`.Id) AS OrderCount FROM `Order` WHERE `Order`.OrderDate IS NOT NULL GROUP BY OrderMonth ORDER BY OrderMonth;
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
from database import models
|
|
2
|
+
import logging
|
|
3
|
+
from safrs import jsonapi_attr
|
|
4
|
+
from sqlalchemy.orm import relationship, remote, foreign
|
|
5
|
+
from functools import wraps # This convenience func preserves name and docstring
|
|
6
|
+
import decimal as decimal
|
|
7
|
+
from sqlalchemy import extract, func
|
|
8
|
+
from flask import request, jsonify
|
|
9
|
+
from safrs import jsonapi_rpc, SAFRSAPI
|
|
10
|
+
from database import models
|
|
11
|
+
import safrs
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
|
|
14
|
+
"""
|
|
15
|
+
Called by iFrame in home.js when admin app is loaded
|
|
16
|
+
|
|
17
|
+
Returns iFrame for ui/admin/home.js, with entries like this to populate the dashboard:
|
|
18
|
+
<iframe src="http://localhost:5656/chart_graphics/sales_per_category" style="flex: 1; border: none; width: 90%; height: 200px;">
|
|
19
|
+
|
|
20
|
+
curl -X GET "http://localhost:5656/dashboard"
|
|
21
|
+
|
|
22
|
+
You typically do not alter this file - it is rebuilt on `als genai-graphics`
|
|
23
|
+
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
app_logger = logging.getLogger(__name__)
|
|
27
|
+
dashboard_result = {}
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def add_service(app, api, project_dir, swagger_host: str, PORT: str, method_decorators = []):
|
|
31
|
+
pass
|
|
32
|
+
|
|
33
|
+
@app.route('/dashboard', methods=['GET','OPTIONS'])
|
|
34
|
+
def dashboard():
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
if request.method == 'OPTIONS':
|
|
38
|
+
return jsonify({"result": "ok"})
|
|
39
|
+
|
|
40
|
+
server = request.host_url
|
|
41
|
+
|
|
42
|
+
iframe_template = '<div class="dashboard-iframe"><iframe src="{url}" style="flex: 1; border: none; width: 90%; height: 200px;"></iframe></div>'
|
|
43
|
+
|
|
44
|
+
iframe_1 = iframe_template.format(url=f"{server}chart_graphics/sales_by_category")
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
iframe_2 = iframe_template.format(url=f"{server}chart_graphics/number_of_sales_per_category")
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
iframe_3 = iframe_template.format(url=f"{server}chart_graphics/count_orders_by_month")
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
return f'<div style="display: flex; flex-direction: row; gap: 10px; border: none; ">{iframe_1} {iframe_2} {iframe_3}</div>'
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
@app.route('/chart_graphics/<path:path>', methods=['GET','OPTIONS'])
|
|
60
|
+
def chart_graphics(path):
|
|
61
|
+
if request.method == 'OPTIONS':
|
|
62
|
+
return jsonify({"result": "ok"})
|
|
63
|
+
|
|
64
|
+
dashboards = get_dashboards()
|
|
65
|
+
if len(dashboards) == 0:
|
|
66
|
+
return jsonify({"result": "No dashboards Found"}), 404
|
|
67
|
+
if path in dashboards:
|
|
68
|
+
return dashboards[path]
|
|
69
|
+
return jsonify({"result": "not found"}), 404
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
##############################
|
|
73
|
+
# generated queries follow
|
|
74
|
+
##############################
|
|
75
|
+
|
|
76
|
+
def get_dashboards():
|
|
77
|
+
if len(dashboard_result) > 0:
|
|
78
|
+
return dashboard_result
|
|
79
|
+
|
|
80
|
+
from jinja2 import Environment, FileSystemLoader
|
|
81
|
+
env = Environment(loader = FileSystemLoader('ui/templates'))
|
|
82
|
+
template = env.get_template('bar_chart.jinja')
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
previously_failed = Path('docs/graphics/sales_by_category.err').exists()
|
|
87
|
+
if previously_failed:
|
|
88
|
+
pass # query has previously failed, so skip it
|
|
89
|
+
else:
|
|
90
|
+
try:
|
|
91
|
+
results = models.Category.sales_by_category(None)
|
|
92
|
+
color = 'rgba(75, 192, 192, 0.2)'
|
|
93
|
+
dashboard1 = template.render(result=results, color=color)
|
|
94
|
+
dashboard_result['sales_by_category']= dashboard1
|
|
95
|
+
except Exception as e:
|
|
96
|
+
msg = f"GenAI query creation error on models.Category.sales_by_category: " + str(e)
|
|
97
|
+
dashboard_result['sales_by_category'] = msg
|
|
98
|
+
app_logger.error(msg)
|
|
99
|
+
with open('docs/graphics//sales_by_category.err', 'w') as err_file:
|
|
100
|
+
err_file.write(msg) # this logs the error to prevent future calls
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
previously_failed = Path('docs/graphics/number_of_sales_per_category.err').exists()
|
|
106
|
+
if previously_failed:
|
|
107
|
+
pass # query has previously failed, so skip it
|
|
108
|
+
else:
|
|
109
|
+
try:
|
|
110
|
+
results = models.Category.number_of_sales_per_category(None)
|
|
111
|
+
color = 'rgba(75, 192, 192, 0.2)'
|
|
112
|
+
dashboard2 = template.render(result=results, color=color)
|
|
113
|
+
dashboard_result['number_of_sales_per_category']= dashboard2
|
|
114
|
+
except Exception as e:
|
|
115
|
+
msg = f"GenAI query creation error on models.Category.number_of_sales_per_category: " + str(e)
|
|
116
|
+
dashboard_result['number_of_sales_per_category'] = msg
|
|
117
|
+
app_logger.error(msg)
|
|
118
|
+
with open('docs/graphics//number_of_sales_per_category.err', 'w') as err_file:
|
|
119
|
+
err_file.write(msg) # this logs the error to prevent future calls
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
previously_failed = Path('docs/graphics/count_orders_by_month.err').exists()
|
|
125
|
+
if previously_failed:
|
|
126
|
+
pass # query has previously failed, so skip it
|
|
127
|
+
else:
|
|
128
|
+
try:
|
|
129
|
+
results = models.Order.count_orders_by_month(None)
|
|
130
|
+
color = 'rgba(75, 192, 192, 0.2)'
|
|
131
|
+
dashboard3 = template.render(result=results, color=color)
|
|
132
|
+
dashboard_result['count_orders_by_month']= dashboard3
|
|
133
|
+
except Exception as e:
|
|
134
|
+
msg = f"GenAI query creation error on models.Order.count_orders_by_month: " + str(e)
|
|
135
|
+
dashboard_result['count_orders_by_month'] = msg
|
|
136
|
+
app_logger.error(msg)
|
|
137
|
+
with open('docs/graphics//count_orders_by_month.err', 'w') as err_file:
|
|
138
|
+
err_file.write(msg) # this logs the error to prevent future calls
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
return dashboard_result
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
from flask import request, jsonify
|
|
2
|
+
import logging
|
|
3
|
+
import os
|
|
4
|
+
import json
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
import requests
|
|
8
|
+
from config.config import Args # circular import error if at top
|
|
9
|
+
|
|
10
|
+
app_logger = logging.getLogger("api_logic_server_app")
|
|
11
|
+
|
|
12
|
+
def add_service(app, api, project_dir, swagger_host: str, PORT: str, method_decorators = []):
|
|
13
|
+
pass
|
|
14
|
+
|
|
15
|
+
def get_server_url():
|
|
16
|
+
""" return the server URL for the OpenAPI spec """
|
|
17
|
+
result = f'http://{Args.instance.swagger_host}:{Args.instance.swagger_port}'
|
|
18
|
+
# get env variable API_LOGIC_SERVER_TUNNEL (or None)
|
|
19
|
+
if tunnel_url := os.getenv("API_LOGIC_SERVER_TUNNEL", None):
|
|
20
|
+
app_logger.info(f".. tunnel URL: {tunnel_url}")
|
|
21
|
+
result = tunnel_url
|
|
22
|
+
return result # + '/api'
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@app.before_request
|
|
26
|
+
def before_any_request():
|
|
27
|
+
# print(f"[DEBUG] Incoming request: {request.method} {request.url}")
|
|
28
|
+
if activate_openapi_logging := False:
|
|
29
|
+
if request.content_type == 'application/json' and request.method in ['POST', 'PUT', 'PATCH']:
|
|
30
|
+
# openapi: Incoming request: PATCH http://localhost:5656/api/Customer/1/ {'data': {'attributes': {'credit_limit': 5555}, 'type': 'Customer', 'id': '1'}}
|
|
31
|
+
# openapi: Incoming request: PATCH http://6f6f-2601-644-4900-d6f0-ecc9-6df3-8863-c5b2.ngrok-free.app/api/Customer/1 {'credit_limit': 5555}
|
|
32
|
+
|
|
33
|
+
app_logger.info(f"openapi: Incoming request: {request.method} {request.url} {str(request.json)}")
|
|
34
|
+
else:
|
|
35
|
+
app_logger.info(f"openapi: Incoming request: {request.method} {request.url}")
|
|
36
|
+
# app_logger.info(f"openapi: Incoming request headers: {request.headers}")
|
|
37
|
+
|
|
38
|
+
chatgpt_request_json = {
|
|
39
|
+
"credit_limit": 25000,
|
|
40
|
+
}
|
|
41
|
+
standard_request_json = {
|
|
42
|
+
"data": {
|
|
43
|
+
"type": "Customer",
|
|
44
|
+
"id": "ALFKI",
|
|
45
|
+
"attributes": {
|
|
46
|
+
"name": "Alice",
|
|
47
|
+
"credit_limit": 25000,
|
|
48
|
+
"balance": 12345
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
swagger_request_json = {
|
|
53
|
+
'data': {
|
|
54
|
+
'attributes': {
|
|
55
|
+
'credit_limit': 5555
|
|
56
|
+
},
|
|
57
|
+
'type': 'Customer',
|
|
58
|
+
'id': '1'
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
pass
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
@app.route('/.well-known/mcp.json', methods=['GET'])
|
|
65
|
+
def mcp_discovery(path=None):
|
|
66
|
+
''' called by mcp_client_executor for discovery; read docs/mcp_learning/ for:
|
|
67
|
+
1. learning, including fan-out & response format, and email requests.
|
|
68
|
+
2. schema
|
|
69
|
+
|
|
70
|
+
see: https://apilogicserver.github.io/Docs/Integration-MCP/#1-discovery
|
|
71
|
+
|
|
72
|
+
test: curl -X GET "http://localhost:5656/.well-known/mcp.json"
|
|
73
|
+
'''
|
|
74
|
+
# return docs/mcp_schema.json
|
|
75
|
+
mcp_learning_path = Path(project_dir + "/docs/mcp_learning")
|
|
76
|
+
try:
|
|
77
|
+
schema_path = mcp_learning_path.joinpath('mcp_schema.json')
|
|
78
|
+
if not schema_path.exists():
|
|
79
|
+
return jsonify({"error": "System Error - /docs/mcp_learning/mcp_schema.json not found"}), 404
|
|
80
|
+
with open(schema_path, "r") as schema_file:
|
|
81
|
+
schema = json.load(schema_file)
|
|
82
|
+
except Exception as e:
|
|
83
|
+
app_logger.error(f"Error loading MCP schema: {e}")
|
|
84
|
+
return jsonify({"error": "MCP schema not found"}), 404
|
|
85
|
+
|
|
86
|
+
try:
|
|
87
|
+
learnings_path = mcp_learning_path.joinpath('mcp.prompt')
|
|
88
|
+
if not learnings_path.exists():
|
|
89
|
+
return jsonify({"error": "System Error - /docs/mcp_learning/mcp,prompt not found"}), 404
|
|
90
|
+
with open(learnings_path, "r") as learnings_file:
|
|
91
|
+
learnings = learnings_file.read()
|
|
92
|
+
except Exception as e:
|
|
93
|
+
app_logger.error(f"Error loading MCP learnings: {e}")
|
|
94
|
+
return jsonify({"error": "MCP learnings not found"}), 404
|
|
95
|
+
schema['learning'] = learnings
|
|
96
|
+
|
|
97
|
+
return jsonify(schema), 200
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
from flask import request, jsonify
|
|
2
|
+
import logging
|
|
3
|
+
|
|
4
|
+
app_logger = logging.getLogger("api_logic_server_app")
|
|
5
|
+
|
|
6
|
+
def add_service(app, api, project_dir, swagger_host: str, PORT: str, method_decorators = []):
|
|
7
|
+
pass
|
|
8
|
+
|
|
9
|
+
@app.route('/hello_service')
|
|
10
|
+
def hello_service():
|
|
11
|
+
"""
|
|
12
|
+
Illustrates:
|
|
13
|
+
* Use standard Flask, here for non-database endpoints.
|
|
14
|
+
|
|
15
|
+
Test it with:
|
|
16
|
+
|
|
17
|
+
http://localhost:5656/hello_service?user=ApiLogicServer
|
|
18
|
+
"""
|
|
19
|
+
user = request.args.get('user')
|
|
20
|
+
app_logger.info(f'{user}')
|
|
21
|
+
return jsonify({"result": f'hello from new_service! from {user}'})
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
from flask import request, jsonify
|
|
2
|
+
import logging
|
|
3
|
+
|
|
4
|
+
app_logger = logging.getLogger("api_logic_server_app")
|
|
5
|
+
|
|
6
|
+
def add_service(app, api, project_dir, swagger_host: str, PORT: str, method_decorators ):
|
|
7
|
+
pass
|
|
8
|
+
|
|
9
|
+
@app.route('/hello_newer_service')
|
|
10
|
+
def hello_newer_service():
|
|
11
|
+
"""
|
|
12
|
+
Illustrates:
|
|
13
|
+
* Use standard Flask, here for non-database endpoints.
|
|
14
|
+
|
|
15
|
+
Test it with:
|
|
16
|
+
|
|
17
|
+
http://localhost:5656/hello_newer_service?user=ApiLogicServer
|
|
18
|
+
"""
|
|
19
|
+
user = request.args.get('user')
|
|
20
|
+
app_logger.info(f'{user}')
|
|
21
|
+
return jsonify({"result": f'hello from even_newer_service! from {user}'})
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>Bar Chart Example</title>
|
|
7
|
+
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
|
8
|
+
</head>
|
|
9
|
+
<body>
|
|
10
|
+
<canvas id="myChart" width="400" height="200"></canvas>
|
|
11
|
+
<script>
|
|
12
|
+
// Input data
|
|
13
|
+
fetch('http://localhost:5656/api/GraphicsServices/number_of_sales_per_category', {
|
|
14
|
+
method: 'POST',
|
|
15
|
+
headers: {
|
|
16
|
+
'accept': 'application/vnd.api+json',
|
|
17
|
+
'Content-Type': 'application/json'
|
|
18
|
+
},
|
|
19
|
+
body: JSON.stringify({}) // Add any required payload here
|
|
20
|
+
})
|
|
21
|
+
.then(response => {
|
|
22
|
+
if (!response.ok) {
|
|
23
|
+
throw new Error(`HTTP error! status: ${response.status}`);
|
|
24
|
+
}
|
|
25
|
+
return response.json();
|
|
26
|
+
})
|
|
27
|
+
.then(result => {
|
|
28
|
+
const labels = [];
|
|
29
|
+
const data = [];
|
|
30
|
+
const type = result.meta.result.chart_type;
|
|
31
|
+
const title = result.meta.result.title;
|
|
32
|
+
const columns = result.meta.result.columns;
|
|
33
|
+
console.log(JSON.stringify(result));
|
|
34
|
+
result.meta.result.results.forEach(item => {
|
|
35
|
+
labels.push(item[columns[0]]);
|
|
36
|
+
data.push(parseFloat(item[columns[1]]));
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
// Create the bar chart
|
|
40
|
+
const ctx = document.getElementById('myChart').getContext('2d');
|
|
41
|
+
const myChart = new Chart(ctx, {
|
|
42
|
+
type: type,
|
|
43
|
+
data: {
|
|
44
|
+
labels: labels,
|
|
45
|
+
datasets: [{
|
|
46
|
+
label: title,
|
|
47
|
+
data: data,
|
|
48
|
+
backgroundColor: 'rgba(75, 192, 192, 0.2)',
|
|
49
|
+
borderColor: 'rgba(75, 192, 192, 1)',
|
|
50
|
+
borderWidth: 1
|
|
51
|
+
}]
|
|
52
|
+
},
|
|
53
|
+
options: {
|
|
54
|
+
responsive: true,
|
|
55
|
+
plugins: {
|
|
56
|
+
legend: {
|
|
57
|
+
position: 'top',
|
|
58
|
+
},
|
|
59
|
+
title: {
|
|
60
|
+
display: true,
|
|
61
|
+
text: title
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
scales: {
|
|
65
|
+
y: {
|
|
66
|
+
beginAtZero: true
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
})
|
|
72
|
+
.catch(error => console.error('Error fetching data:', error));
|
|
73
|
+
|
|
74
|
+
</script>
|
|
75
|
+
</body>
|
|
76
|
+
</html>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
SELECT Category.CategoryName, COUNT(`Order.Id`) AS NumberOfSales FROM Category JOIN Product ON Product.CategoryId = Category.Id JOIN OrderDetail ON OrderDetail.ProductId = Product.Id JOIN `Order` ON `Order`.Id = OrderDetail.OrderId WHERE `Order`.ShippedDate IS NOT NULL GROUP BY Category.CategoryName ORDER BY NumberOfSales DESC;
|