focus-validator 2.0.1__tar.gz → 2.0.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {focus_validator-2.0.1 → focus_validator-2.0.2}/PKG-INFO +1 -1
- {focus_validator-2.0.1 → focus_validator-2.0.2}/focus_validator/config_objects/json_loader.py +3 -3
- focus_validator-2.0.1/focus_validator/rules/model-1.2.json → focus_validator-2.0.2/focus_validator/rules/model-1.2.0.1.json +3 -3
- {focus_validator-2.0.1 → focus_validator-2.0.2}/focus_validator/rules/spec_rules.py +203 -39
- {focus_validator-2.0.1 → focus_validator-2.0.2}/pyproject.toml +1 -1
- {focus_validator-2.0.1 → focus_validator-2.0.2}/LICENSE +0 -0
- {focus_validator-2.0.1 → focus_validator-2.0.2}/README.md +0 -0
- {focus_validator-2.0.1 → focus_validator-2.0.2}/build.py +0 -0
- {focus_validator-2.0.1 → focus_validator-2.0.2}/focus_validator/__init__.py +0 -0
- {focus_validator-2.0.1 → focus_validator-2.0.2}/focus_validator/config/logging.yaml +0 -0
- {focus_validator-2.0.1 → focus_validator-2.0.2}/focus_validator/config_objects/__init__.py +0 -0
- {focus_validator-2.0.1 → focus_validator-2.0.2}/focus_validator/config_objects/common.py +0 -0
- {focus_validator-2.0.1 → focus_validator-2.0.2}/focus_validator/config_objects/focus_to_duckdb_converter.py +0 -0
- {focus_validator-2.0.1 → focus_validator-2.0.2}/focus_validator/config_objects/plan_builder.py +0 -0
- {focus_validator-2.0.1 → focus_validator-2.0.2}/focus_validator/config_objects/rule.py +0 -0
- {focus_validator-2.0.1 → focus_validator-2.0.2}/focus_validator/config_objects/rule_dependency_resolver.py +0 -0
- {focus_validator-2.0.1 → focus_validator-2.0.2}/focus_validator/data_loaders/__init__.py +0 -0
- {focus_validator-2.0.1 → focus_validator-2.0.2}/focus_validator/data_loaders/csv_data_loader.py +0 -0
- {focus_validator-2.0.1 → focus_validator-2.0.2}/focus_validator/data_loaders/csv_data_loader_pandas_backup.py +0 -0
- {focus_validator-2.0.1 → focus_validator-2.0.2}/focus_validator/data_loaders/data_loader.py +0 -0
- {focus_validator-2.0.1 → focus_validator-2.0.2}/focus_validator/data_loaders/parquet_data_loader.py +0 -0
- {focus_validator-2.0.1 → focus_validator-2.0.2}/focus_validator/exceptions.py +0 -0
- {focus_validator-2.0.1 → focus_validator-2.0.2}/focus_validator/main.py +0 -0
- {focus_validator-2.0.1 → focus_validator-2.0.2}/focus_validator/outputter/__init__.py +0 -0
- {focus_validator-2.0.1 → focus_validator-2.0.2}/focus_validator/outputter/outputter.py +0 -0
- {focus_validator-2.0.1 → focus_validator-2.0.2}/focus_validator/outputter/outputter_console.py +0 -0
- {focus_validator-2.0.1 → focus_validator-2.0.2}/focus_validator/outputter/outputter_unittest.py +0 -0
- {focus_validator-2.0.1 → focus_validator-2.0.2}/focus_validator/outputter/outputter_validation_graph.py +0 -0
- {focus_validator-2.0.1 → focus_validator-2.0.2}/focus_validator/outputter/outputter_web.py +0 -0
- {focus_validator-2.0.1 → focus_validator-2.0.2}/focus_validator/rules/__init__.py +0 -0
- {focus_validator-2.0.1 → focus_validator-2.0.2}/focus_validator/rules/currency_codes.csv +0 -0
- {focus_validator-2.0.1 → focus_validator-2.0.2}/focus_validator/utils/__init__.py +0 -0
- {focus_validator-2.0.1 → focus_validator-2.0.2}/focus_validator/utils/download_currency_codes.py +0 -0
- {focus_validator-2.0.1 → focus_validator-2.0.2}/focus_validator/utils/performance_logging.py +0 -0
- {focus_validator-2.0.1 → focus_validator-2.0.2}/focus_validator/validator.py +0 -0
{focus_validator-2.0.1 → focus_validator-2.0.2}/focus_validator/config_objects/json_loader.py
RENAMED
|
@@ -129,14 +129,14 @@ class JsonLoader:
|
|
|
129
129
|
focus_dataset: Optional[str] = "",
|
|
130
130
|
filter_rules: Optional[str] = None,
|
|
131
131
|
applicability_criteria_list: Optional[List[str]] = None,
|
|
132
|
-
) -> Tuple[ValidationPlan, Dict[str, str]]:
|
|
132
|
+
) -> Tuple[ValidationPlan, Dict[str, str], Dict[str, Any]]:
|
|
133
133
|
"""
|
|
134
134
|
Load CR JSON, build the dependency graph with RuleDependencyResolver,
|
|
135
135
|
select relevant rules, and return both an execution-ready ValidationPlan
|
|
136
136
|
(parents preserved, topo-ordered nodes + layers) and a column type mapping.
|
|
137
137
|
|
|
138
138
|
Returns:
|
|
139
|
-
Tuple of (ValidationPlan, Dict[column_name, pandas_dtype])
|
|
139
|
+
Tuple of (ValidationPlan, Dict[column_name, pandas_dtype], Dict[model_data])
|
|
140
140
|
"""
|
|
141
141
|
model_data = JsonLoader.load_json_rules(json_rule_file)
|
|
142
142
|
|
|
@@ -227,4 +227,4 @@ class JsonLoader:
|
|
|
227
227
|
exec_ctx=None, # supply a runtime context later if you want gated edges
|
|
228
228
|
)
|
|
229
229
|
|
|
230
|
-
return val_plan, column_types
|
|
230
|
+
return val_plan, column_types, model_data
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"Details": {
|
|
3
|
-
"ModelVersion": "1.2",
|
|
3
|
+
"ModelVersion": "1.2.0.1",
|
|
4
4
|
"FOCUSVersion": "1.2"
|
|
5
5
|
},
|
|
6
6
|
"ApplicabilityCriteria": {
|
|
@@ -9713,7 +9713,7 @@
|
|
|
9713
9713
|
},
|
|
9714
9714
|
"Condition": {
|
|
9715
9715
|
"CheckFunction": "CheckNotValue",
|
|
9716
|
-
"
|
|
9716
|
+
"ColumnName": "ResourceId",
|
|
9717
9717
|
"Value": null
|
|
9718
9718
|
},
|
|
9719
9719
|
"Dependencies": [
|
|
@@ -12674,7 +12674,7 @@
|
|
|
12674
12674
|
"MustSatisfy": "CommitmentDiscountStatus MUST be null when CommitmentDiscountId is null.",
|
|
12675
12675
|
"Keyword": "MUST",
|
|
12676
12676
|
"Requirement": {
|
|
12677
|
-
"CheckFunction": "
|
|
12677
|
+
"CheckFunction": "CheckValue",
|
|
12678
12678
|
"ColumnName": "CommitmentDiscountStatus",
|
|
12679
12679
|
"Value": null
|
|
12680
12680
|
},
|
|
@@ -55,8 +55,12 @@ class SpecRules:
|
|
|
55
55
|
):
|
|
56
56
|
self.rule_set_path = rule_set_path
|
|
57
57
|
self.rules_file_prefix = rules_file_prefix
|
|
58
|
-
self.rules_version =
|
|
59
|
-
|
|
58
|
+
self.rules_version = (
|
|
59
|
+
rules_version # Will be overridden by FOCUSVersion from JSON Details
|
|
60
|
+
)
|
|
61
|
+
self.model_version = (
|
|
62
|
+
"Unknown" # Will be loaded from ModelVersion in JSON Details
|
|
63
|
+
)
|
|
60
64
|
self.rules_file_suffix = rules_file_suffix
|
|
61
65
|
self.focus_dataset = focus_dataset
|
|
62
66
|
self.filter_rules = filter_rules
|
|
@@ -82,9 +86,18 @@ class SpecRules:
|
|
|
82
86
|
self.local_supported_versions,
|
|
83
87
|
)
|
|
84
88
|
self.remote_versions = {}
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
89
|
+
|
|
90
|
+
# Build dict of local versions for semantic matching
|
|
91
|
+
local_versions_dict = {
|
|
92
|
+
v: {"source": "local"} for v in self.local_supported_versions
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
# Try semantic version matching for local versions first
|
|
96
|
+
matched_version = self._find_best_version_match(
|
|
97
|
+
self.rules_version, local_versions_dict
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
if self.rules_block_remote_download and matched_version is None:
|
|
88
101
|
self.log.error(
|
|
89
102
|
"Version %s not found in local versions and remote download blocked",
|
|
90
103
|
self.rules_version,
|
|
@@ -92,14 +105,11 @@ class SpecRules:
|
|
|
92
105
|
raise UnsupportedVersion(
|
|
93
106
|
f"FOCUS version {self.rules_version} not supported. Supported versions: local {self.local_supported_versions}"
|
|
94
107
|
)
|
|
95
|
-
elif
|
|
96
|
-
self.rules_force_remote_download
|
|
97
|
-
or self.rules_version not in self.local_supported_versions
|
|
98
|
-
):
|
|
108
|
+
elif self.rules_force_remote_download or matched_version is None:
|
|
99
109
|
self.log.info(
|
|
100
110
|
"Remote rule download needed (force: %s, version available locally: %s)",
|
|
101
111
|
self.rules_force_remote_download,
|
|
102
|
-
|
|
112
|
+
matched_version is not None,
|
|
103
113
|
)
|
|
104
114
|
|
|
105
115
|
self.log.debug("Fetching remote supported versions...")
|
|
@@ -110,7 +120,12 @@ class SpecRules:
|
|
|
110
120
|
self.remote_supported_versions,
|
|
111
121
|
)
|
|
112
122
|
|
|
113
|
-
|
|
123
|
+
# Try semantic version matching for remote versions
|
|
124
|
+
matched_version = self._find_best_version_match(
|
|
125
|
+
self.rules_version, self.remote_versions
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
if matched_version is None:
|
|
114
129
|
self.log.error(
|
|
115
130
|
"Version %s not found in remote versions", self.rules_version
|
|
116
131
|
)
|
|
@@ -119,13 +134,19 @@ class SpecRules:
|
|
|
119
134
|
)
|
|
120
135
|
else:
|
|
121
136
|
self.log.info(
|
|
122
|
-
"
|
|
137
|
+
"Matched requested version %s to %s from remote",
|
|
138
|
+
self.rules_version,
|
|
139
|
+
matched_version,
|
|
123
140
|
)
|
|
124
|
-
download_url = self.remote_versions[
|
|
141
|
+
download_url = self.remote_versions[matched_version][
|
|
125
142
|
"asset_browser_download_url"
|
|
126
143
|
]
|
|
144
|
+
filename = self.remote_versions[matched_version]["filename"]
|
|
127
145
|
self.log.debug("Download URL: %s", download_url)
|
|
128
146
|
|
|
147
|
+
# Update json_rule_file path to use matched version filename
|
|
148
|
+
self.json_rule_file = os.path.join(self.rule_set_path, filename)
|
|
149
|
+
|
|
129
150
|
if not self.download_remote_version(
|
|
130
151
|
remote_url=download_url, save_path=self.json_rule_file
|
|
131
152
|
):
|
|
@@ -135,6 +156,18 @@ class SpecRules:
|
|
|
135
156
|
)
|
|
136
157
|
else:
|
|
137
158
|
self.log.info("Remote rules downloaded successfully")
|
|
159
|
+
else:
|
|
160
|
+
# Using local version
|
|
161
|
+
self.log.info(
|
|
162
|
+
"Matched requested version %s to %s from local files",
|
|
163
|
+
self.rules_version,
|
|
164
|
+
matched_version,
|
|
165
|
+
)
|
|
166
|
+
# Update json_rule_file path to use matched version
|
|
167
|
+
self.json_rule_file = os.path.join(
|
|
168
|
+
self.rule_set_path,
|
|
169
|
+
f"{self.rules_file_prefix}{matched_version}{self.rules_file_suffix}",
|
|
170
|
+
)
|
|
138
171
|
self.rules = {}
|
|
139
172
|
self.column_namespace = column_namespace
|
|
140
173
|
self.json_rules = {}
|
|
@@ -143,7 +176,12 @@ class SpecRules:
|
|
|
143
176
|
self.column_types = {}
|
|
144
177
|
|
|
145
178
|
def supported_local_versions(self) -> List[str]:
|
|
146
|
-
"""Return list of versions from files in rule_set_path.
|
|
179
|
+
"""Return list of highest versions from files in rule_set_path.
|
|
180
|
+
|
|
181
|
+
Only returns the highest semantic version for each major.minor prefix.
|
|
182
|
+
For example, if both model-1.2.json and model-1.2.0.1.json exist,
|
|
183
|
+
only 1.2.0.1 will be returned.
|
|
184
|
+
"""
|
|
147
185
|
versions = []
|
|
148
186
|
for filename in os.listdir(self.rule_set_path):
|
|
149
187
|
if filename.startswith(self.rules_file_prefix) and filename.endswith(
|
|
@@ -154,7 +192,96 @@ class SpecRules:
|
|
|
154
192
|
len(self.rules_file_prefix) : -len(self.rules_file_suffix)
|
|
155
193
|
]
|
|
156
194
|
versions.append(version)
|
|
157
|
-
return versions
|
|
195
|
+
return self._filter_to_highest_versions(versions)
|
|
196
|
+
|
|
197
|
+
def _parse_version_from_filename(self, filename: str) -> Optional[str]:
|
|
198
|
+
"""Extract version from filename like 'model-1.2.0.1.json' -> '1.2.0.1'."""
|
|
199
|
+
if not filename.startswith(self.rules_file_prefix) or not filename.endswith(
|
|
200
|
+
self.rules_file_suffix
|
|
201
|
+
):
|
|
202
|
+
return None
|
|
203
|
+
version = filename[len(self.rules_file_prefix) : -len(self.rules_file_suffix)]
|
|
204
|
+
return version if version else None
|
|
205
|
+
|
|
206
|
+
def _parse_version_tuple(self, version: str) -> Tuple[int, ...]:
|
|
207
|
+
"""Convert version string '1.2.0.1' to tuple (1, 2, 0, 1) for comparison."""
|
|
208
|
+
try:
|
|
209
|
+
return tuple(int(x) for x in version.split("."))
|
|
210
|
+
except (ValueError, AttributeError):
|
|
211
|
+
self.log.warning(
|
|
212
|
+
"Malformed version string '%s' - cannot parse as semantic version. "
|
|
213
|
+
"Will sort to bottom. Check for corrupted model filenames.",
|
|
214
|
+
version,
|
|
215
|
+
)
|
|
216
|
+
return (0,) # Fallback for invalid versions
|
|
217
|
+
|
|
218
|
+
def _find_best_version_match(
|
|
219
|
+
self, requested: str, available: Dict[str, Dict[str, Any]]
|
|
220
|
+
) -> Optional[str]:
|
|
221
|
+
"""
|
|
222
|
+
Find the best (highest) semantic version matching the requested prefix.
|
|
223
|
+
|
|
224
|
+
Args:
|
|
225
|
+
requested: Version prefix like '1.2' or '1.3'
|
|
226
|
+
available: Dict of available versions with metadata
|
|
227
|
+
|
|
228
|
+
Returns:
|
|
229
|
+
Best matching version string, or None if no match found
|
|
230
|
+
"""
|
|
231
|
+
# Filter versions that match the requested prefix
|
|
232
|
+
matching = [
|
|
233
|
+
v
|
|
234
|
+
for v in available.keys()
|
|
235
|
+
if v.startswith(requested + ".") or v == requested
|
|
236
|
+
]
|
|
237
|
+
|
|
238
|
+
if not matching:
|
|
239
|
+
return None
|
|
240
|
+
|
|
241
|
+
# Sort by semantic version (highest first)
|
|
242
|
+
matching.sort(key=self._parse_version_tuple, reverse=True)
|
|
243
|
+
return matching[0]
|
|
244
|
+
|
|
245
|
+
def _filter_to_highest_versions(self, versions: List[str]) -> List[str]:
|
|
246
|
+
"""
|
|
247
|
+
Filter version list to only include the highest version for each major.minor prefix.
|
|
248
|
+
|
|
249
|
+
For example, given ['1.2', '1.2.0', '1.2.0.1', '1.3', '1.3.0.1']:
|
|
250
|
+
Returns: ['1.2.0.1', '1.3.0.1']
|
|
251
|
+
|
|
252
|
+
This ensures the supported versions list only shows versions that would
|
|
253
|
+
actually be used (since semantic matching always picks the highest).
|
|
254
|
+
|
|
255
|
+
Args:
|
|
256
|
+
versions: List of version strings
|
|
257
|
+
|
|
258
|
+
Returns:
|
|
259
|
+
Filtered list with only highest version per major.minor
|
|
260
|
+
"""
|
|
261
|
+
# Group versions by major.minor prefix
|
|
262
|
+
prefix_groups: Dict[str, List[str]] = {}
|
|
263
|
+
for v in versions:
|
|
264
|
+
# Extract major.minor (first two components)
|
|
265
|
+
parts = v.split(".")
|
|
266
|
+
if len(parts) >= 2:
|
|
267
|
+
prefix = f"{parts[0]}.{parts[1]}"
|
|
268
|
+
else:
|
|
269
|
+
prefix = v
|
|
270
|
+
|
|
271
|
+
if prefix not in prefix_groups:
|
|
272
|
+
prefix_groups[prefix] = []
|
|
273
|
+
prefix_groups[prefix].append(v)
|
|
274
|
+
|
|
275
|
+
# For each prefix, keep only the highest version
|
|
276
|
+
highest_versions = []
|
|
277
|
+
for prefix, group_versions in prefix_groups.items():
|
|
278
|
+
# Sort by semantic version and take the highest
|
|
279
|
+
group_versions.sort(key=self._parse_version_tuple, reverse=True)
|
|
280
|
+
highest_versions.append(group_versions[0])
|
|
281
|
+
|
|
282
|
+
# Return sorted by semantic version
|
|
283
|
+
highest_versions.sort(key=self._parse_version_tuple)
|
|
284
|
+
return highest_versions
|
|
158
285
|
|
|
159
286
|
def find_release_assets(
|
|
160
287
|
self,
|
|
@@ -164,22 +291,29 @@ class SpecRules:
|
|
|
164
291
|
timeout: float = 15.0,
|
|
165
292
|
) -> Dict[str, Dict[str, Any]]:
|
|
166
293
|
"""
|
|
167
|
-
Search GitHub releases for
|
|
168
|
-
self.rules_files_prefix and end with self.rules_files_suffix.
|
|
294
|
+
Search GitHub releases for ALL model files across all releases.
|
|
169
295
|
|
|
170
|
-
Returns a dict
|
|
296
|
+
Returns a dict keyed by model VERSION (not release tag):
|
|
171
297
|
{
|
|
172
|
-
"
|
|
173
|
-
"release_tag": "v1.
|
|
174
|
-
"
|
|
298
|
+
"1.2.0.1": {
|
|
299
|
+
"release_tag": "v1.3",
|
|
300
|
+
"filename": "model-1.2.0.1.json",
|
|
301
|
+
"asset_browser_download_url": "<url>"
|
|
302
|
+
},
|
|
303
|
+
"1.3": {
|
|
304
|
+
"release_tag": "v1.3",
|
|
305
|
+
"filename": "model-1.3.json",
|
|
306
|
+
"asset_browser_download_url": "<url>"
|
|
175
307
|
}
|
|
176
308
|
}
|
|
309
|
+
|
|
310
|
+
When multiple releases contain the same model version, the first (newest)
|
|
311
|
+
release wins, since GitHub API returns releases in reverse chronological order.
|
|
177
312
|
"""
|
|
178
313
|
session = requests.Session()
|
|
179
314
|
headers = {
|
|
180
315
|
"Accept": "application/vnd.github+json",
|
|
181
316
|
"X-GitHub-Api-Version": "2022-11-28",
|
|
182
|
-
# Optional but helps with routing
|
|
183
317
|
"User-Agent": "focus-validator/asset-scan",
|
|
184
318
|
}
|
|
185
319
|
|
|
@@ -194,7 +328,6 @@ class SpecRules:
|
|
|
194
328
|
if resp.status_code == 401:
|
|
195
329
|
raise PermissionError("Unauthorized (bad or missing token)")
|
|
196
330
|
if resp.status_code == 403:
|
|
197
|
-
# Could be secondary rate limiting or scope issue
|
|
198
331
|
raise RuntimeError(f"Forbidden / rate limited: {resp.text}")
|
|
199
332
|
resp.raise_for_status()
|
|
200
333
|
|
|
@@ -209,24 +342,41 @@ class SpecRules:
|
|
|
209
342
|
if not self.allow_prerelease_releases and rel.get("prerelease"):
|
|
210
343
|
continue
|
|
211
344
|
|
|
345
|
+
release_tag = rel.get("tag_name", "")
|
|
212
346
|
assets = rel.get("assets", []) or []
|
|
347
|
+
|
|
348
|
+
# Scan ALL model files in this release
|
|
213
349
|
for asset in assets:
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
350
|
+
filename = asset.get("name", "")
|
|
351
|
+
model_version = self._parse_version_from_filename(filename)
|
|
352
|
+
|
|
353
|
+
if model_version and model_version not in results:
|
|
354
|
+
# First match wins = newest release, since GitHub API
|
|
355
|
+
# returns releases newest-first
|
|
356
|
+
results[model_version] = {
|
|
357
|
+
"release_tag": release_tag,
|
|
358
|
+
"filename": filename,
|
|
220
359
|
"asset_browser_download_url": asset.get(
|
|
221
360
|
"browser_download_url"
|
|
222
361
|
),
|
|
223
362
|
}
|
|
363
|
+
|
|
224
364
|
page += 1
|
|
225
365
|
|
|
366
|
+
self.log.debug(
|
|
367
|
+
"Found %d model versions across releases: %s",
|
|
368
|
+
len(results),
|
|
369
|
+
list(results.keys()),
|
|
370
|
+
)
|
|
226
371
|
return results
|
|
227
372
|
|
|
228
373
|
def supported_remote_versions(self) -> List[str]:
|
|
229
|
-
"""Return list of versions from remote source.
|
|
374
|
+
"""Return list of highest versions from remote source.
|
|
375
|
+
|
|
376
|
+
Only returns the highest semantic version for each major.minor prefix.
|
|
377
|
+
For example, if both 1.2 and 1.2.0.1 are available remotely,
|
|
378
|
+
only 1.2.0.1 will be returned since that's what semantic matching would use.
|
|
379
|
+
"""
|
|
230
380
|
# Respect block download setting
|
|
231
381
|
if self.rules_block_remote_download:
|
|
232
382
|
self.log.debug(
|
|
@@ -236,7 +386,8 @@ class SpecRules:
|
|
|
236
386
|
|
|
237
387
|
# Implement logic to fetch supported remote versions
|
|
238
388
|
self.remote_versions = self.find_release_assets()
|
|
239
|
-
|
|
389
|
+
all_versions = list(self.remote_versions.keys())
|
|
390
|
+
return self._filter_to_highest_versions(all_versions)
|
|
240
391
|
|
|
241
392
|
def download_remote_version(self, remote_url: str, save_path: str) -> bool:
|
|
242
393
|
"""Download the file from remote_url and save it to save_path.
|
|
@@ -259,21 +410,34 @@ class SpecRules:
|
|
|
259
410
|
self.load_rules()
|
|
260
411
|
|
|
261
412
|
def load_rules(self) -> ValidationPlan:
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
413
|
+
# Load rules and parse JSON once
|
|
414
|
+
val_plan, column_types, model_data = (
|
|
415
|
+
JsonLoader.load_json_rules_with_dependencies_and_types(
|
|
416
|
+
json_rule_file=self.json_rule_file,
|
|
417
|
+
focus_dataset=self.focus_dataset,
|
|
418
|
+
filter_rules=self.filter_rules,
|
|
419
|
+
applicability_criteria_list=self.applicability_criteria_list,
|
|
420
|
+
)
|
|
267
421
|
)
|
|
268
422
|
|
|
269
|
-
#
|
|
423
|
+
# Extract FOCUS version and model version from Details (already parsed above)
|
|
270
424
|
try:
|
|
271
|
-
model_data = JsonLoader.load_json_rules(self.json_rule_file)
|
|
272
425
|
details = model_data.get("Details", {})
|
|
426
|
+
# Override rules_version with FOCUSVersion from model file
|
|
427
|
+
focus_version = details.get("FOCUSVersion", None)
|
|
428
|
+
if focus_version:
|
|
429
|
+
self.rules_version = focus_version
|
|
430
|
+
self.log.debug("Loaded FOCUS version: %s", self.rules_version)
|
|
431
|
+
else:
|
|
432
|
+
self.log.warning(
|
|
433
|
+
"FOCUSVersion not found in Details, using requested version: %s",
|
|
434
|
+
self.rules_version,
|
|
435
|
+
)
|
|
436
|
+
# Load model version
|
|
273
437
|
self.model_version = details.get("ModelVersion", "Unknown")
|
|
274
438
|
self.log.debug("Loaded model version: %s", self.model_version)
|
|
275
439
|
except Exception as e:
|
|
276
|
-
self.log.warning("Failed to load model version: %s", e)
|
|
440
|
+
self.log.warning("Failed to load FOCUS/model version: %s", e)
|
|
277
441
|
self.model_version = "Unknown"
|
|
278
442
|
|
|
279
443
|
self.plan = val_plan
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{focus_validator-2.0.1 → focus_validator-2.0.2}/focus_validator/config_objects/plan_builder.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{focus_validator-2.0.1 → focus_validator-2.0.2}/focus_validator/data_loaders/csv_data_loader.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{focus_validator-2.0.1 → focus_validator-2.0.2}/focus_validator/data_loaders/parquet_data_loader.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{focus_validator-2.0.1 → focus_validator-2.0.2}/focus_validator/outputter/outputter_console.py
RENAMED
|
File without changes
|
{focus_validator-2.0.1 → focus_validator-2.0.2}/focus_validator/outputter/outputter_unittest.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{focus_validator-2.0.1 → focus_validator-2.0.2}/focus_validator/utils/download_currency_codes.py
RENAMED
|
File without changes
|
{focus_validator-2.0.1 → focus_validator-2.0.2}/focus_validator/utils/performance_logging.py
RENAMED
|
File without changes
|
|
File without changes
|