ApiLogicServer 14.2.20__py3-none-any.whl → 14.3.0__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.
Files changed (67) hide show
  1. {ApiLogicServer-14.2.20.dist-info → ApiLogicServer-14.3.0.dist-info}/METADATA +2 -2
  2. {ApiLogicServer-14.2.20.dist-info → ApiLogicServer-14.3.0.dist-info}/RECORD +65 -47
  3. api_logic_server_cli/api_logic_server.py +4 -1
  4. api_logic_server_cli/api_logic_server_info.yaml +3 -3
  5. api_logic_server_cli/create_from_model/__pycache__/api_logic_server_utils.cpython-312.pyc +0 -0
  6. api_logic_server_cli/create_from_model/__pycache__/ont_build.cpython-312.pyc +0 -0
  7. api_logic_server_cli/create_from_model/__pycache__/ont_create.cpython-312.pyc +0 -0
  8. api_logic_server_cli/create_from_model/api_logic_server_utils.py +4 -0
  9. api_logic_server_cli/create_from_model/ont_build.py +53 -19
  10. api_logic_server_cli/create_from_model/ont_create.py +14 -5
  11. api_logic_server_cli/fragments/declare_logic.py +72 -0
  12. api_logic_server_cli/{prototypes/manager/system/genai/create_db_models_inserts/logic_discovery_prefix.py → fragments/declare_logic_begin.py} +2 -1
  13. api_logic_server_cli/fragments/declare_logic_end.py +52 -0
  14. api_logic_server_cli/genai/genai.py +21 -8
  15. api_logic_server_cli/genai/genai_logic_builder.py +14 -11
  16. api_logic_server_cli/genai/genai_svcs.py +102 -7
  17. api_logic_server_cli/model_migrator/model_migrator_start.py +1 -1
  18. api_logic_server_cli/model_migrator/reposreader.py +9 -1
  19. api_logic_server_cli/model_migrator/rule_obj.py +24 -6
  20. api_logic_server_cli/prototypes/base/api/api_discovery/ontimize_api.py +4 -1
  21. api_logic_server_cli/prototypes/base/config/activate_logicbank.py +8 -4
  22. api_logic_server_cli/prototypes/base/database/bind_dbs.py +1 -1
  23. api_logic_server_cli/prototypes/base/database/test_data/readme.md +5 -5
  24. api_logic_server_cli/prototypes/base/logic/declare_logic.py +8 -3
  25. api_logic_server_cli/prototypes/base/logic/load_verify_rules.py +216 -0
  26. api_logic_server_cli/prototypes/base/logic/logic_discovery/auto_discovery.py +22 -13
  27. api_logic_server_cli/prototypes/genai_demo/logic/declare_logic.py +29 -21
  28. api_logic_server_cli/prototypes/manager/README.md +18 -3
  29. api_logic_server_cli/prototypes/manager/system/genai/examples/genai_demo/genai_demo.response_example +19 -18
  30. api_logic_server_cli/prototypes/manager/system/genai/examples/genai_demo/genai_demo_iteration_discount/.DS_Store +0 -0
  31. api_logic_server_cli/prototypes/manager/system/genai/examples/genai_demo/genai_demo_iteration_discount/000_you_are.prompt +1 -0
  32. api_logic_server_cli/prototypes/manager/system/genai/examples/genai_demo/genai_demo_iteration_discount/001_logic_training.prompt +314 -0
  33. api_logic_server_cli/prototypes/manager/system/genai/examples/genai_demo/genai_demo_iteration_discount/002_create_db_models.prompt +150 -0
  34. api_logic_server_cli/prototypes/manager/system/genai/examples/genai_demo/genai_demo_iteration_discount/003_create_db_models.response +134 -0
  35. api_logic_server_cli/prototypes/manager/system/genai/examples/genai_demo/genai_demo_iteration_discount/004_iteratio_logic.prompt +131 -0
  36. api_logic_server_cli/prototypes/manager/system/genai/examples/genai_demo/genai_demo_iteration_discount/005_create_db_models.response-example +141 -0
  37. api_logic_server_cli/prototypes/manager/system/genai/examples/genai_demo/genai_demo_iteration_discount/create_db_models.py +105 -0
  38. api_logic_server_cli/prototypes/manager/system/genai/examples/genai_demo/genai_demo_iteration_discount/db.dbml +70 -0
  39. api_logic_server_cli/prototypes/manager/system/genai/examples/genai_demo/genai_demo_iteration_discount/readme.md +6 -0
  40. api_logic_server_cli/prototypes/manager/system/genai/examples/genai_demo/genai_demo_iteration_discount/response.json +178 -0
  41. api_logic_server_cli/prototypes/manager/system/genai/examples/genai_demo/wg_dev_merge/base_genai_demo_no_logic/logic/declare_logic.py +0 -1
  42. api_logic_server_cli/prototypes/manager/system/genai/examples/genai_demo/wg_dev_merge/dev_demo_no_logic_fixed/logic/declare_logic.py +0 -1
  43. api_logic_server_cli/prototypes/manager/system/genai/examples/genai_demo/wg_dev_merge/wg_demo_no_logic_fixed/genai/examples/genai_demo/wg_dev_merge/base_genai_demo_no_logic/logic/declare_logic.py +0 -1
  44. api_logic_server_cli/prototypes/manager/system/genai/examples/genai_demo/wg_dev_merge/wg_demo_no_logic_fixed/genai/examples/genai_demo/wg_dev_merge/dev_demo_no_logic_fixed/logic/declare_logic.py +0 -1
  45. api_logic_server_cli/prototypes/manager/system/genai/examples/genai_demo/wg_dev_merge/wg_demo_no_logic_fixed/genai/examples/genai_demo/wg_dev_merge/wg_genai_demo_no_logic_fixed_from_CLI/logic/declare_logic.py +0 -1
  46. api_logic_server_cli/prototypes/manager/system/genai/examples/genai_demo/wg_dev_merge/wg_demo_no_logic_fixed/logic/declare_logic.py +0 -1
  47. api_logic_server_cli/prototypes/manager/system/genai/examples/genai_demo/wg_dev_merge/wg_demo_no_logic_fixed/system/genai/examples/genai_demo/wg_dev_merge/base_genai_demo_no_logic/logic/declare_logic.py +0 -1
  48. api_logic_server_cli/prototypes/manager/system/genai/examples/genai_demo/wg_dev_merge/wg_demo_no_logic_fixed/system/genai/examples/genai_demo/wg_dev_merge/dev_demo_no_logic_fixed/logic/declare_logic.py +0 -1
  49. api_logic_server_cli/prototypes/manager/system/genai/examples/genai_demo/wg_dev_merge/wg_demo_no_logic_fixed/system/genai/examples/genai_demo/wg_dev_merge/wg_genai_demo_no_logic_fixed_from_CLI/logic/declare_logic.py +0 -1
  50. api_logic_server_cli/prototypes/manager/system/genai/examples/time_tracking_billing/002_create_db_models.prompt +194 -0
  51. api_logic_server_cli/prototypes/manager/system/genai/examples/time_tracking_billing/003_create_db_models.response +298 -0
  52. api_logic_server_cli/prototypes/manager/system/genai/examples/time_tracking_billing/db.sqlite +0 -0
  53. api_logic_server_cli/prototypes/manager/system/genai/examples/time_tracking_billing/readme.md +8 -0
  54. api_logic_server_cli/prototypes/manager/system/genai/learning_requests/logic_bank_api.prompt +14 -10
  55. api_logic_server_cli/prototypes/manager/system/genai/prompt_inserts/iteration.prompt +2 -1
  56. api_logic_server_cli/prototypes/nw_no_cust/venv_setup/system_note.txt +1 -1
  57. api_logic_server_cli/prototypes/ont_app/templates/home_tree_template.html +9 -0
  58. api_logic_server_cli/prototypes/ont_app/templates/tree_routing.jinja +32 -0
  59. api_logic_server_cli/sqlacodegen_wrapper/sqlacodegen/sqlacodegen/__pycache__/codegen.cpython-312.pyc +0 -0
  60. api_logic_server_cli/sqlacodegen_wrapper/sqlacodegen/sqlacodegen/codegen.py +2 -1
  61. api_logic_server_cli/tools/mini_skel/logic/load_verify_rules.py +1 -1
  62. api_logic_server_cli/model_migrator/system/custom_endpoint.py +0 -545
  63. api_logic_server_cli/prototypes/base/database/test_data/z_test_data_rows.py +0 -98
  64. {ApiLogicServer-14.2.20.dist-info → ApiLogicServer-14.3.0.dist-info}/LICENSE +0 -0
  65. {ApiLogicServer-14.2.20.dist-info → ApiLogicServer-14.3.0.dist-info}/WHEEL +0 -0
  66. {ApiLogicServer-14.2.20.dist-info → ApiLogicServer-14.3.0.dist-info}/entry_points.txt +0 -0
  67. {ApiLogicServer-14.2.20.dist-info → ApiLogicServer-14.3.0.dist-info}/top_level.txt +0 -0
@@ -404,7 +404,7 @@ class ModelMigrationService(object):
404
404
  j = json.loads(d)
405
405
  isActive = j["isActive"]
406
406
  if isActive:
407
- func_type = j["functionType"]
407
+ func_type = j.get("functionType","Java")
408
408
  if func_type == "rowLevel":
409
409
  comments = j["comments"]
410
410
  if comments != "":
@@ -427,6 +427,14 @@ class ModelMigrationService(object):
427
427
  self.add_content("'''")
428
428
  self.add_content("")
429
429
  lac_func.append(name)
430
+ elif func_type == "Java":
431
+ name = j["name"]
432
+ self.add_content("'''")
433
+ self.add_content(f"#Java Function: {j['methodName']}")
434
+ self.add_content(f"#Java Function: {j['className']}")
435
+ self.add_content("'''")
436
+ self.add_content("")
437
+ lac_func.append(name)
430
438
  return lac_func
431
439
 
432
440
  def functionList(self, thisPath: str):
@@ -102,25 +102,43 @@ class RuleObj:
102
102
  title =""
103
103
  if j.title is not None:
104
104
  title = j.title
105
- funName = "fn_" + name.split(".")[0]
105
+ #funName = "fn_" + name.split(".")[0]
106
+ entityLower = entity.lower()
107
+ funName = f"fn_{entityLower}_{ruleType}_{name}"
106
108
  comments = j.comments
107
109
  appliesTo = ""
108
110
  if j.appliesTo is not None:
109
111
  appliesTo = j.appliesTo
110
112
 
111
113
  # Define a function to use in the rule
112
- ruleJSObj = None if self.jsObj is None else fixup(self.jsObj)
114
+ ruleJSObj = None if self.jsObj is None else fixup(self.jsObj) if self.jsObj is None else ""
113
115
  tab = "\t\t"
114
116
  self.add_content(f"\t# RuleType: {ruleType}")
115
117
  self.add_content(f"\t# Title: {title}")
116
118
  self.add_content(f"\t# Name: {name}")
117
119
  self.add_content(f"\t# Entity: {entity}")
120
+
121
+ codeType = j.get("codeType", None)
122
+ if codeType == "Java":
123
+ className = j.get("className", None)
124
+ methodName = j.get("methodName", None)
125
+ self.add_content(f"\t# CodeType: {codeType}")
126
+ self.add_content(f"\t# ClassName: {className}")
127
+ self.add_content(f"\t# MethodName: {methodName}")
128
+ if name == "cache":
129
+ funName = f"fn_{methodName}"
130
+
131
+
118
132
  self.add_content(f"\t# Comments: {comments}")
119
133
  self.add_content("")
134
+ if codeType == "Java":
135
+ self.add_content(f"\tdef {funName}(row: models.{entity}, old_row: models.{entity}, logic_row: LogicRow):")
136
+ self.add_content(f"\t\t# Call Java Code: {className}.{methodName}(row, old_row, logic_row)")
137
+ self.add_content("\t\tpass")
138
+
139
+
120
140
  if ruleJSObj is not None:
121
- entityLower = entity.lower()
122
- funName = f"\tfn_{entityLower}_{ruleType}_{name}"
123
- if len(ruleJSObj) < 80 and ruleType == "formula":
141
+ if len(ruleJSObj) < 80 and ruleType == "formula" and codeType == "JavaScript":
124
142
  pass
125
143
  else:
126
144
  self.add_content(f"\tdef {funName}(row: models.{entity}, old_row: models.{entity}, logic_row: LogicRow):")
@@ -149,7 +167,7 @@ class RuleObj:
149
167
  if ruleJSObj is not None and len(ruleJSObj) > 80:
150
168
  self.add_content(f"{tab}calling={funName})")
151
169
  else:
152
- ruleJSObj = ruleJSObj.replace("return","lambda row: ")
170
+ ruleJSObj = ruleJSObj.replace("return","lambda row: ") if ruleJSObj is not None else ""
153
171
  self.add_content(f"{tab}as_expression={ruleJSObj})")
154
172
  case "count":
155
173
  attr = j.attribute
@@ -231,7 +231,10 @@ def add_service(app, api, project_dir, swagger_host: str, PORT: str, method_deco
231
231
  session.flush()
232
232
  except Exception as ex:
233
233
  session.rollback()
234
- return jsonify({"code":1,"message":f"{ex}","data":[],"sqlTypes":None})
234
+ msg = f"{ex.message if hasattr(ex, 'message') else ex}"
235
+ return jsonify(
236
+ {"code": 1, "message": f"{msg}", "data": [], "sqlTypes": None}
237
+ )
235
238
 
236
239
  return jsonify({"code":0,"message":f"{method}:True","data":result,"sqlTypes":None}) #{f"{method}":True})
237
240
 
@@ -19,23 +19,27 @@ def activate_logicbank(session, constraint_handler):
19
19
 
20
20
  app_logger.info("LogicBank Activation - declare_logic.py")
21
21
  aggregate_defaults = os.environ.get("AGGREGATE_DEFAULTS") == "True"
22
+ all_defaults = os.environ.get("ALL_DEFAULTS") == "True"
22
23
  try: # hover activate for info
23
24
  LogicBank.activate(session=session,
24
25
  activator=declare_logic.declare_logic,
25
26
  constraint_event=constraint_handler,
26
- aggregate_defaults=aggregate_defaults)
27
+ aggregate_defaults=aggregate_defaults,
28
+ all_defaults=all_defaults)
27
29
  except LBActivateException as e:
28
30
  app_logger.error("\nLogic Bank Activation Error -- see https://apilogicserver.github.io/Docs/WebGenAI-CLI/#recovery-options")
29
31
  if e.invalid_rules: logic_logger.error(f"Invalid Rules: {e.invalid_rules}")
30
32
  if e.missing_attributes: logic_logger.error(f"Missing Attrs (try als genai-utils --fixup): {e.missing_attributes}")
31
33
  app_logger.error("\n")
32
- if not os.environ.get("VERIFY_RULES") == "True":
33
- # WG Rule Verification, continue if VERIFY_RULES is True
34
+ if not os.environ.get("VERIFY_RULES") == "True" and not os.environ.get("WG_PROJECT") == "True":
35
+ # WG Rule Verification, continue if VERIFY_RULES is True or inside WebGenAI
34
36
  raise e
35
37
 
36
38
  except Exception as e:
37
39
  app_logger.error(f"Logic Bank Activation Error: {e}")
38
40
  app_logger.exception(e)
39
- raise e
41
+ if not os.environ.get("WG_PROJECT") == "True":
42
+ # Continue if inside WebGenAI
43
+ raise e
40
44
  logic_logger.setLevel(logic_logger_level)
41
45
 
@@ -15,7 +15,7 @@ def bind_dbs(flask_app):
15
15
 
16
16
  flask_app.config.update(SQLALCHEMY_BINDS = {
17
17
  'authentication': flask_app.config['SQLALCHEMY_DATABASE_URI_AUTHENTICATION'],
18
- 'landing_page' : flask_app.config['SQLALCHEMY_DATABASE_URI_LANDING'],
18
+ 'landing_page' : flask_app.config['SQLALCHEMY_DATABASE_URI_LANDING']
19
19
  }) # make multiple databases available to SQLAlchemy
20
20
 
21
21
  return
@@ -1,10 +1,10 @@
1
- GenAI often fails to build proper test data that matches the derivation rules.
1
+ ChatGPT sometimes fails to build proper test data that matches the derivation rules.
2
2
 
3
- You can rebuild the test data, using Logic Bank rules for proper derivations.
3
+ You can rebuild the test data, using Logic Bank rules for proper derivations, to rebuild your `database/db.sqlite` (make a copy first to preserve your existing data).
4
4
 
5
- Envisioned support will create a new db.sqlite, with test data that reflects derivations.
6
- Review, and copy to your database/db.sqlite.
7
5
 
8
6
  ```
9
7
  als genai-utils --rebuild-test-data
10
- ```
8
+ ```
9
+
10
+ You can explore the generated `database/test_data/test_data_code.py` to control test data generation.
@@ -5,9 +5,9 @@ from logic_bank.extensions.rule_extensions import RuleExtension
5
5
  from logic_bank.logic_bank import Rule
6
6
  from logic_bank.logic_bank import DeclareRule
7
7
  import database.models as models
8
- from database.models import *
9
8
  import api.system.opt_locking.opt_locking as opt_locking
10
9
  from security.system.authorization import Grant, Security
10
+ from logic.load_verify_rules import load_verify_rules
11
11
  import logging
12
12
 
13
13
  app_logger = logging.getLogger(__name__)
@@ -22,8 +22,13 @@ def declare_logic():
22
22
  Your Code Goes Here - Use code completion (Rule.) to declare rules
23
23
  '''
24
24
 
25
- from logic.logic_discovery.auto_discovery import discover_logic
26
- discover_logic()
25
+ if os.environ.get("WG_PROJECT"):
26
+ # Inside WG: Load rules from docs/expprt/export.json
27
+ load_verify_rules()
28
+ else:
29
+ # Outside WG: load declare_logic function
30
+ from logic.logic_discovery.auto_discovery import discover_logic
31
+ discover_logic()
27
32
 
28
33
  def handle_all(logic_row: LogicRow): # #als: TIME / DATE STAMPING, OPTIMISTIC LOCKING
29
34
  """
@@ -0,0 +1,216 @@
1
+ #
2
+ # This code loads and verifies rules from export.json and activates them if they pass verification
3
+ # It is WebGenAI specific, used only when env var WG_PROJECT is set
4
+ #
5
+ import ast
6
+ import json
7
+ import logging
8
+ import os
9
+ import sys
10
+ import safrs
11
+ import subprocess
12
+ from importlib import import_module
13
+ from pathlib import Path
14
+ from werkzeug.utils import secure_filename
15
+ from database.models import *
16
+ from logic_bank.logic_bank import DeclareRule, Rule, LogicBank
17
+ from colorama import Fore, Style, init
18
+ from logic_bank.logic_bank import RuleBank
19
+ from logic_bank.rule_bank.rule_bank_setup import find_referenced_attributes
20
+ import tempfile
21
+
22
+
23
+ app_logger = logging.getLogger(__name__)
24
+ declare_logic_message = "ALERT: *** No Rules Yet ***" # printed in api_logic_server.py
25
+
26
+ rule_import_template = """
27
+ from logic_bank.logic_bank import Rule
28
+ from database.models import *
29
+
30
+ def init_rule():
31
+ {rule_code}
32
+ """
33
+
34
+ MANAGER_PATH = "/opt/webgenai/database/manager.py"
35
+ EXPORT_JSON_PATH = os.environ.get("EXPORT_JSON_PATH", "./docs/export/export.json")
36
+
37
+
38
+ def set_rule_status(rule_id, status):
39
+ """
40
+ Call the manager.py script to set the status of a rule
41
+
42
+ (if the status is "active", the manager will remove the rule error)
43
+ """
44
+ if not Path(MANAGER_PATH).exists():
45
+ app_logger.info(f"No manager, can't set rule {rule_id} status {status}")
46
+ return
47
+ subprocess.run([
48
+ "python", MANAGER_PATH,
49
+ "-R", rule_id,
50
+ "--rule-status", status],
51
+ cwd="/opt/webgenai")
52
+
53
+
54
+ def set_rule_error(rule_id, error):
55
+ """
56
+ Call the manager.py script to set the error of a rule
57
+ """
58
+ if not Path(MANAGER_PATH).exists():
59
+ app_logger.warning(f"No manager, can't set rule {rule_id} error {error}")
60
+ return
61
+ subprocess.check_output([
62
+ "python", MANAGER_PATH,
63
+ "-R", rule_id,
64
+ "--rule-error", error],
65
+ cwd="/opt/webgenai")
66
+
67
+
68
+ def check_rule_code_syntax(rule_code):
69
+ """
70
+ Check the syntax of the rule code
71
+ """
72
+ try:
73
+ ast.parse(rule_code)
74
+ return rule_code
75
+ except Exception as exc:
76
+ log.warning(f"Syntax error in rule code '{rule_code}': {exc}")
77
+
78
+ rule_code = rule_code.replace("\\\\", "\\")
79
+ try:
80
+ ast.parse(rule_code)
81
+ return rule_code
82
+ except Exception as exc:
83
+ log.warning(f"Syntax error in rule code '{rule_code}': {exc}")
84
+ return None
85
+
86
+
87
+ def get_exported_rules(rule_code_dir):
88
+ """
89
+ Read the exported rules from export.json and write the code to the
90
+ rule_code_dir
91
+ """
92
+ export_file = Path(EXPORT_JSON_PATH)
93
+ if not export_file.exists():
94
+ app_logger.info(f"{export_file.resolve()} does not exist")
95
+ return []
96
+
97
+ try:
98
+ with open(export_file) as f:
99
+ export = json.load(f)
100
+ rules = export.get("rules", [])
101
+ except Exception as exc:
102
+ app_logger.warning(f"Failed to load rules from {export_file}: {exc}")
103
+ return []
104
+
105
+ for rule in rules:
106
+ if rule["status"] == "rejected":
107
+ continue
108
+ rule_file = rule_code_dir / f"{secure_filename(rule['name']).replace('.','_')}.py"
109
+ try:
110
+ # write current rule to rule_file
111
+ # (we can't use eval, because logicbank uses inspect)
112
+ rule_code_str = check_rule_code_syntax(rule["code"])
113
+ if not rule_code_str:
114
+ continue
115
+ with open(rule_file, "w") as temp_file:
116
+ rule_code = "\n".join([f" {code}" for code in rule_code_str.split("\n")])
117
+ temp_file.write(rule_import_template.format(rule_code=rule_code))
118
+ temp_file_path = temp_file.name
119
+ # module_name used to import current rule
120
+ module_name = Path(temp_file_path).stem
121
+ rule["module_name"] = module_name
122
+ app_logger.info(f"{rule['id']} rule file: {rule_file}")
123
+ except Exception as exc:
124
+ app_logger.exception(exc)
125
+ app_logger.warning(f"Failed to write rule code to {rule_file}: {exc}")
126
+
127
+ return rules
128
+
129
+
130
+ def verify_rules(rule_code_dir, rule_type="accepted"):
131
+ """
132
+ Verify the rules from export.json and activate them if they pass verification
133
+
134
+ Write the rule code to a temporary file and import it as a module
135
+ """
136
+ rules = get_exported_rules(rule_code_dir)
137
+
138
+ for rule in rules:
139
+ if not rule["status"] == rule_type:
140
+ continue
141
+ module_name = rule["module_name"]
142
+ app_logger.info(f"\n{Fore.BLUE}Verifying rule: {module_name} - {rule['id']}{Style.RESET_ALL}")
143
+ try:
144
+ rule_module = import_module(module_name)
145
+ rule_module.init_rule()
146
+ LogicBank.activate(session=safrs.DB.session, activator=rule_module.init_rule)
147
+ if rule["status"] != "active":
148
+ set_rule_status(rule["id"], "active")
149
+ app_logger.info(f"\n{Fore.GREEN}Activated rule {rule['id']}{Style.RESET_ALL}")
150
+
151
+ except Exception as exc:
152
+ app_logger.exception(exc)
153
+ set_rule_error(rule["id"], f"{type(exc).__name__}: {exc}")
154
+ app_logger.warning(f"{Fore.RED}Failed to verify {rule_type} rule code\n{rule['code']}\n{Fore.YELLOW}{type(exc).__name__}: {exc}{Style.RESET_ALL}")
155
+ app_logger.debug(f"{rule}")
156
+ rule["status"] = "accepted"
157
+ rule["error"] = f"{type(exc).__name__}: {exc}"
158
+
159
+ return rules
160
+
161
+
162
+ def load_active_rules(rule_code_dir, rules=None):
163
+ """
164
+ Load the active rules from export.json
165
+ """
166
+ if not rules:
167
+ rules = get_exported_rules(rule_code_dir)
168
+ for rule in rules:
169
+ module_name = rule.get("module_name", None)
170
+ if not rule["status"] == "active" or module_name is None:
171
+ continue
172
+ app_logger.info(f"{Fore.GREEN}Loading Rule Module {module_name} {rule['id']} {Style.RESET_ALL}")
173
+ rule_module = import_module(module_name)
174
+ try:
175
+ rule_module.init_rule()
176
+ except Exception as exc:
177
+ app_logger.exception(exc)
178
+ app_logger.warning(f"{Fore.RED}Failed to load active rule {rule['id']} {rule['code']} {Style.RESET_ALL}")
179
+ set_rule_error(rule["id"], f"{type(exc).__name__}: {exc}")
180
+
181
+
182
+ def get_project_id():
183
+ if os.environ.get("PROJECT_ID"):
184
+ return os.environ.get("PROJECT_ID")
185
+
186
+ return Path(os.getcwd()).name
187
+
188
+
189
+ def load_verify_rules():
190
+
191
+ # Add FileHandler to root_logger
192
+ log_file = Path(os.getenv("LOG_DIR",tempfile.mkdtemp())) / "load_verify_rules.log"
193
+ file_handler = logging.FileHandler(log_file)
194
+ root_logger = logging.getLogger()
195
+ root_logger.addHandler(file_handler)
196
+
197
+ rule_code_dir = Path("./logic/wg_rules") # in the project root
198
+ rule_code_dir.mkdir(parents=True, exist_ok=True)
199
+ sys.path.append(f"{rule_code_dir}")
200
+
201
+ app_logger.info(f"Loading rules from {rule_code_dir.resolve()}")
202
+
203
+ rules = []
204
+
205
+ if os.environ.get("VERIFY_RULES") == "True":
206
+ rules = verify_rules(rule_code_dir, rule_type="active")
207
+ verify_rules(rule_code_dir, rule_type="accepted")
208
+ else:
209
+ try:
210
+ load_active_rules(rule_code_dir, rules)
211
+ except Exception as exc:
212
+ app_logger.exception(exc)
213
+ app_logger.warning(f"{Fore.RED}Failed to load active exported rules: {exc}{Style.RESET_ALL}")
214
+
215
+ root_logger.removeHandler(file_handler)
216
+
@@ -23,21 +23,30 @@ def discover_logic():
23
23
  spec.loader.exec_module(each_logic_file) # runs "bare" module code (e.g., initialization)
24
24
  each_logic_file.declare_logic() # invoke create function
25
25
 
26
- if False and Path(__file__).parent.parent.parent.joinpath("docs/project_is_genai_demo.txt").exists():
27
- return # for genai_demo, logic is in logic/declare_logic.py (so ignore logic_discovery)
26
+ # if False and Path(__file__).parent.parent.parent.joinpath("docs/project_is_genai_demo.txt").exists():
27
+ # return # for genai_demo, logic is in logic/declare_logic.py (so ignore logic_discovery)
28
28
 
29
29
  wg_logic_path = Path(__file__).parent.parent.joinpath("wg_rules")
30
- for root, dirs, files in os.walk(wg_logic_path):
31
- for file in files:
32
- if file.endswith(".py"):
33
- spec = importlib.util.spec_from_file_location("module.name", wg_logic_path.joinpath(file))
34
- if file.endswith("auto_discovery.py"):
35
- pass
36
- else:
37
- logic.append(file)
38
- each_logic_file = importlib.util.module_from_spec(spec)
39
- spec.loader.exec_module(each_logic_file) # runs "bare" module code (e.g., initialization)
40
- each_logic_file.init_rule() # invoke create function
30
+ if wg_logic_path.exists():
31
+ run_local = os.environ.get("WG_PROJECT") is None # eg, running export locally
32
+ # run_local = False # for debug
33
+ if run_local:
34
+ wg_export_logic_path = Path(__file__).parent.parent.parent.joinpath("logic/wg_rules/active_rules_export.py")
35
+ if wg_export_logic_path.is_file():
36
+ spec = importlib.util.spec_from_file_location("module.name", wg_export_logic_path)
37
+ logic.append(str(wg_export_logic_path))
38
+ wg_export_logic_file = importlib.util.module_from_spec(spec)
39
+ spec.loader.exec_module(wg_export_logic_file) # runs "bare" module code (e.g., initialization)
40
+ wg_export_logic_file.declare_logic() # invoke create function
41
+ else:
42
+ for root, dirs, files in os.walk(wg_logic_path):
43
+ for file in files:
44
+ if file.endswith(".py") and 'active_rules_export.py' != file:
45
+ spec = importlib.util.spec_from_file_location("module.name", wg_logic_path.joinpath(file))
46
+ logic.append(file)
47
+ each_logic_file = importlib.util.module_from_spec(spec)
48
+ spec.loader.exec_module(each_logic_file) # runs "bare" module code (e.g., initialization)
49
+ each_logic_file.init_rule() # invoke create function
41
50
 
42
51
  app_logger.info(f"..discovered logic: {logic}")
43
52
  return
@@ -20,19 +20,16 @@ def declare_logic():
20
20
  ''' Declarative multi-table derivations and constraints, extensible with Python.
21
21
 
22
22
  Brief background: see readme_declare_logic.md
23
-
24
- Your Code Goes Here - Use code completion (Rule.) to declare rules
25
23
 
26
- GenAI: Paste the following into Copilot Chat, and paste the result below.
24
+ GenAI: the following prompt was sent to GenAI-Logic, which translated it into the code below:
27
25
 
28
- Use Logic Bank to enforce these requirements:
26
+ Use case: Check Credit
29
27
 
30
- Enforce the Check Credit requirement (do not generate check constraints):
31
- 1. Customer.Balance <= CreditLimit
32
- 2. Customer.Balance = Sum(Order.AmountTotal where date shipped is null)
33
- 3. Order.AmountTotal = Sum(Items.Amount)
34
- 4. Items.Amount = Quantity * UnitPrice
35
- 5. Store the Items.UnitPrice as a copy from Product.UnitPrice
28
+ 1. The Customer's balance is less than the credit limit
29
+ 2. The Customer's balance is the sum of the Order amount_total where date_shipped is null
30
+ 3. The Order's amount_total is the sum of the Item amount
31
+ 4. The Item amount is the quantity * unit_price
32
+ 5. The Item unit_price is copied from the Product unit_price
36
33
  '''
37
34
 
38
35
  from logic_bank.logic_bank import Rule
@@ -40,39 +37,50 @@ def declare_logic():
40
37
  from logic.logic_discovery.auto_discovery import discover_logic
41
38
  discover_logic()
42
39
 
43
- # Logic from GenAI: (or, use your IDE w/ code completion)n
40
+ # Logic from GenAI: (or, use your IDE w/ code completion)
41
+
42
+ # Ensures the customer's balance is less than the credit limit.
43
+ Rule.constraint(validate=Customer,
44
+ as_condition=lambda row: row.Balance <= row.CreditLimit,
45
+ error_msg="Customer balance ({row.Balance}) exceeds credit limit ({row.CreditLimit})")
46
+
47
+ # Computes the customer's balance as the sum of unshipped orders.
44
48
  Rule.sum(derive=Customer.Balance, as_sum_of=Order.AmountTotal, where=lambda row: row.ShipDate is None)
49
+
50
+ # Computes the total amount of an order as the sum of item amounts.
45
51
  Rule.sum(derive=Order.AmountTotal, as_sum_of=Item.Amount)
52
+
53
+ # Calculates the item amount as the quantity times unit price. (original rule, prior to iteration)
46
54
  # Rule.formula(derive=Item.amount, as_expression=lambda row: row.quantity * row.unit_price)
55
+
56
+ # Copies the unit price from the parent product to the item.
47
57
  Rule.copy(derive=Item.UnitPrice, from_parent=Product.UnitPrice)
48
- Rule.constraint(validate=Customer,
49
- as_condition=lambda row: row.Balance <= row.CreditLimit,
50
- error_msg="Customer balance ({row.Balance}) exceeds credit limit ({row.CreditLimit})")
51
58
 
52
59
  # End Logic from GenAI
53
60
 
61
+
62
+ #als: Demonstrate that logic == Rules + Python (for extensibility)
63
+
64
+ # 4. Items.Amount = Quantity * UnitPrice, altered with IDE for CarbonNeutral discount
54
65
  def derive_amount(row: Item, old_row: Item, logic_row: LogicRow):
55
66
  amount = row.Quantity * row.UnitPrice
56
67
  if row.Product.CarbonNeutral == True and row.Quantity >= 10:
57
68
  amount = amount * Decimal(0.9) # breakpoint here
58
69
  return amount
59
-
60
- # 4. Items.Amount = Quantity * UnitPrice
61
- Rule.formula(derive=Item.Amount, calling=derive_amount)
62
-
63
- #als: Demonstrate that logic == Rules + Python (for extensibility)
70
+ # now register function; note logic ordering is automatic
71
+ Rule.formula(derive=Item.Amount, calling=derive_amount)
64
72
 
65
73
  def send_order_to_shipping(row: Order, old_row: Order, logic_row: LogicRow):
66
74
  """ #als: Send Kafka message formatted by OrderShipping RowDictMapper
67
75
 
68
76
  Format row per shipping requirements, and send (e.g., a message)
69
77
 
70
- NB: the after_flush event makes Order.Id avaible. Contrast to congratulate_sales_rep().
78
+ NB: the after_flush event makes Order.Id avaible.
71
79
 
72
80
  Args:
73
81
  row (Order): inserted Order
74
82
  old_row (Order): n/a
75
- logic_row (LogicRow): bundles curr/old row, with ins/upd/dlt logic
83
+ logic_row (LogicRow): bundles curr/old row, with ins/upd/dlt (etc) state
76
84
  """
77
85
  if logic_row.is_inserted():
78
86
  kafka_producer.send_kafka_message(logic_row=logic_row,
@@ -149,9 +149,22 @@ Verify it's operating properly:
149
149
 
150
150
  <details markdown>
151
151
 
152
- <summary> You can iterate the data model and logic</summary>
152
+ <summary> You can iterate the logic and data model</summary>
153
153
 
154
- <br>You can add new columns/tables, while keeping the prior model intact:
154
+ <br>Logic iterations are particuarly useful. For example, here we take the basic check-credit logic, and add:
155
+
156
+ > Provide a 10% discount when buying more than 10 carbon neutral products.<br><br>The Item carbon neutral is copied from the Product carbon neutral
157
+
158
+ Explore [genai_demo_iteration_discount](system/genai/examples/genai_demo/genai_demo_iteration_discount). This will add carbon_neutral to the data model, and update the logic to provide the discount:
159
+
160
+ ```bash title='Iterate Business Logic'
161
+ # Iterate with data model and logic
162
+ als genai --project-name='genai_demo_with_logic' --using=system/genai/examples/genai_demo/genai_demo_iteration
163
+ # open Docs/db.dbml
164
+ ```
165
+ <br>
166
+
167
+ You can add new columns/tables, while keeping the prior model intact:
155
168
 
156
169
  ```bash title='Iterate Without Logic'
157
170
  # Step 1 - create without logic
@@ -164,7 +177,9 @@ als genai --project-name='genai_demo_no_logic' --using=system/genai/examples/gen
164
177
  als genai --project-name='genai_demo_with_logic' --using=system/genai/examples/genai_demo/genai_demo_iteration
165
178
  # open Docs/db.dbml
166
179
  ```
167
- > Explore [genai_demo_iteration](system/genai/examples/genai_demo/genai_demo_iteration) - observe the `--using` is a *directory* of prompts. These include the prompts from the first example, plus an *iteration prompt* (`004_iteration_renames_logic.prompt`) to rename tables and add logic.
180
+
181
+ Explore [genai_demo_iteration](system/genai/examples/genai_demo/genai_demo_iteration) - observe the `--using` is a *directory* of prompts. These include the prompts from the first example, plus an *iteration prompt* (`004_iteration_renames_logic.prompt`) to rename tables and add logic.
182
+
168
183
 
169
184
  </details>
170
185
  </br>