pydpm_xl 0.2.5__tar.gz → 0.2.5rc1__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.
- {pydpm_xl-0.2.5 → pydpm_xl-0.2.5rc1}/PKG-INFO +1 -1
- {pydpm_xl-0.2.5 → pydpm_xl-0.2.5rc1}/py_dpm/__init__.py +1 -1
- {pydpm_xl-0.2.5 → pydpm_xl-0.2.5rc1}/py_dpm/api/dpm_xl/ast_generator.py +17 -122
- {pydpm_xl-0.2.5 → pydpm_xl-0.2.5rc1}/py_dpm/dpm/models.py +7 -257
- {pydpm_xl-0.2.5 → pydpm_xl-0.2.5rc1}/py_dpm/dpm_xl/utils/scopes_calculator.py +30 -86
- {pydpm_xl-0.2.5 → pydpm_xl-0.2.5rc1}/pydpm_xl.egg-info/PKG-INFO +1 -1
- {pydpm_xl-0.2.5 → pydpm_xl-0.2.5rc1}/pyproject.toml +2 -2
- {pydpm_xl-0.2.5 → pydpm_xl-0.2.5rc1}/LICENSE +0 -0
- {pydpm_xl-0.2.5 → pydpm_xl-0.2.5rc1}/README.md +0 -0
- {pydpm_xl-0.2.5 → pydpm_xl-0.2.5rc1}/py_dpm/api/__init__.py +0 -0
- {pydpm_xl-0.2.5 → pydpm_xl-0.2.5rc1}/py_dpm/api/dpm/__init__.py +0 -0
- {pydpm_xl-0.2.5 → pydpm_xl-0.2.5rc1}/py_dpm/api/dpm/data_dictionary.py +0 -0
- {pydpm_xl-0.2.5 → pydpm_xl-0.2.5rc1}/py_dpm/api/dpm/explorer.py +0 -0
- {pydpm_xl-0.2.5 → pydpm_xl-0.2.5rc1}/py_dpm/api/dpm/hierarchical_queries.py +0 -0
- {pydpm_xl-0.2.5 → pydpm_xl-0.2.5rc1}/py_dpm/api/dpm/instance.py +0 -0
- {pydpm_xl-0.2.5 → pydpm_xl-0.2.5rc1}/py_dpm/api/dpm/migration.py +0 -0
- {pydpm_xl-0.2.5 → pydpm_xl-0.2.5rc1}/py_dpm/api/dpm_xl/__init__.py +0 -0
- {pydpm_xl-0.2.5 → pydpm_xl-0.2.5rc1}/py_dpm/api/dpm_xl/complete_ast.py +0 -0
- {pydpm_xl-0.2.5 → pydpm_xl-0.2.5rc1}/py_dpm/api/dpm_xl/operation_scopes.py +0 -0
- {pydpm_xl-0.2.5 → pydpm_xl-0.2.5rc1}/py_dpm/api/dpm_xl/semantic.py +0 -0
- {pydpm_xl-0.2.5 → pydpm_xl-0.2.5rc1}/py_dpm/api/dpm_xl/syntax.py +0 -0
- {pydpm_xl-0.2.5 → pydpm_xl-0.2.5rc1}/py_dpm/cli/__init__.py +0 -0
- {pydpm_xl-0.2.5 → pydpm_xl-0.2.5rc1}/py_dpm/cli/commands/__init__.py +0 -0
- {pydpm_xl-0.2.5 → pydpm_xl-0.2.5rc1}/py_dpm/cli/main.py +0 -0
- {pydpm_xl-0.2.5 → pydpm_xl-0.2.5rc1}/py_dpm/dpm/__init__.py +0 -0
- {pydpm_xl-0.2.5 → pydpm_xl-0.2.5rc1}/py_dpm/dpm/migration.py +0 -0
- {pydpm_xl-0.2.5 → pydpm_xl-0.2.5rc1}/py_dpm/dpm/queries/base.py +0 -0
- {pydpm_xl-0.2.5 → pydpm_xl-0.2.5rc1}/py_dpm/dpm/queries/basic_objects.py +0 -0
- {pydpm_xl-0.2.5 → pydpm_xl-0.2.5rc1}/py_dpm/dpm/queries/explorer_queries.py +0 -0
- {pydpm_xl-0.2.5 → pydpm_xl-0.2.5rc1}/py_dpm/dpm/queries/filters.py +0 -0
- {pydpm_xl-0.2.5 → pydpm_xl-0.2.5rc1}/py_dpm/dpm/queries/glossary.py +0 -0
- {pydpm_xl-0.2.5 → pydpm_xl-0.2.5rc1}/py_dpm/dpm/queries/hierarchical_queries.py +0 -0
- {pydpm_xl-0.2.5 → pydpm_xl-0.2.5rc1}/py_dpm/dpm/queries/tables.py +0 -0
- {pydpm_xl-0.2.5 → pydpm_xl-0.2.5rc1}/py_dpm/dpm/utils.py +0 -0
- {pydpm_xl-0.2.5 → pydpm_xl-0.2.5rc1}/py_dpm/dpm_xl/__init__.py +0 -0
- {pydpm_xl-0.2.5 → pydpm_xl-0.2.5rc1}/py_dpm/dpm_xl/ast/__init__.py +0 -0
- {pydpm_xl-0.2.5 → pydpm_xl-0.2.5rc1}/py_dpm/dpm_xl/ast/constructor.py +0 -0
- {pydpm_xl-0.2.5 → pydpm_xl-0.2.5rc1}/py_dpm/dpm_xl/ast/ml_generation.py +0 -0
- {pydpm_xl-0.2.5 → pydpm_xl-0.2.5rc1}/py_dpm/dpm_xl/ast/module_analyzer.py +0 -0
- {pydpm_xl-0.2.5 → pydpm_xl-0.2.5rc1}/py_dpm/dpm_xl/ast/module_dependencies.py +0 -0
- {pydpm_xl-0.2.5 → pydpm_xl-0.2.5rc1}/py_dpm/dpm_xl/ast/nodes.py +0 -0
- {pydpm_xl-0.2.5 → pydpm_xl-0.2.5rc1}/py_dpm/dpm_xl/ast/operands.py +0 -0
- {pydpm_xl-0.2.5 → pydpm_xl-0.2.5rc1}/py_dpm/dpm_xl/ast/template.py +0 -0
- {pydpm_xl-0.2.5 → pydpm_xl-0.2.5rc1}/py_dpm/dpm_xl/ast/visitor.py +0 -0
- {pydpm_xl-0.2.5 → pydpm_xl-0.2.5rc1}/py_dpm/dpm_xl/ast/where_clause.py +0 -0
- {pydpm_xl-0.2.5 → pydpm_xl-0.2.5rc1}/py_dpm/dpm_xl/grammar/__init__.py +0 -0
- {pydpm_xl-0.2.5 → pydpm_xl-0.2.5rc1}/py_dpm/dpm_xl/grammar/generated/__init__.py +0 -0
- {pydpm_xl-0.2.5 → pydpm_xl-0.2.5rc1}/py_dpm/dpm_xl/grammar/generated/dpm_xlLexer.interp +0 -0
- {pydpm_xl-0.2.5 → pydpm_xl-0.2.5rc1}/py_dpm/dpm_xl/grammar/generated/dpm_xlLexer.py +0 -0
- {pydpm_xl-0.2.5 → pydpm_xl-0.2.5rc1}/py_dpm/dpm_xl/grammar/generated/dpm_xlLexer.tokens +0 -0
- {pydpm_xl-0.2.5 → pydpm_xl-0.2.5rc1}/py_dpm/dpm_xl/grammar/generated/dpm_xlParser.interp +0 -0
- {pydpm_xl-0.2.5 → pydpm_xl-0.2.5rc1}/py_dpm/dpm_xl/grammar/generated/dpm_xlParser.py +0 -0
- {pydpm_xl-0.2.5 → pydpm_xl-0.2.5rc1}/py_dpm/dpm_xl/grammar/generated/dpm_xlParser.tokens +0 -0
- {pydpm_xl-0.2.5 → pydpm_xl-0.2.5rc1}/py_dpm/dpm_xl/grammar/generated/dpm_xlParserListener.py +0 -0
- {pydpm_xl-0.2.5 → pydpm_xl-0.2.5rc1}/py_dpm/dpm_xl/grammar/generated/dpm_xlParserVisitor.py +0 -0
- {pydpm_xl-0.2.5 → pydpm_xl-0.2.5rc1}/py_dpm/dpm_xl/grammar/generated/listeners.py +0 -0
- {pydpm_xl-0.2.5 → pydpm_xl-0.2.5rc1}/py_dpm/dpm_xl/operators/__init__.py +0 -0
- {pydpm_xl-0.2.5 → pydpm_xl-0.2.5rc1}/py_dpm/dpm_xl/operators/aggregate.py +0 -0
- {pydpm_xl-0.2.5 → pydpm_xl-0.2.5rc1}/py_dpm/dpm_xl/operators/arithmetic.py +0 -0
- {pydpm_xl-0.2.5 → pydpm_xl-0.2.5rc1}/py_dpm/dpm_xl/operators/base.py +0 -0
- {pydpm_xl-0.2.5 → pydpm_xl-0.2.5rc1}/py_dpm/dpm_xl/operators/boolean.py +0 -0
- {pydpm_xl-0.2.5 → pydpm_xl-0.2.5rc1}/py_dpm/dpm_xl/operators/clause.py +0 -0
- {pydpm_xl-0.2.5 → pydpm_xl-0.2.5rc1}/py_dpm/dpm_xl/operators/comparison.py +0 -0
- {pydpm_xl-0.2.5 → pydpm_xl-0.2.5rc1}/py_dpm/dpm_xl/operators/conditional.py +0 -0
- {pydpm_xl-0.2.5 → pydpm_xl-0.2.5rc1}/py_dpm/dpm_xl/operators/string.py +0 -0
- {pydpm_xl-0.2.5 → pydpm_xl-0.2.5rc1}/py_dpm/dpm_xl/operators/time.py +0 -0
- {pydpm_xl-0.2.5 → pydpm_xl-0.2.5rc1}/py_dpm/dpm_xl/semantic_analyzer.py +0 -0
- {pydpm_xl-0.2.5 → pydpm_xl-0.2.5rc1}/py_dpm/dpm_xl/symbols.py +0 -0
- {pydpm_xl-0.2.5 → pydpm_xl-0.2.5rc1}/py_dpm/dpm_xl/types/__init__.py +0 -0
- {pydpm_xl-0.2.5 → pydpm_xl-0.2.5rc1}/py_dpm/dpm_xl/types/promotion.py +0 -0
- {pydpm_xl-0.2.5 → pydpm_xl-0.2.5rc1}/py_dpm/dpm_xl/types/scalar.py +0 -0
- {pydpm_xl-0.2.5 → pydpm_xl-0.2.5rc1}/py_dpm/dpm_xl/types/time.py +0 -0
- {pydpm_xl-0.2.5 → pydpm_xl-0.2.5rc1}/py_dpm/dpm_xl/utils/__init__.py +0 -0
- {pydpm_xl-0.2.5 → pydpm_xl-0.2.5rc1}/py_dpm/dpm_xl/utils/data_handlers.py +0 -0
- {pydpm_xl-0.2.5 → pydpm_xl-0.2.5rc1}/py_dpm/dpm_xl/utils/operands_mapping.py +0 -0
- {pydpm_xl-0.2.5 → pydpm_xl-0.2.5rc1}/py_dpm/dpm_xl/utils/operator_mapping.py +0 -0
- {pydpm_xl-0.2.5 → pydpm_xl-0.2.5rc1}/py_dpm/dpm_xl/utils/serialization.py +0 -0
- {pydpm_xl-0.2.5 → pydpm_xl-0.2.5rc1}/py_dpm/dpm_xl/utils/tokens.py +0 -0
- {pydpm_xl-0.2.5 → pydpm_xl-0.2.5rc1}/py_dpm/exceptions/__init__.py +0 -0
- {pydpm_xl-0.2.5 → pydpm_xl-0.2.5rc1}/py_dpm/exceptions/exceptions.py +0 -0
- {pydpm_xl-0.2.5 → pydpm_xl-0.2.5rc1}/py_dpm/exceptions/messages.py +0 -0
- {pydpm_xl-0.2.5 → pydpm_xl-0.2.5rc1}/py_dpm/instance/__init__.py +0 -0
- {pydpm_xl-0.2.5 → pydpm_xl-0.2.5rc1}/py_dpm/instance/instance.py +0 -0
- {pydpm_xl-0.2.5 → pydpm_xl-0.2.5rc1}/pydpm_xl.egg-info/SOURCES.txt +0 -0
- {pydpm_xl-0.2.5 → pydpm_xl-0.2.5rc1}/pydpm_xl.egg-info/dependency_links.txt +0 -0
- {pydpm_xl-0.2.5 → pydpm_xl-0.2.5rc1}/pydpm_xl.egg-info/entry_points.txt +0 -0
- {pydpm_xl-0.2.5 → pydpm_xl-0.2.5rc1}/pydpm_xl.egg-info/requires.txt +0 -0
- {pydpm_xl-0.2.5 → pydpm_xl-0.2.5rc1}/pydpm_xl.egg-info/top_level.txt +0 -0
- {pydpm_xl-0.2.5 → pydpm_xl-0.2.5rc1}/setup.cfg +0 -0
- {pydpm_xl-0.2.5 → pydpm_xl-0.2.5rc1}/tests/test_cli_semantic.py +0 -0
- {pydpm_xl-0.2.5 → pydpm_xl-0.2.5rc1}/tests/test_data_dictionary_releases.py +0 -0
- {pydpm_xl-0.2.5 → pydpm_xl-0.2.5rc1}/tests/test_db_connection_handling.py +0 -0
- {pydpm_xl-0.2.5 → pydpm_xl-0.2.5rc1}/tests/test_get_table_details.py +0 -0
- {pydpm_xl-0.2.5 → pydpm_xl-0.2.5rc1}/tests/test_get_tables_date_filter.py +0 -0
- {pydpm_xl-0.2.5 → pydpm_xl-0.2.5rc1}/tests/test_get_tables_release_code.py +0 -0
- {pydpm_xl-0.2.5 → pydpm_xl-0.2.5rc1}/tests/test_hierarchical_query.py +0 -0
- {pydpm_xl-0.2.5 → pydpm_xl-0.2.5rc1}/tests/test_query_refactor.py +0 -0
- {pydpm_xl-0.2.5 → pydpm_xl-0.2.5rc1}/tests/test_release_filters_semantic.py +0 -0
- {pydpm_xl-0.2.5 → pydpm_xl-0.2.5rc1}/tests/test_semantic_release.py +0 -0
|
@@ -41,7 +41,7 @@ Available packages:
|
|
|
41
41
|
- pydpm.api: Main APIs for migration, syntax, and semantic analysis
|
|
42
42
|
"""
|
|
43
43
|
|
|
44
|
-
__version__ = "0.2.
|
|
44
|
+
__version__ = "0.2.5rc1"
|
|
45
45
|
__author__ = "MeaningfulData S.L."
|
|
46
46
|
__email__ = "info@meaningfuldata.eu"
|
|
47
47
|
__license__ = "GPL-3.0-or-later"
|
|
@@ -811,7 +811,7 @@ class ASTGeneratorAPI:
|
|
|
811
811
|
preconditions = {}
|
|
812
812
|
precondition_variables = {}
|
|
813
813
|
|
|
814
|
-
if precondition:
|
|
814
|
+
if precondition or (context and "table" in context):
|
|
815
815
|
preconditions, precondition_variables = self._build_preconditions(
|
|
816
816
|
precondition=precondition,
|
|
817
817
|
context=context,
|
|
@@ -974,6 +974,8 @@ class ASTGeneratorAPI:
|
|
|
974
974
|
match = re.match(r"\{v_([^}]+)\}", precondition)
|
|
975
975
|
if match:
|
|
976
976
|
table_code = match.group(1)
|
|
977
|
+
elif context and "table" in context:
|
|
978
|
+
table_code = context["table"]
|
|
977
979
|
|
|
978
980
|
if table_code:
|
|
979
981
|
# Query database for actual variable ID and version
|
|
@@ -1037,81 +1039,6 @@ class ASTGeneratorAPI:
|
|
|
1037
1039
|
extract_from_node(ast_dict)
|
|
1038
1040
|
return all_variables, variables_by_table
|
|
1039
1041
|
|
|
1040
|
-
def _extract_time_shifts_by_table(self, expression: str) -> Dict[str, str]:
|
|
1041
|
-
"""
|
|
1042
|
-
Extract time shift information for each table in the expression.
|
|
1043
|
-
|
|
1044
|
-
Uses the AST to properly parse the expression and find TimeShiftOp nodes
|
|
1045
|
-
to determine the ref_period for each table reference.
|
|
1046
|
-
|
|
1047
|
-
Args:
|
|
1048
|
-
expression: DPM-XL expression
|
|
1049
|
-
|
|
1050
|
-
Returns:
|
|
1051
|
-
Dict mapping table codes to ref_period values (e.g., {"C_01.00": "T-1Q"})
|
|
1052
|
-
Tables without time shifts default to "T".
|
|
1053
|
-
"""
|
|
1054
|
-
from py_dpm.dpm_xl.ast.template import ASTTemplate
|
|
1055
|
-
|
|
1056
|
-
time_shifts = {}
|
|
1057
|
-
current_period = ["t"] # Use list to allow mutation in nested function
|
|
1058
|
-
|
|
1059
|
-
class TimeShiftExtractor(ASTTemplate):
|
|
1060
|
-
"""Lightweight AST visitor that extracts time shifts for each table."""
|
|
1061
|
-
|
|
1062
|
-
def visit_TimeShiftOp(self, node):
|
|
1063
|
-
# Save current time period and compute new one
|
|
1064
|
-
previous_period = current_period[0]
|
|
1065
|
-
|
|
1066
|
-
period_indicator = node.period_indicator
|
|
1067
|
-
shift_number = node.shift_number
|
|
1068
|
-
|
|
1069
|
-
# Compute time period (same logic as ModuleDependencies)
|
|
1070
|
-
if "-" in str(shift_number):
|
|
1071
|
-
current_period[0] = f"t+{period_indicator}{shift_number}"
|
|
1072
|
-
else:
|
|
1073
|
-
current_period[0] = f"t-{period_indicator}{shift_number}"
|
|
1074
|
-
|
|
1075
|
-
# Visit operand (which contains the VarID)
|
|
1076
|
-
self.visit(node.operand)
|
|
1077
|
-
|
|
1078
|
-
# Restore previous time period
|
|
1079
|
-
current_period[0] = previous_period
|
|
1080
|
-
|
|
1081
|
-
def visit_VarID(self, node):
|
|
1082
|
-
if node.table and current_period[0] != "t":
|
|
1083
|
-
time_shifts[node.table] = current_period[0]
|
|
1084
|
-
|
|
1085
|
-
def convert_to_ref_period(internal_period: str) -> str:
|
|
1086
|
-
"""Convert internal time period format to ref_period format.
|
|
1087
|
-
|
|
1088
|
-
Internal format: "t+Q-1" or "t-Q1"
|
|
1089
|
-
Output format: "T-1Q" for one quarter back
|
|
1090
|
-
"""
|
|
1091
|
-
if internal_period.startswith("t+"):
|
|
1092
|
-
# e.g., "t+Q-1" -> "T-1Q"
|
|
1093
|
-
indicator = internal_period[2]
|
|
1094
|
-
number = internal_period[3:]
|
|
1095
|
-
if number.startswith("-"):
|
|
1096
|
-
return f"T{number}{indicator}"
|
|
1097
|
-
return f"T+{number}{indicator}"
|
|
1098
|
-
elif internal_period.startswith("t-"):
|
|
1099
|
-
# e.g., "t-Q1" -> "T-1Q"
|
|
1100
|
-
indicator = internal_period[2]
|
|
1101
|
-
number = internal_period[3:]
|
|
1102
|
-
return f"T-{number}{indicator}"
|
|
1103
|
-
return "T"
|
|
1104
|
-
|
|
1105
|
-
try:
|
|
1106
|
-
ast = self.syntax_api.parse_expression(expression)
|
|
1107
|
-
extractor = TimeShiftExtractor()
|
|
1108
|
-
extractor.visit(ast)
|
|
1109
|
-
|
|
1110
|
-
return {table: convert_to_ref_period(period) for table, period in time_shifts.items()}
|
|
1111
|
-
|
|
1112
|
-
except Exception:
|
|
1113
|
-
return {}
|
|
1114
|
-
|
|
1115
1042
|
def _detect_cross_module_dependencies(
|
|
1116
1043
|
self,
|
|
1117
1044
|
expression: str,
|
|
@@ -1148,7 +1075,7 @@ class ASTGeneratorAPI:
|
|
|
1148
1075
|
)
|
|
1149
1076
|
|
|
1150
1077
|
try:
|
|
1151
|
-
# Get tables with module info
|
|
1078
|
+
# Get tables with module info
|
|
1152
1079
|
tables_with_modules = scopes_api.get_tables_with_metadata_from_expression(
|
|
1153
1080
|
expression=expression,
|
|
1154
1081
|
release_id=release_id
|
|
@@ -1164,35 +1091,10 @@ class ASTGeneratorAPI:
|
|
|
1164
1091
|
if scope_result.has_error or not scope_result.is_cross_module:
|
|
1165
1092
|
return {}, []
|
|
1166
1093
|
|
|
1167
|
-
# Extract time shifts for each table from expression
|
|
1168
|
-
time_shifts_by_table = self._extract_time_shifts_by_table(expression)
|
|
1169
|
-
|
|
1170
1094
|
# Determine primary module from first table if not provided
|
|
1171
1095
|
if primary_module_vid is None and tables_with_modules:
|
|
1172
1096
|
primary_module_vid = tables_with_modules[0].get("module_vid")
|
|
1173
1097
|
|
|
1174
|
-
# Helper to normalize table code (remove 't' prefix if present)
|
|
1175
|
-
def normalize_table_code(code: str) -> str:
|
|
1176
|
-
return code[1:] if code and code.startswith('t') else code
|
|
1177
|
-
|
|
1178
|
-
# Helper to lookup ref_period for a table
|
|
1179
|
-
def get_ref_period(table_code: str) -> str:
|
|
1180
|
-
if not table_code:
|
|
1181
|
-
return "T"
|
|
1182
|
-
ref = time_shifts_by_table.get(table_code)
|
|
1183
|
-
if not ref:
|
|
1184
|
-
ref = time_shifts_by_table.get(normalize_table_code(table_code))
|
|
1185
|
-
return ref or "T"
|
|
1186
|
-
|
|
1187
|
-
# Helper to lookup variables for a table
|
|
1188
|
-
def get_table_variables(table_code: str) -> dict:
|
|
1189
|
-
if not table_code:
|
|
1190
|
-
return {}
|
|
1191
|
-
variables = variables_by_table.get(table_code)
|
|
1192
|
-
if not variables:
|
|
1193
|
-
variables = variables_by_table.get(f"t{table_code}", {})
|
|
1194
|
-
return variables or {}
|
|
1195
|
-
|
|
1196
1098
|
# Group external tables by module
|
|
1197
1099
|
external_modules = {}
|
|
1198
1100
|
for table_info in tables_with_modules:
|
|
@@ -1204,38 +1106,36 @@ class ASTGeneratorAPI:
|
|
|
1204
1106
|
if not module_code:
|
|
1205
1107
|
continue
|
|
1206
1108
|
|
|
1207
|
-
# Get module URI
|
|
1109
|
+
# Get module URI using existing ExplorerQuery
|
|
1208
1110
|
try:
|
|
1209
1111
|
module_uri = ExplorerQuery.get_module_url(
|
|
1210
1112
|
scopes_api.session,
|
|
1211
1113
|
module_code=module_code,
|
|
1212
1114
|
release_id=release_id,
|
|
1213
1115
|
)
|
|
1116
|
+
# Remove .json suffix if present (for consistency with expected format)
|
|
1214
1117
|
if module_uri.endswith(".json"):
|
|
1215
1118
|
module_uri = module_uri[:-5]
|
|
1216
1119
|
except Exception:
|
|
1217
1120
|
continue
|
|
1218
1121
|
|
|
1219
|
-
table_code = table_info.get("code")
|
|
1220
|
-
ref_period = get_ref_period(table_code)
|
|
1221
|
-
|
|
1222
1122
|
if module_uri not in external_modules:
|
|
1223
1123
|
external_modules[module_uri] = {
|
|
1224
1124
|
"module_vid": module_vid,
|
|
1225
|
-
"module_version": table_info.get("module_version"), # Already in table_info
|
|
1226
|
-
"ref_period": ref_period,
|
|
1227
1125
|
"tables": {},
|
|
1228
1126
|
"variables": {},
|
|
1229
1127
|
"from_date": None,
|
|
1230
1128
|
"to_date": None
|
|
1231
1129
|
}
|
|
1232
|
-
elif ref_period != "T":
|
|
1233
|
-
# Keep most specific ref_period (non-T takes precedence)
|
|
1234
|
-
external_modules[module_uri]["ref_period"] = ref_period
|
|
1235
1130
|
|
|
1236
|
-
# Add table
|
|
1131
|
+
# Add table - get variables from variables_by_table
|
|
1132
|
+
table_code = table_info.get("code")
|
|
1237
1133
|
if table_code:
|
|
1238
|
-
|
|
1134
|
+
# Look up variables in variables_by_table (handles t prefix)
|
|
1135
|
+
table_variables = variables_by_table.get(table_code, {})
|
|
1136
|
+
if not table_variables:
|
|
1137
|
+
# Try with t prefix
|
|
1138
|
+
table_variables = variables_by_table.get(f"t{table_code}", {})
|
|
1239
1139
|
external_modules[module_uri]["tables"][table_code] = {
|
|
1240
1140
|
"variables": table_variables,
|
|
1241
1141
|
"open_keys": {}
|
|
@@ -1269,16 +1169,11 @@ class ASTGeneratorAPI:
|
|
|
1269
1169
|
# cross_instance_dependencies entry (one per external module)
|
|
1270
1170
|
from_date = data["from_date"]
|
|
1271
1171
|
to_date = data["to_date"]
|
|
1272
|
-
module_entry = {
|
|
1273
|
-
"URI": uri,
|
|
1274
|
-
"ref_period": data["ref_period"]
|
|
1275
|
-
}
|
|
1276
|
-
# Add module_version if available
|
|
1277
|
-
if data["module_version"]:
|
|
1278
|
-
module_entry["module_version"] = data["module_version"]
|
|
1279
|
-
|
|
1280
1172
|
cross_instance_dependencies.append({
|
|
1281
|
-
"modules": [
|
|
1173
|
+
"modules": [{
|
|
1174
|
+
"URI": uri,
|
|
1175
|
+
"ref_period": "T"
|
|
1176
|
+
}],
|
|
1282
1177
|
"affected_operations": [operation_code],
|
|
1283
1178
|
"from_reference_date": str(from_date) if from_date else "",
|
|
1284
1179
|
"to_reference_date": str(to_date) if to_date else ""
|
|
@@ -720,20 +720,16 @@ class ModuleVersion(Base):
|
|
|
720
720
|
release_id: Optional release ID to filter modules by release range
|
|
721
721
|
|
|
722
722
|
Returns:
|
|
723
|
-
pandas DataFrame with columns: ModuleVID, TableVID (as
|
|
724
|
-
|
|
725
|
-
StartReleaseID, EndReleaseID
|
|
723
|
+
pandas DataFrame with columns: ModuleVID, TableVID (as VARIABLE_VID),
|
|
724
|
+
FromReferenceDate, ToReferenceDate, EndReleaseID
|
|
726
725
|
"""
|
|
727
726
|
if not tables_vids:
|
|
728
727
|
return pd.DataFrame(
|
|
729
728
|
columns=[
|
|
730
729
|
"ModuleVID",
|
|
731
730
|
"variable_vid",
|
|
732
|
-
"ModuleCode",
|
|
733
|
-
"VersionNumber",
|
|
734
731
|
"FromReferenceDate",
|
|
735
732
|
"ToReferenceDate",
|
|
736
|
-
"StartReleaseID",
|
|
737
733
|
"EndReleaseID",
|
|
738
734
|
]
|
|
739
735
|
)
|
|
@@ -742,11 +738,8 @@ class ModuleVersion(Base):
|
|
|
742
738
|
session.query(
|
|
743
739
|
cls.modulevid.label("ModuleVID"),
|
|
744
740
|
ModuleVersionComposition.tablevid.label("variable_vid"),
|
|
745
|
-
cls.code.label("ModuleCode"),
|
|
746
|
-
cls.versionnumber.label("VersionNumber"),
|
|
747
741
|
cls.fromreferencedate.label("FromReferenceDate"),
|
|
748
742
|
cls.toreferencedate.label("ToReferenceDate"),
|
|
749
|
-
cls.startreleaseid.label("StartReleaseID"),
|
|
750
743
|
cls.endreleaseid.label("EndReleaseID"),
|
|
751
744
|
)
|
|
752
745
|
.join(
|
|
@@ -766,20 +759,16 @@ class ModuleVersion(Base):
|
|
|
766
759
|
)
|
|
767
760
|
|
|
768
761
|
results = query.all()
|
|
769
|
-
|
|
762
|
+
return pd.DataFrame(
|
|
770
763
|
results,
|
|
771
764
|
columns=[
|
|
772
765
|
"ModuleVID",
|
|
773
766
|
"variable_vid",
|
|
774
|
-
"ModuleCode",
|
|
775
|
-
"VersionNumber",
|
|
776
767
|
"FromReferenceDate",
|
|
777
768
|
"ToReferenceDate",
|
|
778
|
-
"StartReleaseID",
|
|
779
769
|
"EndReleaseID",
|
|
780
770
|
],
|
|
781
771
|
)
|
|
782
|
-
return cls._apply_fallback_for_equal_dates(session, df)
|
|
783
772
|
|
|
784
773
|
@classmethod
|
|
785
774
|
def get_from_table_codes(cls, session, table_codes, release_id=None):
|
|
@@ -801,8 +790,6 @@ class ModuleVersion(Base):
|
|
|
801
790
|
columns=[
|
|
802
791
|
"ModuleVID",
|
|
803
792
|
"variable_vid",
|
|
804
|
-
"ModuleCode",
|
|
805
|
-
"VersionNumber",
|
|
806
793
|
"FromReferenceDate",
|
|
807
794
|
"ToReferenceDate",
|
|
808
795
|
"StartReleaseID",
|
|
@@ -817,8 +804,6 @@ class ModuleVersion(Base):
|
|
|
817
804
|
session.query(
|
|
818
805
|
cls.modulevid.label("ModuleVID"),
|
|
819
806
|
ModuleVersionComposition.tablevid.label("variable_vid"),
|
|
820
|
-
cls.code.label("ModuleCode"),
|
|
821
|
-
cls.versionnumber.label("VersionNumber"),
|
|
822
807
|
cls.fromreferencedate.label("FromReferenceDate"),
|
|
823
808
|
cls.toreferencedate.label("ToReferenceDate"),
|
|
824
809
|
cls.startreleaseid.label("StartReleaseID"),
|
|
@@ -846,13 +831,11 @@ class ModuleVersion(Base):
|
|
|
846
831
|
)
|
|
847
832
|
|
|
848
833
|
results = query.all()
|
|
849
|
-
|
|
834
|
+
return pd.DataFrame(
|
|
850
835
|
results,
|
|
851
836
|
columns=[
|
|
852
837
|
"ModuleVID",
|
|
853
838
|
"variable_vid",
|
|
854
|
-
"ModuleCode",
|
|
855
|
-
"VersionNumber",
|
|
856
839
|
"FromReferenceDate",
|
|
857
840
|
"ToReferenceDate",
|
|
858
841
|
"StartReleaseID",
|
|
@@ -860,7 +843,6 @@ class ModuleVersion(Base):
|
|
|
860
843
|
"TableCode",
|
|
861
844
|
],
|
|
862
845
|
)
|
|
863
|
-
return cls._apply_fallback_for_equal_dates(session, df)
|
|
864
846
|
|
|
865
847
|
@classmethod
|
|
866
848
|
def get_precondition_module_versions(
|
|
@@ -875,21 +857,16 @@ class ModuleVersion(Base):
|
|
|
875
857
|
release_id: Optional release ID to filter modules by release range
|
|
876
858
|
|
|
877
859
|
Returns:
|
|
878
|
-
pandas DataFrame with columns: ModuleVID,
|
|
879
|
-
|
|
880
|
-
StartReleaseID, EndReleaseID, Code
|
|
860
|
+
pandas DataFrame with columns: ModuleVID, VariableVID (as VARIABLE_VID),
|
|
861
|
+
FromReferenceDate, ToReferenceDate, Code
|
|
881
862
|
"""
|
|
882
863
|
if not precondition_items:
|
|
883
864
|
return pd.DataFrame(
|
|
884
865
|
columns=[
|
|
885
866
|
"ModuleVID",
|
|
886
867
|
"variable_vid",
|
|
887
|
-
"ModuleCode",
|
|
888
|
-
"VersionNumber",
|
|
889
868
|
"FromReferenceDate",
|
|
890
869
|
"ToReferenceDate",
|
|
891
|
-
"StartReleaseID",
|
|
892
|
-
"EndReleaseID",
|
|
893
870
|
"Code",
|
|
894
871
|
]
|
|
895
872
|
)
|
|
@@ -898,12 +875,8 @@ class ModuleVersion(Base):
|
|
|
898
875
|
session.query(
|
|
899
876
|
cls.modulevid.label("ModuleVID"),
|
|
900
877
|
VariableVersion.variablevid.label("variable_vid"),
|
|
901
|
-
cls.code.label("ModuleCode"),
|
|
902
|
-
cls.versionnumber.label("VersionNumber"),
|
|
903
878
|
cls.fromreferencedate.label("FromReferenceDate"),
|
|
904
879
|
cls.toreferencedate.label("ToReferenceDate"),
|
|
905
|
-
cls.startreleaseid.label("StartReleaseID"),
|
|
906
|
-
cls.endreleaseid.label("EndReleaseID"),
|
|
907
880
|
VariableVersion.code.label("Code"),
|
|
908
881
|
)
|
|
909
882
|
.join(ModuleParameters, cls.modulevid == ModuleParameters.modulevid)
|
|
@@ -926,21 +899,16 @@ class ModuleVersion(Base):
|
|
|
926
899
|
)
|
|
927
900
|
|
|
928
901
|
results = query.all()
|
|
929
|
-
|
|
902
|
+
return pd.DataFrame(
|
|
930
903
|
results,
|
|
931
904
|
columns=[
|
|
932
905
|
"ModuleVID",
|
|
933
906
|
"variable_vid",
|
|
934
|
-
"ModuleCode",
|
|
935
|
-
"VersionNumber",
|
|
936
907
|
"FromReferenceDate",
|
|
937
908
|
"ToReferenceDate",
|
|
938
|
-
"StartReleaseID",
|
|
939
|
-
"EndReleaseID",
|
|
940
909
|
"Code",
|
|
941
910
|
],
|
|
942
911
|
)
|
|
943
|
-
return cls._apply_fallback_for_equal_dates(session, df)
|
|
944
912
|
|
|
945
913
|
@classmethod
|
|
946
914
|
def get_module_version_by_vid(cls, session, vid):
|
|
@@ -978,170 +946,6 @@ class ModuleVersion(Base):
|
|
|
978
946
|
],
|
|
979
947
|
)
|
|
980
948
|
|
|
981
|
-
@classmethod
|
|
982
|
-
def _apply_fallback_for_equal_dates(cls, session, df, module_vid_col="ModuleVID"):
|
|
983
|
-
"""
|
|
984
|
-
Apply fallback logic for rows where FromReferenceDate == ToReferenceDate.
|
|
985
|
-
|
|
986
|
-
For each such row, find the previous module version (same moduleid,
|
|
987
|
-
highest startreleaseid less than current) and replace module-specific
|
|
988
|
-
columns while preserving association columns (variable_vid, TableCode, Code).
|
|
989
|
-
|
|
990
|
-
Args:
|
|
991
|
-
session: SQLAlchemy session
|
|
992
|
-
df: pandas DataFrame with module version data
|
|
993
|
-
module_vid_col: Column name for module version ID (default: "ModuleVID")
|
|
994
|
-
|
|
995
|
-
Returns:
|
|
996
|
-
pandas DataFrame with fallback logic applied
|
|
997
|
-
"""
|
|
998
|
-
if df.empty:
|
|
999
|
-
return df
|
|
1000
|
-
|
|
1001
|
-
# Identify rows needing fallback
|
|
1002
|
-
mask = df["FromReferenceDate"] == df["ToReferenceDate"]
|
|
1003
|
-
rows_needing_fallback = df[mask]
|
|
1004
|
-
|
|
1005
|
-
if rows_needing_fallback.empty:
|
|
1006
|
-
return df
|
|
1007
|
-
|
|
1008
|
-
# Get unique ModuleVIDs that need fallback
|
|
1009
|
-
module_vids_needing_fallback = (
|
|
1010
|
-
rows_needing_fallback[module_vid_col].unique().tolist()
|
|
1011
|
-
)
|
|
1012
|
-
|
|
1013
|
-
# Batch query: get module info (moduleid, startreleaseid) for affected rows
|
|
1014
|
-
current_modules = (
|
|
1015
|
-
session.query(
|
|
1016
|
-
cls.modulevid,
|
|
1017
|
-
cls.moduleid,
|
|
1018
|
-
cls.startreleaseid,
|
|
1019
|
-
)
|
|
1020
|
-
.filter(cls.modulevid.in_(module_vids_needing_fallback))
|
|
1021
|
-
.all()
|
|
1022
|
-
)
|
|
1023
|
-
|
|
1024
|
-
# Build mapping: current_modulevid -> (moduleid, startreleaseid)
|
|
1025
|
-
current_module_info = {
|
|
1026
|
-
row.modulevid: (row.moduleid, row.startreleaseid) for row in current_modules
|
|
1027
|
-
}
|
|
1028
|
-
|
|
1029
|
-
# Get all potential previous versions for the affected modules
|
|
1030
|
-
unique_module_ids = list(set(info[0] for info in current_module_info.values()))
|
|
1031
|
-
|
|
1032
|
-
previous_versions_query = (
|
|
1033
|
-
session.query(cls)
|
|
1034
|
-
.filter(cls.moduleid.in_(unique_module_ids))
|
|
1035
|
-
.order_by(cls.moduleid, cls.startreleaseid.desc())
|
|
1036
|
-
.all()
|
|
1037
|
-
)
|
|
1038
|
-
|
|
1039
|
-
# Build lookup: moduleid -> list of versions sorted by startreleaseid desc
|
|
1040
|
-
versions_by_moduleid = {}
|
|
1041
|
-
for mv in previous_versions_query:
|
|
1042
|
-
if mv.moduleid not in versions_by_moduleid:
|
|
1043
|
-
versions_by_moduleid[mv.moduleid] = []
|
|
1044
|
-
versions_by_moduleid[mv.moduleid].append(mv)
|
|
1045
|
-
|
|
1046
|
-
# For each current modulevid, find the previous version
|
|
1047
|
-
replacement_map = {} # current_modulevid -> previous_moduleversion
|
|
1048
|
-
for current_vid, (moduleid, current_startreleaseid) in current_module_info.items():
|
|
1049
|
-
versions = versions_by_moduleid.get(moduleid, [])
|
|
1050
|
-
for mv in versions:
|
|
1051
|
-
if mv.startreleaseid < current_startreleaseid:
|
|
1052
|
-
replacement_map[current_vid] = mv
|
|
1053
|
-
break # Already sorted desc, so first match is highest
|
|
1054
|
-
|
|
1055
|
-
# Apply replacements to DataFrame
|
|
1056
|
-
if not replacement_map:
|
|
1057
|
-
return df
|
|
1058
|
-
|
|
1059
|
-
# Create a copy to avoid modifying original
|
|
1060
|
-
result_df = df.copy()
|
|
1061
|
-
|
|
1062
|
-
for idx, row in result_df.iterrows():
|
|
1063
|
-
if row["FromReferenceDate"] == row["ToReferenceDate"]:
|
|
1064
|
-
current_vid = row[module_vid_col]
|
|
1065
|
-
if current_vid in replacement_map:
|
|
1066
|
-
prev_mv = replacement_map[current_vid]
|
|
1067
|
-
result_df.at[idx, "ModuleVID"] = prev_mv.modulevid
|
|
1068
|
-
result_df.at[idx, "ModuleCode"] = prev_mv.code
|
|
1069
|
-
result_df.at[idx, "VersionNumber"] = prev_mv.versionnumber
|
|
1070
|
-
result_df.at[idx, "FromReferenceDate"] = prev_mv.fromreferencedate
|
|
1071
|
-
result_df.at[idx, "ToReferenceDate"] = prev_mv.toreferencedate
|
|
1072
|
-
if "StartReleaseID" in result_df.columns:
|
|
1073
|
-
result_df.at[idx, "StartReleaseID"] = prev_mv.startreleaseid
|
|
1074
|
-
if "EndReleaseID" in result_df.columns:
|
|
1075
|
-
result_df.at[idx, "EndReleaseID"] = prev_mv.endreleaseid
|
|
1076
|
-
|
|
1077
|
-
return result_df
|
|
1078
|
-
|
|
1079
|
-
@classmethod
|
|
1080
|
-
def get_from_release_id(
|
|
1081
|
-
cls, session, release_id, module_id=None, module_code=None
|
|
1082
|
-
):
|
|
1083
|
-
"""
|
|
1084
|
-
Get the module version applicable to a given release for a specific module.
|
|
1085
|
-
|
|
1086
|
-
If the resulting module version has fromreferencedate == toreferencedate,
|
|
1087
|
-
the previous module version for the same module is returned instead.
|
|
1088
|
-
|
|
1089
|
-
Args:
|
|
1090
|
-
session: SQLAlchemy session
|
|
1091
|
-
release_id: The release ID to filter for
|
|
1092
|
-
module_id: Optional module ID (mutually exclusive with module_code)
|
|
1093
|
-
module_code: Optional module code (mutually exclusive with module_id)
|
|
1094
|
-
|
|
1095
|
-
Returns:
|
|
1096
|
-
ModuleVersion instance or None if not found
|
|
1097
|
-
|
|
1098
|
-
Raises:
|
|
1099
|
-
ValueError: If neither module_id nor module_code is provided,
|
|
1100
|
-
or if both are provided
|
|
1101
|
-
"""
|
|
1102
|
-
if module_id is None and module_code is None:
|
|
1103
|
-
raise ValueError("Either module_id or module_code must be provided.")
|
|
1104
|
-
if module_id is not None and module_code is not None:
|
|
1105
|
-
raise ValueError(
|
|
1106
|
-
"Specify only one of module_id or module_code, not both."
|
|
1107
|
-
)
|
|
1108
|
-
|
|
1109
|
-
# Build the base query with release filtering
|
|
1110
|
-
query = session.query(cls).filter(
|
|
1111
|
-
and_(
|
|
1112
|
-
cls.startreleaseid <= release_id,
|
|
1113
|
-
or_(cls.endreleaseid > release_id, cls.endreleaseid.is_(None)),
|
|
1114
|
-
)
|
|
1115
|
-
)
|
|
1116
|
-
|
|
1117
|
-
# Apply module filter
|
|
1118
|
-
if module_id is not None:
|
|
1119
|
-
query = query.filter(cls.moduleid == module_id)
|
|
1120
|
-
else: # module_code
|
|
1121
|
-
query = query.filter(cls.code == module_code)
|
|
1122
|
-
|
|
1123
|
-
module_version = query.first()
|
|
1124
|
-
|
|
1125
|
-
if module_version is None:
|
|
1126
|
-
return None
|
|
1127
|
-
|
|
1128
|
-
# Check if fromreferencedate == toreferencedate
|
|
1129
|
-
if module_version.fromreferencedate == module_version.toreferencedate:
|
|
1130
|
-
# Get the previous module version for the same module
|
|
1131
|
-
prev_query = (
|
|
1132
|
-
session.query(cls)
|
|
1133
|
-
.filter(
|
|
1134
|
-
cls.moduleid == module_version.moduleid,
|
|
1135
|
-
cls.startreleaseid < module_version.startreleaseid,
|
|
1136
|
-
)
|
|
1137
|
-
.order_by(cls.startreleaseid.desc())
|
|
1138
|
-
)
|
|
1139
|
-
prev_module_version = prev_query.first()
|
|
1140
|
-
if prev_module_version:
|
|
1141
|
-
return prev_module_version
|
|
1142
|
-
|
|
1143
|
-
return module_version
|
|
1144
|
-
|
|
1145
949
|
@classmethod
|
|
1146
950
|
def get_last_release(cls, session):
|
|
1147
951
|
"""
|
|
@@ -1315,60 +1119,6 @@ class OperationScope(Base):
|
|
|
1315
1119
|
"OperationScopeComposition", back_populates="operation_scope"
|
|
1316
1120
|
)
|
|
1317
1121
|
|
|
1318
|
-
def to_dict(self):
|
|
1319
|
-
"""
|
|
1320
|
-
Convert the operation scope to a dictionary representation.
|
|
1321
|
-
|
|
1322
|
-
Returns:
|
|
1323
|
-
dict: A dictionary with module codes as keys and module details as values.
|
|
1324
|
-
Format: {
|
|
1325
|
-
"<module_code>": {
|
|
1326
|
-
"module_version_number": <versionnumber>,
|
|
1327
|
-
"from_reference_date": <fromreferencedate>,
|
|
1328
|
-
"to_reference_date": <toreferencedate>
|
|
1329
|
-
},
|
|
1330
|
-
...
|
|
1331
|
-
}
|
|
1332
|
-
"""
|
|
1333
|
-
from sqlalchemy.orm import object_session
|
|
1334
|
-
|
|
1335
|
-
def format_date(date_value):
|
|
1336
|
-
"""Format date to string (YYYY-MM-DD) or None if NaT/None."""
|
|
1337
|
-
if date_value is None:
|
|
1338
|
-
return None
|
|
1339
|
-
if pd.isna(date_value):
|
|
1340
|
-
return None
|
|
1341
|
-
if hasattr(date_value, "strftime"):
|
|
1342
|
-
return date_value.strftime("%Y-%m-%d")
|
|
1343
|
-
return str(date_value)
|
|
1344
|
-
|
|
1345
|
-
result = {}
|
|
1346
|
-
for composition in self.operation_scope_compositions:
|
|
1347
|
-
# For new/proposed scopes, use transient _module_info attribute
|
|
1348
|
-
if hasattr(composition, "_module_info") and composition._module_info:
|
|
1349
|
-
info = composition._module_info
|
|
1350
|
-
result[info["code"]] = {
|
|
1351
|
-
"module_version_number": info["version_number"],
|
|
1352
|
-
"from_reference_date": format_date(info["from_reference_date"]),
|
|
1353
|
-
"to_reference_date": format_date(info["to_reference_date"]),
|
|
1354
|
-
}
|
|
1355
|
-
else:
|
|
1356
|
-
# For existing scopes from DB, use relationship or query
|
|
1357
|
-
module_version = composition.module_version
|
|
1358
|
-
if module_version is None:
|
|
1359
|
-
session = object_session(self)
|
|
1360
|
-
if session is not None:
|
|
1361
|
-
module_version = session.query(ModuleVersion).filter(
|
|
1362
|
-
ModuleVersion.modulevid == composition.modulevid
|
|
1363
|
-
).first()
|
|
1364
|
-
if module_version is not None:
|
|
1365
|
-
result[module_version.code] = {
|
|
1366
|
-
"module_version_number": module_version.versionnumber,
|
|
1367
|
-
"from_reference_date": format_date(module_version.fromreferencedate),
|
|
1368
|
-
"to_reference_date": format_date(module_version.toreferencedate),
|
|
1369
|
-
}
|
|
1370
|
-
return result
|
|
1371
|
-
|
|
1372
1122
|
|
|
1373
1123
|
class OperationScopeComposition(Base):
|
|
1374
1124
|
__tablename__ = "OperationScopeComposition"
|
|
@@ -71,19 +71,9 @@ class OperationScopeService:
|
|
|
71
71
|
if len(modules_info_dataframe) == 1:
|
|
72
72
|
module_vid = modules_vids[0]
|
|
73
73
|
from_date = modules_info_dataframe["FromReferenceDate"].values[0]
|
|
74
|
-
to_date = modules_info_dataframe["ToReferenceDate"].values[0]
|
|
75
|
-
module_code = modules_info_dataframe["ModuleCode"].values[0]
|
|
76
|
-
version_number = modules_info_dataframe["VersionNumber"].values[0]
|
|
77
74
|
operation_scope = self.create_operation_scope(from_date)
|
|
78
75
|
self.create_operation_scope_composition(
|
|
79
|
-
operation_scope=operation_scope,
|
|
80
|
-
module_vid=module_vid,
|
|
81
|
-
module_info={
|
|
82
|
-
"code": module_code,
|
|
83
|
-
"version_number": version_number,
|
|
84
|
-
"from_reference_date": from_date,
|
|
85
|
-
"to_reference_date": to_date,
|
|
86
|
-
},
|
|
76
|
+
operation_scope=operation_scope, module_vid=module_vid
|
|
87
77
|
)
|
|
88
78
|
else:
|
|
89
79
|
intra_modules = []
|
|
@@ -93,10 +83,11 @@ class OperationScopeService:
|
|
|
93
83
|
if table_codes:
|
|
94
84
|
unique_operands_number = len(table_codes) + len(precondition_items)
|
|
95
85
|
|
|
96
|
-
#
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
86
|
+
# Categorize modules by lifecycle: starting vs ending in this release
|
|
87
|
+
starting_modules = (
|
|
88
|
+
{}
|
|
89
|
+
) # Modules that START in this release (replacements)
|
|
90
|
+
ending_modules = {} # Modules that END in this release (being replaced)
|
|
100
91
|
|
|
101
92
|
for module_vid, group_df in modules_info_dataframe.groupby(MODULE_VID):
|
|
102
93
|
table_codes_in_module = (
|
|
@@ -114,55 +105,31 @@ class OperationScopeService:
|
|
|
114
105
|
end_release = group_df["EndReleaseID"].values[0]
|
|
115
106
|
|
|
116
107
|
# Determine if this is a "new" module starting in this release
|
|
108
|
+
# or an "old" module ending in this release
|
|
117
109
|
is_starting = start_release == release_id
|
|
110
|
+
is_ending = end_release == release_id or end_release == float(
|
|
111
|
+
release_id
|
|
112
|
+
)
|
|
118
113
|
|
|
119
114
|
if len(table_codes_in_module) == unique_operands_number:
|
|
120
115
|
# Intra-module: include ALL modules active in the release
|
|
116
|
+
# (don't filter by lifecycle - that's only for cross-module)
|
|
121
117
|
intra_modules.append(module_vid)
|
|
122
118
|
else:
|
|
123
|
-
#
|
|
119
|
+
# For cross-module, group by table code AND lifecycle stage
|
|
120
|
+
target_dict = (
|
|
121
|
+
starting_modules if is_starting else ending_modules
|
|
122
|
+
)
|
|
124
123
|
for table_code in table_codes_in_module:
|
|
125
|
-
if
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
# Second pass: determine if lifecycle separation is needed
|
|
135
|
-
# Only separate if a table code has modules in BOTH starting and ending
|
|
136
|
-
# (indicating a version transition for that table)
|
|
137
|
-
needs_lifecycle_separation = any(
|
|
138
|
-
code in starting_by_code and code in ending_by_code
|
|
139
|
-
for code in set(starting_by_code.keys()) | set(ending_by_code.keys())
|
|
140
|
-
)
|
|
141
|
-
|
|
142
|
-
if needs_lifecycle_separation:
|
|
143
|
-
# Separate into starting and ending scopes
|
|
144
|
-
starting_modules = {}
|
|
145
|
-
ending_modules = {}
|
|
146
|
-
for code, vids in starting_by_code.items():
|
|
147
|
-
starting_modules[code] = vids
|
|
148
|
-
for code, vids in ending_by_code.items():
|
|
149
|
-
ending_modules[code] = vids
|
|
150
|
-
if starting_modules:
|
|
151
|
-
cross_modules["_starting"] = starting_modules
|
|
152
|
-
if ending_modules:
|
|
153
|
-
cross_modules["_ending"] = ending_modules
|
|
154
|
-
else:
|
|
155
|
-
# No version transitions - combine all modules by table code
|
|
156
|
-
all_by_code = {}
|
|
157
|
-
for code, vids in starting_by_code.items():
|
|
158
|
-
if code not in all_by_code:
|
|
159
|
-
all_by_code[code] = []
|
|
160
|
-
all_by_code[code].extend(vids)
|
|
161
|
-
for code, vids in ending_by_code.items():
|
|
162
|
-
if code not in all_by_code:
|
|
163
|
-
all_by_code[code] = []
|
|
164
|
-
all_by_code[code].extend(vids)
|
|
165
|
-
cross_modules = all_by_code
|
|
124
|
+
if table_code not in target_dict:
|
|
125
|
+
target_dict[table_code] = []
|
|
126
|
+
target_dict[table_code].append(module_vid)
|
|
127
|
+
|
|
128
|
+
# Process cross-module scopes separately for each generation
|
|
129
|
+
if starting_modules:
|
|
130
|
+
cross_modules["_starting"] = starting_modules
|
|
131
|
+
if ending_modules:
|
|
132
|
+
cross_modules["_ending"] = ending_modules
|
|
166
133
|
else:
|
|
167
134
|
# Original logic for table VIDs
|
|
168
135
|
unique_operands_number = len(tables_vids) + len(precondition_items)
|
|
@@ -301,21 +268,12 @@ class OperationScopeService:
|
|
|
301
268
|
:param modules_vids: list with module version ids
|
|
302
269
|
"""
|
|
303
270
|
for module_vid in modules_vids:
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
module_code = module_row["ModuleCode"]
|
|
308
|
-
version_number = module_row["VersionNumber"]
|
|
271
|
+
from_date = modules_info[modules_info["ModuleVID"] == module_vid][
|
|
272
|
+
"FromReferenceDate"
|
|
273
|
+
].values[0]
|
|
309
274
|
operation_scope = self.create_operation_scope(from_date)
|
|
310
275
|
self.create_operation_scope_composition(
|
|
311
|
-
operation_scope=operation_scope,
|
|
312
|
-
module_vid=module_vid,
|
|
313
|
-
module_info={
|
|
314
|
-
"code": module_code,
|
|
315
|
-
"version_number": version_number,
|
|
316
|
-
"from_reference_date": from_date,
|
|
317
|
-
"to_reference_date": to_date,
|
|
318
|
-
},
|
|
276
|
+
operation_scope=operation_scope, module_vid=module_vid
|
|
319
277
|
)
|
|
320
278
|
|
|
321
279
|
def process_cross_module(self, cross_modules, modules_dataframe):
|
|
@@ -355,18 +313,8 @@ class OperationScopeService:
|
|
|
355
313
|
operation_scope = self.create_operation_scope(from_submission_date)
|
|
356
314
|
combination = set(combination)
|
|
357
315
|
for module in combination:
|
|
358
|
-
module_row = modules_dataframe[
|
|
359
|
-
modules_dataframe[MODULE_VID] == module
|
|
360
|
-
].iloc[0]
|
|
361
316
|
self.create_operation_scope_composition(
|
|
362
|
-
operation_scope=operation_scope,
|
|
363
|
-
module_vid=module,
|
|
364
|
-
module_info={
|
|
365
|
-
"code": module_row["ModuleCode"],
|
|
366
|
-
"version_number": module_row["VersionNumber"],
|
|
367
|
-
"from_reference_date": module_row[FROM_REFERENCE_DATE],
|
|
368
|
-
"to_reference_date": module_row[TO_REFERENCE_DATE],
|
|
369
|
-
},
|
|
317
|
+
operation_scope=operation_scope, module_vid=module
|
|
370
318
|
)
|
|
371
319
|
|
|
372
320
|
def create_operation_scope(self, submission_date):
|
|
@@ -392,21 +340,17 @@ class OperationScopeService:
|
|
|
392
340
|
self.session.add(operation_scope)
|
|
393
341
|
return operation_scope
|
|
394
342
|
|
|
395
|
-
def create_operation_scope_composition(self, operation_scope, module_vid
|
|
343
|
+
def create_operation_scope_composition(self, operation_scope, module_vid):
|
|
396
344
|
"""
|
|
397
345
|
Method to populate OperationScopeComposition table
|
|
398
346
|
:param operation_scope: Operation scope data
|
|
399
347
|
:param module_vid: Module version id
|
|
400
|
-
:param module_info: Optional dict with module info (code, from_reference_date, to_reference_date)
|
|
401
348
|
"""
|
|
402
349
|
operation_scope_composition = OperationScopeComposition(
|
|
403
350
|
operation_scope=operation_scope,
|
|
404
351
|
modulevid=module_vid,
|
|
405
352
|
rowguid=str(uuid.uuid4()),
|
|
406
353
|
)
|
|
407
|
-
# Store module info as transient attribute for to_dict() access
|
|
408
|
-
if module_info:
|
|
409
|
-
operation_scope_composition._module_info = module_info
|
|
410
354
|
self.session.add(operation_scope_composition)
|
|
411
355
|
|
|
412
356
|
def get_scopes_with_status(self):
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "pydpm_xl"
|
|
3
|
-
version = "0.2.
|
|
3
|
+
version = "0.2.5rc1"
|
|
4
4
|
description = "Python library for DPM-XL data processing and analysis"
|
|
5
5
|
authors = [
|
|
6
6
|
{name = "MeaningfulData S.L.", email = "info@meaningfuldata.eu"}
|
|
@@ -52,7 +52,7 @@ exclude = []
|
|
|
52
52
|
|
|
53
53
|
[tool.poetry]
|
|
54
54
|
name = "pydpm_xl"
|
|
55
|
-
version = "0.2.
|
|
55
|
+
version = "0.2.5rc1"
|
|
56
56
|
description = "Python library for DPM-XL data processing and analysis"
|
|
57
57
|
authors = ["MeaningfulData S.L. <info@meaningfuldata.eu>"]
|
|
58
58
|
readme = "README.md"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pydpm_xl-0.2.5 → pydpm_xl-0.2.5rc1}/py_dpm/dpm_xl/grammar/generated/dpm_xlParserListener.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|