StrangeIB commited on
Commit ·
2fd90c5
1
Parent(s): c377a9f
Make this Space the single home: latest app + engine + CLI
Browse files- app.py: BBH branding, experience.usc.edu download instructions, in-memory redaction
- redactors/: canonical engine (PII + grades passes, verify-before-write)
- redact_stars.py: command-line tool (single/batch)
- README: single-home model + what-to-edit-where
- remove Dockerfile + src/ template clutter (Streamlit SDK runs app.py)
- Dockerfile +0 -20
- README.md +45 -13
- app.py +17 -7
- redact_stars.py +176 -0
- src/streamlit_app.py +0 -40
Dockerfile
DELETED
|
@@ -1,20 +0,0 @@
|
|
| 1 |
-
FROM python:3.13.5-slim
|
| 2 |
-
|
| 3 |
-
WORKDIR /app
|
| 4 |
-
|
| 5 |
-
RUN apt-get update && apt-get install -y \
|
| 6 |
-
build-essential \
|
| 7 |
-
curl \
|
| 8 |
-
git \
|
| 9 |
-
&& rm -rf /var/lib/apt/lists/*
|
| 10 |
-
|
| 11 |
-
COPY requirements.txt ./
|
| 12 |
-
COPY src/ ./src/
|
| 13 |
-
|
| 14 |
-
RUN pip3 install -r requirements.txt
|
| 15 |
-
|
| 16 |
-
EXPOSE 8501
|
| 17 |
-
|
| 18 |
-
HEALTHCHECK CMD curl --fail http://localhost:8501/_stcore/health
|
| 19 |
-
|
| 20 |
-
ENTRYPOINT ["streamlit", "run", "src/streamlit_app.py", "--server.port=8501", "--server.address=0.0.0.0"]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
README.md
CHANGED
|
@@ -11,22 +11,54 @@ license: agpl-3.0
|
|
| 11 |
|
| 12 |
# STARS Report Redactor
|
| 13 |
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
information (name, ID, address, sport) and grades (replaced with
|
| 17 |
-
|
| 18 |
-
file is never written to disk or stored.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 19 |
|
| 20 |
## Configuration
|
| 21 |
|
| 22 |
-
Set a Space **secret** named `APP_PASSPHRASE` to the access phrase
|
| 23 |
-
refuses to run without it.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 24 |
|
| 25 |
-
#
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 26 |
|
| 27 |
-
|
| 28 |
-
`redactors/` package (a copy of the canonical package in
|
| 29 |
-
`internal-tools/redaction/redactors/` of the main repo). If the redaction logic
|
| 30 |
-
changes there, re-copy `redactors/` here.
|
| 31 |
|
| 32 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 11 |
|
| 12 |
# STARS Report Redactor
|
| 13 |
|
| 14 |
+
Web tool for the **BUAI Builder Hub (BBH) / BBHCourseReg** project. A student
|
| 15 |
+
uploads their USC STARS Degree Progress Report PDF; it removes personal
|
| 16 |
+
information (name, ID, address, sport) and grades (each grade replaced with a
|
| 17 |
+
pass/fail marker, GPA figures blanked) **entirely in memory** and returns a
|
| 18 |
+
redacted copy. The uploaded file is never written to disk or stored.
|
| 19 |
+
|
| 20 |
+
**This repository is the single home for the redaction tool** — the web app,
|
| 21 |
+
the redaction engine, and the command-line tool all live here together. Pushing
|
| 22 |
+
to this repo automatically rebuilds and redeploys the live Space.
|
| 23 |
+
|
| 24 |
+
## What to edit where
|
| 25 |
+
|
| 26 |
+
| File / folder | What it is | Edit it for |
|
| 27 |
+
|---|---|---|
|
| 28 |
+
| `app.py` | the Streamlit web UI | look & wording of the web app |
|
| 29 |
+
| `redactors/` | the redaction engine | redaction logic / behavior |
|
| 30 |
+
| `redact_stars.py` | the command-line tool (batch/single) | CLI features |
|
| 31 |
+
| `requirements.txt` | Python dependencies | adding a package |
|
| 32 |
+
| `README.md` | this file (the top YAML configures the Space) | docs / Space config |
|
| 33 |
+
|
| 34 |
+
Deploying is automatic: commit and `git push` to this repo, and the Space
|
| 35 |
+
rebuilds. There are no copies to keep in sync.
|
| 36 |
|
| 37 |
## Configuration
|
| 38 |
|
| 39 |
+
Set a Space **secret** named `APP_PASSPHRASE` to the access phrase (Space →
|
| 40 |
+
Settings → Variables and secrets). The app refuses to run without it.
|
| 41 |
+
|
| 42 |
+
## Run locally
|
| 43 |
+
|
| 44 |
+
```bash
|
| 45 |
+
pip install -r requirements.txt
|
| 46 |
+
|
| 47 |
+
# web app:
|
| 48 |
+
streamlit run app.py # set APP_PASSPHRASE in your env first
|
| 49 |
|
| 50 |
+
# command line (single file, folder batch, or preview):
|
| 51 |
+
python redact_stars.py report.pdf # PII + grades
|
| 52 |
+
python redact_stars.py a_folder/ -o out/ # batch
|
| 53 |
+
python redact_stars.py report.pdf --redact pii # PII only
|
| 54 |
+
python redact_stars.py report.pdf --dry-run # show, write nothing
|
| 55 |
+
```
|
| 56 |
|
| 57 |
+
## Notes
|
|
|
|
|
|
|
|
|
|
| 58 |
|
| 59 |
+
- Redaction is not perfect anonymization: major, terms, and the course list
|
| 60 |
+
remain, which can be identifying in a small program. This is disclosed to
|
| 61 |
+
students in the app before they consent.
|
| 62 |
+
- Scanned/image-only PDFs have no text layer and are skipped (they can't be
|
| 63 |
+
redacted here); use the PDF saved from experience.usc.edu.
|
| 64 |
+
- Licensed AGPL-3.0 because it uses MuPDF/PyMuPDF.
|
app.py
CHANGED
|
@@ -21,7 +21,7 @@ st.set_page_config(page_title="STARS Report Redactor", page_icon="🛡️",
|
|
| 21 |
|
| 22 |
st.title("🛡️ STARS Report Redactor")
|
| 23 |
st.caption("Remove your personal information and grades from a USC STARS "
|
| 24 |
-
"Degree Progress Report before sharing it with the
|
| 25 |
|
| 26 |
with st.expander("What this does and how your file is handled", expanded=True):
|
| 27 |
st.markdown(
|
|
@@ -34,6 +34,15 @@ with st.expander("What this does and how your file is handled", expanded=True):
|
|
| 34 |
"Please share only if you consent."
|
| 35 |
)
|
| 36 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 37 |
# --- access gate -----------------------------------------------------------
|
| 38 |
if not PASSPHRASE:
|
| 39 |
st.error("This app isn't configured yet (no access phrase set). "
|
|
@@ -41,7 +50,7 @@ if not PASSPHRASE:
|
|
| 41 |
st.stop()
|
| 42 |
|
| 43 |
entered = st.text_input("Access phrase", type="password",
|
| 44 |
-
help="Provided to you by the
|
| 45 |
if entered.strip() != PASSPHRASE:
|
| 46 |
if entered:
|
| 47 |
st.warning("That access phrase isn't right. Please try again.")
|
|
@@ -49,10 +58,10 @@ if entered.strip() != PASSPHRASE:
|
|
| 49 |
|
| 50 |
# --- redaction flow --------------------------------------------------------
|
| 51 |
consent = st.checkbox(
|
| 52 |
-
"I consent to share my redacted STARS report with the
|
| 53 |
|
| 54 |
uploaded = st.file_uploader(
|
| 55 |
-
"Upload your STARS report (the PDF you
|
| 56 |
type=["pdf"], accept_multiple_files=False)
|
| 57 |
|
| 58 |
if uploaded is not None and not consent:
|
|
@@ -85,13 +94,14 @@ if uploaded is not None and consent:
|
|
| 85 |
|
| 86 |
elif status == "SKIPPED":
|
| 87 |
st.error("This PDF looks like a **scan** (it has no text layer), so it "
|
| 88 |
-
"can't be redacted here. Please
|
| 89 |
-
"
|
|
|
|
| 90 |
else:
|
| 91 |
st.error("The redaction could not be fully verified, so **no file was "
|
| 92 |
"produced**. Please contact the team — and do not share the "
|
| 93 |
"original in the meantime.")
|
| 94 |
|
| 95 |
st.divider()
|
| 96 |
-
st.caption("Internal tool of the
|
| 97 |
"Processing happens in memory; no files are stored.")
|
|
|
|
| 21 |
|
| 22 |
st.title("🛡️ STARS Report Redactor")
|
| 23 |
st.caption("Remove your personal information and grades from a USC STARS "
|
| 24 |
+
"Degree Progress Report before sharing it with the BUAI Builder Hub (BBH).")
|
| 25 |
|
| 26 |
with st.expander("What this does and how your file is handled", expanded=True):
|
| 27 |
st.markdown(
|
|
|
|
| 34 |
"Please share only if you consent."
|
| 35 |
)
|
| 36 |
|
| 37 |
+
with st.expander("How to download your STARS report (PDF)"):
|
| 38 |
+
st.markdown(
|
| 39 |
+
"1. Go to **experience.usc.edu** and open **My Academics**.\n"
|
| 40 |
+
"2. Open your **STARS** (Degree Progress) report.\n"
|
| 41 |
+
"3. Click the **red Print button** at the top of the report.\n"
|
| 42 |
+
"4. In the print dialog, set the destination to **Save as PDF** and save the file.\n"
|
| 43 |
+
"5. Upload that saved PDF below."
|
| 44 |
+
)
|
| 45 |
+
|
| 46 |
# --- access gate -----------------------------------------------------------
|
| 47 |
if not PASSPHRASE:
|
| 48 |
st.error("This app isn't configured yet (no access phrase set). "
|
|
|
|
| 50 |
st.stop()
|
| 51 |
|
| 52 |
entered = st.text_input("Access phrase", type="password",
|
| 53 |
+
help="Provided to you by the BBH team.")
|
| 54 |
if entered.strip() != PASSPHRASE:
|
| 55 |
if entered:
|
| 56 |
st.warning("That access phrase isn't right. Please try again.")
|
|
|
|
| 58 |
|
| 59 |
# --- redaction flow --------------------------------------------------------
|
| 60 |
consent = st.checkbox(
|
| 61 |
+
"I consent to share my redacted STARS report with the BUAI Builder Hub (BBH).")
|
| 62 |
|
| 63 |
uploaded = st.file_uploader(
|
| 64 |
+
"Upload your STARS report (the PDF you saved from experience.usc.edu)",
|
| 65 |
type=["pdf"], accept_multiple_files=False)
|
| 66 |
|
| 67 |
if uploaded is not None and not consent:
|
|
|
|
| 94 |
|
| 95 |
elif status == "SKIPPED":
|
| 96 |
st.error("This PDF looks like a **scan** (it has no text layer), so it "
|
| 97 |
+
"can't be redacted here. Please use the PDF you save via the "
|
| 98 |
+
"**Print → Save as PDF** steps above on experience.usc.edu, "
|
| 99 |
+
"rather than a scanned or photographed copy.")
|
| 100 |
else:
|
| 101 |
st.error("The redaction could not be fully verified, so **no file was "
|
| 102 |
"produced**. Please contact the team — and do not share the "
|
| 103 |
"original in the meantime.")
|
| 104 |
|
| 105 |
st.divider()
|
| 106 |
+
st.caption("Internal tool of the BUAI Builder Hub (BBH) / BBHCourseReg project. "
|
| 107 |
"Processing happens in memory; no files are stored.")
|
redact_stars.py
ADDED
|
@@ -0,0 +1,176 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""
|
| 3 |
+
redact_stars.py — Bulletproof, structure-faithful redaction for USC STARS
|
| 4 |
+
Degree Progress Reports (single- or double-column, any student).
|
| 5 |
+
|
| 6 |
+
One tool, two redaction passes, chosen with --redact:
|
| 7 |
+
|
| 8 |
+
--redact all (default) remove PII *and* redact grades
|
| 9 |
+
--redact pii remove PII only
|
| 10 |
+
--redact grades redact grades only
|
| 11 |
+
|
| 12 |
+
Both passes run on the same in-memory document and are gated by a single
|
| 13 |
+
verification step, so a file is only written once it is proven clean for the
|
| 14 |
+
selected passes. Output filenames encode the mode so a partial redaction can
|
| 15 |
+
never be mistaken for a complete one:
|
| 16 |
+
|
| 17 |
+
all -> REDACTED_<name>.pdf
|
| 18 |
+
pii -> REDACTED-PII_<name>.pdf
|
| 19 |
+
grades -> REDACTED-GRADES_<name>.pdf
|
| 20 |
+
|
| 21 |
+
PII pass: student ID, roster + diploma name, mailing address, sport/team.
|
| 22 |
+
Grade pass: each grade -> basis + pass/fail token (Lp/Lf/Pp/Pn); keeps
|
| 23 |
+
IN/IX, RG, TR, W, CR/NC, flags; neutralizes GPA/POINTS figures.
|
| 24 |
+
|
| 25 |
+
How it stays faithful & bulletproof: it TRULY removes the target glyphs
|
| 26 |
+
(PyMuPDF apply_redactions) and re-inserts width-preserving monospace filler, so
|
| 27 |
+
the fixed-width columns are preserved and the originals are gone — not covered.
|
| 28 |
+
|
| 29 |
+
Usage:
|
| 30 |
+
python3 redact_stars.py INPUT.pdf [-o OUT] [--redact all|pii|grades]
|
| 31 |
+
[--dry-run] [--tag]
|
| 32 |
+
python3 redact_stars.py FOLDER [-o OUT_FOLDER] [--redact ...]
|
| 33 |
+
"""
|
| 34 |
+
import argparse
|
| 35 |
+
import os
|
| 36 |
+
import re
|
| 37 |
+
import sys
|
| 38 |
+
|
| 39 |
+
try:
|
| 40 |
+
import fitz # PyMuPDF
|
| 41 |
+
except ImportError:
|
| 42 |
+
sys.exit("PyMuPDF is required: pip install pymupdf --break-system-packages")
|
| 43 |
+
|
| 44 |
+
from redactors import common, pii, grades, engine
|
| 45 |
+
|
| 46 |
+
MODE_PREFIX = {
|
| 47 |
+
frozenset({"pii", "grades"}): "REDACTED_",
|
| 48 |
+
frozenset({"pii"}): "REDACTED-PII_",
|
| 49 |
+
frozenset({"grades"}): "REDACTED-GRADES_",
|
| 50 |
+
}
|
| 51 |
+
|
| 52 |
+
|
| 53 |
+
def _default_out(in_path, modes, out_dir=None):
|
| 54 |
+
base = os.path.basename(in_path)
|
| 55 |
+
base = re.sub(r"^DONOTSHARE[_-]*", "", base, flags=re.IGNORECASE)
|
| 56 |
+
base = re.sub(r"^REDACTED[A-Z-]*_", "", base, flags=re.IGNORECASE) # no stacking
|
| 57 |
+
total = engine.existing_modes(in_path) | set(modes) # cumulative state
|
| 58 |
+
d = out_dir if out_dir else os.path.dirname(in_path)
|
| 59 |
+
return os.path.join(d, MODE_PREFIX[frozenset(total)] + base)
|
| 60 |
+
|
| 61 |
+
|
| 62 |
+
def process_one(in_path, out_path, modes, tag=False):
|
| 63 |
+
"""Redact one report for the selected passes (file in -> file out). Returns
|
| 64 |
+
a status dict and NEVER writes a file that fails verification."""
|
| 65 |
+
doc = fitz.open(in_path)
|
| 66 |
+
try:
|
| 67 |
+
res = engine.redact_doc(doc, modes, tag=tag,
|
| 68 |
+
existing=engine.existing_modes(in_path))
|
| 69 |
+
res["file"] = in_path
|
| 70 |
+
res["out"] = out_path
|
| 71 |
+
res["modes"] = modes
|
| 72 |
+
if res["status"] in ("REDACTED", "REVIEW"):
|
| 73 |
+
doc.save(out_path, garbage=4, deflate=True, clean=True)
|
| 74 |
+
finally:
|
| 75 |
+
doc.close()
|
| 76 |
+
return res
|
| 77 |
+
|
| 78 |
+
|
| 79 |
+
def _print_one(res):
|
| 80 |
+
print(f"\n=== {os.path.basename(res['file'])} [{'+'.join(sorted(res['modes']))}] ===")
|
| 81 |
+
for k, v in res.get("pii", {}).items():
|
| 82 |
+
print(f" {k:24s}: {v!r}")
|
| 83 |
+
if res.get("pii_note"):
|
| 84 |
+
print(f" {res['pii_note']}")
|
| 85 |
+
if res.get("grades"):
|
| 86 |
+
g = res["grades"]
|
| 87 |
+
print(f" grades redacted: {g['grades_replaced']} | "
|
| 88 |
+
f"GPA/POINTS figures: {g['gpa_replaced']} | "
|
| 89 |
+
f"course rows seen: {g['course_rows']}")
|
| 90 |
+
label = {"REDACTED": "OK (verified clean)", "REVIEW": "OK — needs human review",
|
| 91 |
+
"SKIPPED": "SKIPPED", "FAILED": "FAILED"}[res["status"]]
|
| 92 |
+
print(f" -> {label}: {res['reason'] or res['out']}")
|
| 93 |
+
for tok, pages in res["fragments"]:
|
| 94 |
+
print(f" review: name fragment {tok!r} still appears on page(s) {pages}")
|
| 95 |
+
for h in res.get("hard", []):
|
| 96 |
+
print(f" !! {h}")
|
| 97 |
+
|
| 98 |
+
|
| 99 |
+
def run_batch(in_dir, out_dir, modes, tag=False):
|
| 100 |
+
os.makedirs(out_dir, exist_ok=True)
|
| 101 |
+
results = []
|
| 102 |
+
for name in sorted(os.listdir(in_dir)):
|
| 103 |
+
if not name.lower().endswith(".pdf"):
|
| 104 |
+
continue
|
| 105 |
+
if name.upper().startswith("REDACTED_"): # already fully redacted -> skip
|
| 106 |
+
continue
|
| 107 |
+
src = os.path.join(in_dir, name)
|
| 108 |
+
try:
|
| 109 |
+
r = process_one(src, _default_out(src, modes, out_dir), modes, tag=tag)
|
| 110 |
+
except Exception as e: # unexpected -> report, keep going
|
| 111 |
+
r = {"file": src, "modes": modes, "status": "FAILED",
|
| 112 |
+
"reason": f"error: {e}", "pii": {}, "grades": {},
|
| 113 |
+
"fragments": [], "hard": []}
|
| 114 |
+
_print_one(r)
|
| 115 |
+
results.append(r)
|
| 116 |
+
|
| 117 |
+
order = ["REDACTED", "REVIEW", "SKIPPED", "FAILED"]
|
| 118 |
+
counts = {s: sum(1 for r in results if r["status"] == s) for s in order}
|
| 119 |
+
print("\n" + "=" * 60 + "\nBATCH SUMMARY [redact: " + "+".join(sorted(modes)) + "]")
|
| 120 |
+
for s in order:
|
| 121 |
+
print(f" {s:9s}: {counts[s]}")
|
| 122 |
+
print(f" outputs in: {out_dir}")
|
| 123 |
+
return results
|
| 124 |
+
|
| 125 |
+
|
| 126 |
+
def _dry_run(path, modes):
|
| 127 |
+
d = fitz.open(path)
|
| 128 |
+
if not common.has_text_layer(d):
|
| 129 |
+
print("No text layer (scanned image) — not viable for this tool.")
|
| 130 |
+
return
|
| 131 |
+
if "pii" in modes:
|
| 132 |
+
values = pii.detect(d)
|
| 133 |
+
print("Detected identifiers:" if values else "No identifiers detected.")
|
| 134 |
+
for k, v in values.items():
|
| 135 |
+
print(f" {k:24s}: {v!r}")
|
| 136 |
+
if "grades" in modes:
|
| 137 |
+
_, gm = grades.build_findings(d)
|
| 138 |
+
print(f"Grades to redact: {gm['grades_replaced']} | "
|
| 139 |
+
f"GPA/POINTS figures: {gm['gpa_replaced']} | "
|
| 140 |
+
f"course rows: {gm['course_rows']}")
|
| 141 |
+
|
| 142 |
+
|
| 143 |
+
def main():
|
| 144 |
+
ap = argparse.ArgumentParser(
|
| 145 |
+
description="Bulletproof redaction of USC STARS report(s): PII and/or grades.")
|
| 146 |
+
ap.add_argument("input", help="a STARS report PDF, or a folder of them")
|
| 147 |
+
ap.add_argument("-o", "--output",
|
| 148 |
+
help="output file (single) or output folder (batch)")
|
| 149 |
+
ap.add_argument("--redact", choices=["all", "pii", "grades"], default="all",
|
| 150 |
+
help="what to redact (default: all = PII + grades)")
|
| 151 |
+
ap.add_argument("--dry-run", action="store_true",
|
| 152 |
+
help="print what would be redacted and exit (writes nothing)")
|
| 153 |
+
ap.add_argument("--tag", action="store_true",
|
| 154 |
+
help="use [REDACTED-*] labels instead of X filler for PII")
|
| 155 |
+
args = ap.parse_args()
|
| 156 |
+
|
| 157 |
+
modes = {"pii", "grades"} if args.redact == "all" else {args.redact}
|
| 158 |
+
|
| 159 |
+
if os.path.isdir(args.input):
|
| 160 |
+
out_dir = args.output or os.path.join(args.input, "REDACTED")
|
| 161 |
+
run_batch(args.input, out_dir, modes, tag=args.tag)
|
| 162 |
+
return
|
| 163 |
+
|
| 164 |
+
if args.dry_run:
|
| 165 |
+
_dry_run(args.input, modes)
|
| 166 |
+
return
|
| 167 |
+
|
| 168 |
+
res = process_one(args.input, args.output or _default_out(args.input, modes),
|
| 169 |
+
modes, tag=args.tag)
|
| 170 |
+
_print_one(res)
|
| 171 |
+
if res["status"] == "FAILED":
|
| 172 |
+
sys.exit(1)
|
| 173 |
+
|
| 174 |
+
|
| 175 |
+
if __name__ == "__main__":
|
| 176 |
+
main()
|
src/streamlit_app.py
DELETED
|
@@ -1,40 +0,0 @@
|
|
| 1 |
-
import altair as alt
|
| 2 |
-
import numpy as np
|
| 3 |
-
import pandas as pd
|
| 4 |
-
import streamlit as st
|
| 5 |
-
|
| 6 |
-
"""
|
| 7 |
-
# Welcome to Streamlit!
|
| 8 |
-
|
| 9 |
-
Edit `/streamlit_app.py` to customize this app to your heart's desire :heart:.
|
| 10 |
-
If you have any questions, checkout our [documentation](https://docs.streamlit.io) and [community
|
| 11 |
-
forums](https://discuss.streamlit.io).
|
| 12 |
-
|
| 13 |
-
In the meantime, below is an example of what you can do with just a few lines of code:
|
| 14 |
-
"""
|
| 15 |
-
|
| 16 |
-
num_points = st.slider("Number of points in spiral", 1, 10000, 1100)
|
| 17 |
-
num_turns = st.slider("Number of turns in spiral", 1, 300, 31)
|
| 18 |
-
|
| 19 |
-
indices = np.linspace(0, 1, num_points)
|
| 20 |
-
theta = 2 * np.pi * num_turns * indices
|
| 21 |
-
radius = indices
|
| 22 |
-
|
| 23 |
-
x = radius * np.cos(theta)
|
| 24 |
-
y = radius * np.sin(theta)
|
| 25 |
-
|
| 26 |
-
df = pd.DataFrame({
|
| 27 |
-
"x": x,
|
| 28 |
-
"y": y,
|
| 29 |
-
"idx": indices,
|
| 30 |
-
"rand": np.random.randn(num_points),
|
| 31 |
-
})
|
| 32 |
-
|
| 33 |
-
st.altair_chart(alt.Chart(df, height=700, width=700)
|
| 34 |
-
.mark_point(filled=True)
|
| 35 |
-
.encode(
|
| 36 |
-
x=alt.X("x", axis=None),
|
| 37 |
-
y=alt.Y("y", axis=None),
|
| 38 |
-
color=alt.Color("idx", legend=None, scale=alt.Scale()),
|
| 39 |
-
size=alt.Size("rand", legend=None, scale=alt.Scale(range=[1, 150])),
|
| 40 |
-
))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|