Skip to content
Draft
Show file tree
Hide file tree
Changes from 50 commits
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
fada301
design: multi-file batch analysis (phase 1)
jeanbez Jun 4, 2026
279d9c5
spec: incorporate sub-agent review findings
jeanbez Jun 4, 2026
30fa1cb
spec: finalize combined-summary design (phase 1)
jeanbez Jun 4, 2026
705156b
spec: sort batch overview by name; fetch Globus counts async
jeanbez Jun 4, 2026
5d5dfa9
plan: multi-file batch analysis implementation
jeanbez Jun 4, 2026
76abdfe
feat(core): infer reader type from file extension
jeanbez Jun 4, 2026
2fa3395
feat(core): summarize_files batch structural overview
jeanbez Jun 4, 2026
03277b5
feat(web): server-side file list + active-file shim
jeanbez Jun 4, 2026
b3baf37
fix(web): key metric cache by active file id
jeanbez Jun 4, 2026
2338ae1
feat(web): files blueprint (list/activate/remove)
jeanbez Jun 4, 2026
7203c4f
feat(web): /files/summary batch overview endpoint
jeanbez Jun 4, 2026
bc9ffdb
feat(web): accept multiple files on upload, infer types
jeanbez Jun 4, 2026
2973c45
fix(web): don't wipe active Globus file on local existence check
jeanbez Jun 4, 2026
b62621e
feat(web): upload size cap + AIDRIN_MAX_UPLOAD_FILES=50 + 413
jeanbez Jun 5, 2026
e55979e
feat(web): Globus selections feed the shared file list
jeanbez Jun 5, 2026
3c6b53b
feat(ui): multi-file AJAX upload; drop manual type dropdown
jeanbez Jun 5, 2026
f4e6537
feat(ui): file switcher sidebar
jeanbez Jun 5, 2026
0ea868a
feat(ui): batch overview landing view with async Globus counts
jeanbez Jun 5, 2026
f00b47e
fix(ui): load active-file metrics + switcher in batch and globus modes
jeanbez Jun 5, 2026
9dc7fc5
fix(web): reject unsupported file types at upload with a clear message
jeanbez Jun 5, 2026
17fdd5b
spec: top-bar searchable file selector replaces sidebar switcher
jeanbez Jun 5, 2026
50655f4
feat(ui): top-bar searchable file selector; remove sidebar switcher
jeanbez Jun 5, 2026
af50190
feat(ui): Batch Overview menu entry, uppercase title, overview-style …
jeanbez Jun 5, 2026
0b4e11a
feat(ui): drop Status column from batch overview table
jeanbez Jun 5, 2026
f43e0bb
feat: add numerical/categorical feature counts to batch overview
jeanbez Jun 5, 2026
d72401d
fix(web): persist the uploaded-file list to disk
jeanbez Jun 5, 2026
a57d41a
chore: gitignore runtime data dir and generated custom metrics
jeanbez Jun 5, 2026
01d0d66
fix(web): custom-metric editor never loads empty; atomic writes
jeanbez Jun 5, 2026
9098db5
fix(web): write runtime custom-metric files outside the package
jeanbez Jun 5, 2026
09e711c
fix(ui): refresh CodeMirror when re-showing custom-metrics panel
jeanbez Jun 5, 2026
f4bf9b7
style(ui): Batch Overview title in normal case to match other panels
jeanbez Jun 5, 2026
b8b5298
fix(web): metric routes return clean error when the active file is un…
jeanbez Jun 5, 2026
191983c
feat(ui): switching a file lands on Data Overview; batch overview onl…
jeanbez Jun 5, 2026
f7d1d9b
fix: review findings — clear_file leak, test isolation, dead stub, dr…
jeanbez Jun 5, 2026
796676b
feat(web): enable custom metrics for all supported file types
jeanbez Jun 5, 2026
8af5177
fix: batch-overview landing regression + duplicate custom-metric exec…
jeanbez Jun 8, 2026
422c7b5
feat(globus): add a remote file OR a whole directory; infer type from…
jeanbez Jun 8, 2026
2236ae7
fix(globus): list remote files via remote_metric_runner instead of a …
jeanbez Jun 8, 2026
378cb05
fix(globus): hash function source in the registration cache key
jeanbez Jun 8, 2026
34e3d33
fix(globus): retry submissions on transient 409 'endpoint in use'
jeanbez Jun 8, 2026
df16310
chore(globus): log whether the loaded remote_metric_runner supports _…
jeanbez Jun 8, 2026
fd7f19d
fix(globus): compute remote batch overview inline, not via aidrin.sum…
jeanbez Jun 8, 2026
6c35b06
fix(globus): surface why batch stats fail + allow retry of failed ent…
jeanbez Jun 8, 2026
7f529f3
fix(globus): clearer stats-failure reasons (stale build, per-file rea…
jeanbez Jun 8, 2026
ce1aab2
chore(globus): log the __summarize_files__ result (ok count + sample …
jeanbez Jun 8, 2026
b03d9f9
fix(globus): also retry on transient 'Failed to start / SystemExit 73…
jeanbez Jun 8, 2026
9bb3e1d
fix(privacy): clear error for empty quasi-identifiers (k-anon 'No gro…
jeanbez Jun 8, 2026
a73ff51
fix(globus): clear message for privacy metrics with no remote handler
jeanbez Jun 8, 2026
7497963
Merge remote-tracking branch 'origin/develop' into multi-file-analysis
jeanbez Jul 1, 2026
3706d43
chore: gitignore local session artifacts (AIDRIN-gtag, claude.session)
jeanbez Jul 1, 2026
905b714
Merge remote-tracking branch 'origin/develop' into multi-file-analysis
jeanbez Jul 2, 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
11 changes: 11 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -185,3 +185,14 @@ examples/*_vector_store/

dump.rdb
celerybeat-schedule.db

# Runtime data (uploads, persisted file lists, scratch test data)
data/

# Runtime-generated custom metrics & remedy data (package keeps __init__.py + base_dr.py)
aidrin/custom_metrics/customDR_*.py
aidrin/custom_metrics/remedy_data/

# session/local artifacts
AIDRIN-gtag/
claude.session
12 changes: 12 additions & 0 deletions aidrin/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -334,8 +334,20 @@ def compute_entropy_risk(quasi_identifiers, file_info):
return _fn(quasi_identifiers, file_info)


# ---------------------------------------------------------------------------
# Batch / Structural Overview
# ---------------------------------------------------------------------------

def summarize_files(file_infos):
"""Per-file structural overview + totals for a batch of files (no metrics)."""
from aidrin.batch import summarize_files as _fn
return _fn(file_infos)


__all__ = [
"__version__",
# Batch / Structural Overview
"summarize_files",
# Data Quality
"calculate_completeness",
"calculate_duplicates",
Expand Down
71 changes: 71 additions & 0 deletions aidrin/batch.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
"""Session-free batch helpers shared by the web UI, CLI, and library users."""

import os

import pandas as pd

from aidrin.file_handling.file_parser import read_file


def _summarize_one(file_info):
path, name, file_type = file_info
if not path:
return {
"name": name, "type": file_type,
"records": None, "features": None,
"numerical": None, "categorical": None,
"size_bytes": None, "status": "error",
"error": "No file path provided.",
}
size = None
try:
if path and os.path.exists(path):
size = os.path.getsize(path)
except OSError:
size = None

result = read_file(file_info) # DataFrame | None | str
if isinstance(result, pd.DataFrame):
# Same dtype convention as the Data Overview panel: numeric vs string.
numerical = int(sum(pd.api.types.is_numeric_dtype(d) for d in result.dtypes))
categorical = int(sum(pd.api.types.is_string_dtype(d) for d in result.dtypes))
return {
"name": name, "type": file_type,
"records": int(len(result)),
"features": int(len(result.columns)),
"numerical": numerical, "categorical": categorical,
"size_bytes": size, "status": "ok", "error": None,
}

message = result if isinstance(result, str) else (
"Could not read the file. The format may be unsupported or the file "
"may be corrupted."
)
return {
"name": name, "type": file_type,
"records": None, "features": None,
"numerical": None, "categorical": None,
"size_bytes": size, "status": "error", "error": message,
}


def summarize_files(file_infos):
"""Return {"files": [per_file, ...], "totals": {...}} for a list of files.

Computes structural facts only (records, features, size, load status) — no
metrics. A file that fails to load becomes a status:"error" row and never
aborts the batch. ``file_infos`` is a list of (path, name, type) tuples.
"""
files = [_summarize_one(fi) for fi in file_infos]
by_type = {}
for f in files:
by_type[f["type"]] = by_type.get(f["type"], 0) + 1
totals = {
"file_count": len(files),
"ok_count": sum(1 for f in files if f["status"] == "ok"),
"error_count": sum(1 for f in files if f["status"] == "error"),
"total_records": sum(f["records"] or 0 for f in files),
"total_size_bytes": sum(f["size_bytes"] or 0 for f in files),
"by_type": by_type,
}
return {"files": files, "totals": totals}
31 changes: 31 additions & 0 deletions aidrin/file_handling/file_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,37 @@
# (file_type,file_type_name)
]

# Map a real file extension to the READER_MAP key that handles it.
# Excel uses a single combined reader key, so all its extensions point at it.
_EXCEL_KEY = ".xls, .xlsb, .xlsx, .xlsm"
EXTENSION_MAP = {
".csv": ".csv",
".json": ".json",
".npz": ".npz",
".h5": ".h5",
".parquet": ".parquet", # resolves only once a parquet reader is registered in READER_MAP
".xls": _EXCEL_KEY,
".xlsb": _EXCEL_KEY,
".xlsx": _EXCEL_KEY,
".xlsm": _EXCEL_KEY,
}


def file_extension(filename):
"""Return the lowercased real extension (e.g. ``.csv``), or ``""``."""
return os.path.splitext(filename or "")[1].lower()


def infer_file_type(filename):
"""Return the READER_MAP key for a filename's extension, or None.

Only returns a key that is actually registered in READER_MAP on this
install (e.g. ``.parquet`` resolves only if the parquet reader exists).
"""
key = EXTENSION_MAP.get(file_extension(filename))
return key if key in READER_MAP else None


# logger config
file_upload_time_log = logging.getLogger("file_upload")

Expand Down
8 changes: 8 additions & 0 deletions aidrin/structured_data_metrics/privacy_measure.py
Original file line number Diff line number Diff line change
Expand Up @@ -517,6 +517,8 @@ def compute_k_anonymity(quasi_identifiers: List[str], file_info):
"k-Anonymity Visualization": base64_str}``
or ``{"Error": str}`` on validation failure.
"""
if not quasi_identifiers:
return {"Error": "Please select at least one quasi-identifier."}
# Handle both DataFrame and tuple inputs
if isinstance(file_info, tuple):
data = read_file(file_info)
Expand Down Expand Up @@ -634,6 +636,8 @@ def compute_l_diversity(
"l-Diversity Visualization": base64_str}``
or ``{"Error": str}`` on validation failure.
"""
if not quasi_identifiers:
return {"Error": "Please select at least one quasi-identifier."}
# Handle both DataFrame and tuple inputs
if isinstance(file_info, tuple):
data = read_file(file_info)
Expand Down Expand Up @@ -765,6 +769,8 @@ def compute_t_closeness(
where ``t-Value`` is in ``[0, 1]``,
or ``{"Error": str}`` on validation failure.
"""
if not quasi_identifiers:
return {"Error": "Please select at least one quasi-identifier."}
# Handle both DataFrame and tuple inputs
if isinstance(file_info, tuple):
data = read_file(file_info)
Expand Down Expand Up @@ -895,6 +901,8 @@ def compute_entropy_risk(quasi_identifiers, file_info):
where ``Entropy-Value >= 0``,
or ``{"Error": str}`` on validation failure.
"""
if not quasi_identifiers:
return {"Error": "Please select at least one quasi-identifier."}
# Handle both DataFrame and tuple inputs
if isinstance(file_info, tuple):
data = read_file(file_info)
Expand Down
Loading
Loading