Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
75 commits
Select commit Hold shift + click to select a range
cb90472
updated ontology handler to use g and do more sense checking
jim-smith Mar 16, 2026
d332476
updated acro- generating lookups but not completed
jim-smith Mar 16, 2026
97d612f
Class SDCChecks added, writes FAIR output, tested and working for reg…
jim-smith Apr 2, 2026
e9b13a2
initial crosstab working with counts, [counts,counts], and margins an…
jim-smith Apr 9, 2026
7c5eef7
cleaned up for video
jim-smith Apr 16, 2026
4890d4a
refactoring
jim-smith Apr 16, 2026
cd253c2
updates to tables, ready for testing
jim-smith May 15, 2026
698e53e
refactored, added variable metadata and dep/indep var names to FAIR dict
jim-smith May 21, 2026
46804e3
added support for plots
jim-smith May 22, 2026
837ae1d
linting record and table_utils
jim-smith May 26, 2026
5dd28bb
refactored. ready for testing except Kaplan-meier
jim-smith Jun 3, 2026
80f15ea
committing so I can switch to main
jim-smith Jun 8, 2026
ee61089
linting
jim-smith Jun 8, 2026
f75507d
added in newer mitigation code and rounding
jim-smith Jun 10, 2026
1444715
added preliminary design document
jim-smith Jun 10, 2026
1cba8ad
adding new test file
jim-smith Jun 18, 2026
3daa0a9
update .gitignore
jim-smith Jun 22, 2026
2746442
refactored o produce evidence that can later be used for federated ve…
jim-smith Jun 26, 2026
9d37fc2
update .gitignore
jim-smith Jun 28, 2026
1014140
updated design brief to reflect evidence collection and with new flow…
jim-smith Jun 28, 2026
d093d6e
Fix: Restore missing module attributes and functions for ontology-dri…
JessUWE Jun 29, 2026
cf88b3c
Clean up unused code and functions in acro_tables.py
JessUWE Jun 29, 2026
cca1547
Update acro_tables.py
JessUWE Jun 29, 2026
18a6f77
Add newline at end of acro_tables.py
JessUWE Jun 29, 2026
6d5970b
Fix formatting and remove redundant lines in sdcchecks.py
JessUWE Jun 29, 2026
ae8e45b
Refactor collate_risk_assessments for readability
JessUWE Jun 29, 2026
e90612a
Add newline at end of tablemodeldetails.py
JessUWE Jun 29, 2026
9c3bad9
Add newline at end of utils.py
JessUWE Jun 29, 2026
0211457
Refactor test_ontology_driven.py for clarity and efficiency
JessUWE Jun 29, 2026
861271e
fix: resolve pre-commit code quality issues
JessUWE Jun 29, 2026
6102828
improved flowcharts
jim-smith Jun 29, 2026
f6a6c5b
flowchart layout
jim-smith Jun 29, 2026
dbecae6
feat: implement federated mode (Steps 4 & 5) and fix rounded margins
JessUWE Jul 8, 2026
740cc3c
Update table_utils.py
JessUWE Jul 8, 2026
69c55ea
Fix finalise call in acro.py
JessUWE Jul 8, 2026
af6ddc9
Fix return statement formatting in acro_regression.py
JessUWE Jul 8, 2026
66d18ec
Implement federated evidence storage functionality
JessUWE Jul 8, 2026
791463a
Fix missing newline at end of record.py
JessUWE Jul 8, 2026
2cb2914
Refactor tests and improve assertions
JessUWE Jul 8, 2026
ebfa189
Update federated mode setting in default.yaml
JessUWE Jul 8, 2026
1e3b260
fix: resolve pre-commit code quality issues in test_ontology_driven.p…
JessUWE Jul 8, 2026
218832e
Complete merge of remote changes
JessUWE Jul 8, 2026
3b1128c
fix: re-apply pre-commit code quality fixes after merge
JessUWE Jul 8, 2026
39d5fdd
fix: update test_surv_func assertion and remove invalid ruff config
JessUWE Jul 8, 2026
21b3613
fix: update end-of-file formatting and remove unused imports
JessUWE Jul 8, 2026
ba98d00
Merge branch 'main' into 350-feature-adapt-acro-to-be-ontology-driven
JessUWE Jul 8, 2026
1966afe
style: pre-commit fixes
pre-commit-ci[bot] Jul 8, 2026
8553c34
fix: resolve ruff violations and fix pyproject.toml configuration
JessUWE Jul 8, 2026
da7ccf3
style: pre-commit fixes
pre-commit-ci[bot] Jul 8, 2026
5693149
fix: resolve all pre-commit violations and pre-existing errors
JessUWE Jul 8, 2026
d2e61ab
Merge remote changes: resolve test file conflicts
JessUWE Jul 8, 2026
43bdd09
fix: restore clean test_initial.py without merge conflicts
JessUWE Jul 8, 2026
d90a270
fix: resolve all pre-commit violations and improve code quality
JessUWE Jul 8, 2026
83fa11b
style: pre-commit fixes
pre-commit-ci[bot] Jul 8, 2026
3f8a28c
;2Afeat: implement federated mode (Steps 4 & 5) and fix rounded margins
JessUWE Jul 9, 2026
c4d5823
cleaned files and improve code quality
JessUWE Jul 9, 2026
a40d4c4
clean up the files and improve code quality
JessUWE Jul 9, 2026
6934c59
fix: resolve linting issues and improve code quality
JessUWE Jul 9, 2026
247bf83
style: pre-commit fixes
pre-commit-ci[bot] Jul 9, 2026
889bf13
Refactor evidence accumulation in federated mode
JessUWE Jul 10, 2026
8aa5049
Correct docstring for agg_nk function
JessUWE Jul 10, 2026
a2537d6
Update ontology_handler.py
JessUWE Jul 10, 2026
a0bcf96
Fix docstring formatting in aggregation functions
JessUWE Jul 10, 2026
cba24d7
Improve docstrings and mask_sdc structure
JessUWE Jul 10, 2026
79799e0
Fix docstring formatting in table_utils.py
JessUWE Jul 10, 2026
eab31a2
Update tablemodeldetails.py
JessUWE Jul 10, 2026
699c873
Refactor docstrings for clarity and consistency
JessUWE Jul 10, 2026
9e49802
updated ontology_handler.py to use uris for mitigations
jim-smith Jul 13, 2026
f9ed81c
style: pre-commit fixes
pre-commit-ci[bot] Jul 13, 2026
6a2793b
refactor: regression helpers, evidence interface, logging, test split
JessUWE Jul 14, 2026
762f763
style: pre-commit fixes
pre-commit-ci[bot] Jul 14, 2026
01c42e7
fix: correct None type checks and add test coverage
JessUWE Jul 14, 2026
3959464
Merge branch '350-feature-adapt-acro-to-be-ontology-driven' of https:…
JessUWE Jul 14, 2026
cc32ad8
fix lining and formatting
JessUWE Jul 14, 2026
818138e
style: pre-commit fixes
pre-commit-ci[bot] Jul 14, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -136,13 +136,16 @@ dmypy.json

#Jims Development
stata/various_stata_development__files
playing/

# outputs folder
/notebooks/outputs/
/notebooks/old/

# test output
/test.xlsx
/outputs/

# test datasets that can't b shared
# test datasets that can't be shared
UKDA-7401-stata12/
notebooks/old/test.ipynb
68 changes: 51 additions & 17 deletions acro/acro.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

import yaml

from . import acro_tables
from . import acro_tables, sdcchecks
from .acro_regression import Regression
from .acro_tables import Tables
from .record import Records
Expand Down Expand Up @@ -59,6 +59,7 @@ def __init__(
suppress: bool = False,
mitigation: str | None = None,
round_base: int | None = None,
federated: bool | None = None,
) -> None:
"""Construct a new ACRO object and reads parameters from config.

Expand All @@ -76,36 +77,36 @@ def __init__(
round_base : int, optional
The base to round to when ``mitigation="round"``. Defaults to the
``safe_round_base`` value from the yaml config.
federated : bool, optional
Whether to run in federated mode. When ``True``, checks are skipped
and evidence is written to ``evidence.json`` for a trusted
aggregator. When ``None``, falls back to the yaml config value
(default ``False``).
"""
Tables.__init__(self, suppress=suppress, mitigation=mitigation)
Regression.__init__(self, config)
self._federated_override = federated # applied after yaml is loaded
self.config: dict[str, Any] = {}
path: pathlib.Path = pathlib.Path(__file__).with_name(config + ".yaml")
logger.debug("path: %s", path)
with open(path, encoding="utf-8") as handle:
self.config = yaml.load(handle, Loader=yaml.loader.SafeLoader)

# Tables and Regression each construct their own ``self.results`` so
# they can be used standalone; in the combined ACRO object we want a
# single shared Records instance regardless of init order, so make
# that explicit here.
self.results: Records = Records(
blocked_extensions=self.config.get("blocked_extensions", [])
)

# Preserve the user-requested suppress value alongside the yaml config.
# Tables.suppress is now a property derived from the live mitigation, so
# it can drift over a session (enable_rounding flips mitigation away from
# 'suppress'). Recording the original ask here keeps it available for
# callers that need to re-enforce suppression for disclosive outputs.
self.config["suppress"] = suppress
# set globals needed for aggregation functions
acro_tables.THRESHOLD = self.config["safe_threshold"]
acro_tables.SAFE_PRATIO_P = self.config["safe_pratio_p"]
acro_tables.SAFE_NK_N = self.config["safe_nk_n"]
acro_tables.SAFE_NK_K = self.config["safe_nk_k"]
acro_tables.CHECK_MISSING_VALUES = self.config["check_missing_values"]
acro_tables.ZEROS_ARE_DISCLOSIVE = self.config["zeros_are_disclosive"]
# set globals for survival analysis
acro_tables.SURVIVAL_THRESHOLD = self.config["survival_safe_threshold"]

# set globals and instance state for the round mitigation strategy
acro_tables.SAFE_ROUND_BASE = self.config.get(
"safe_round_base", acro_tables.SAFE_ROUND_BASE
Expand All @@ -117,6 +118,16 @@ def __init__(
logger.info("config: %s", self.config)
logger.info("mitigation: %s (round_base=%s)", self.mitigation, self.round_base)

# make object to handle all the ontology-driven checking
self.sdc_checks = sdcchecks.SDCChecks(self.config)

if self._federated_override is not None:
self.federated: bool = bool(self._federated_override)
else:
self.federated = bool(self.config.get("federated", False))
self.config["federated"] = self.federated
logger.info("federated: %s", self.federated)

def finalise(
self, path: str = "outputs", ext: str = "json", interactive: bool = False
) -> Records | None:
Expand All @@ -140,11 +151,23 @@ def finalise(
if os.path.exists(path):
logger.warning(
"Results file can not be created. "
"Directory %s already exists. Please choose a different directory name.",
"Directory %s already exists. "
"Please choose a different directory name.",
path,
)
return None
self.results.finalise(path, ext, interactive)

if self.federated:
os.makedirs(path, exist_ok=True)
merged_evidence: dict = dict(getattr(self, "_federated_evidence", {}))
evidence_data = self.results.finalise_evidence(path, merged_evidence)
evidence_filename: str = os.path.normpath(f"{path}/evidence.json")
with open(evidence_filename, "w", newline="", encoding="utf-8") as fh:
json.dump(evidence_data, fh, indent=4, sort_keys=False)
logger.info("federated evidence written to: %s", path)
else:
self.results.finalise(path, ext, interactive)

config_filename: str = os.path.normpath(f"{path}/config.json")
try:
with open(config_filename, "w", newline="", encoding="utf-8") as file:
Expand Down Expand Up @@ -238,16 +261,29 @@ def disable_suppression(self) -> None:
self.suppress = False

def enable_rounding(self, base: int | None = None) -> None:
"""Turn rounding on; overwrites any prior suppress=True (not restored on disable_rounding)."""
"""Turn rounding on. Overwrites any prior suppress=True (not restored on disable_rounding)."""
if base is not None:
self.round_base = base
self.mitigation = "round"

def disable_rounding(self) -> None:
"""Turn rounding off; always falls back to mitigation='none' (prior suppress not restored)."""
"""Turn rounding off. Always falls back to mitigation='none' (prior suppress not restored)."""
if self.mitigation == "round":
self.mitigation = "none"

def show_fair_summaries(self) -> str:
"""Print ids and fair summaries for all outputs in session."""
thestr = ""
for uid, value in self.results.results.items():
thestr += uid + "\n"
for key, val in value.fair.items():
if isinstance(val, dict):
for key2, val2 in val.items():
thestr += f"{key2} : {val2}"
else:
thestr += f"{key}:{val}"
return thestr + "\n"


def add_to_acro(src_path: str, dest_path: str = "sdc_results") -> None:
"""Add outputs to an acro object and creates a results file for checking.
Expand All @@ -260,11 +296,9 @@ def add_to_acro(src_path: str, dest_path: str = "sdc_results") -> None:
Name of the folder to save outputs.
"""
acro: ACRO = ACRO()
output_id: int = 0
# add the files from the folder to an acro obj
for file in os.listdir(src_path):
for output_id, file in enumerate(os.listdir(src_path)):
filename: str = os.path.join(src_path, file)
acro.custom_output(filename)
acro.rename_output(f"output_{output_id}", file)
output_id += 1
acro.finalise(dest_path, "json")
Loading