A decision-safe workflow for AI vs human vs post-edited AI text detection that goes beyond accuracy with probability calibration, a learned abstention threshold, coverage analysis, reliability figures, and a Streamlit review dashboard.
Important: This project is a portfolio and research demo, not a production content-detection system.
The models are simple TF-IDF baselines on a small example dataset. The metrics, thresholds, and abstention policy are designed to demonstrate a professional reliability workflow, not to make real moderation or integrity decisions without expert validation, monitoring, and review.
- Project Overview
- What This Project Does
- What This Project Does Not Do
- Key Features
- System Workflow
- Project Structure
- Installation
- Quick Start
- Data Format
- How the Pipeline Works
- Metrics Explained
- Abstention and Coverage
- Visual Reports
- Streamlit Dashboard
- Recommended Threshold
- Decision Safety
- Testing and CI
- Code Quality
- Limitations
- Responsible Use
- Future Improvements
- Tech Stack
- Troubleshooting
- Author
- License
Most detector projects stop at accuracy. Real detection workflows need more, because a wrong decision is costly:
- flagging humans as AI (false positives) harms trust and policy enforcement
- missing AI (false negatives) weakens moderation and integrity workflows
- post-edited AI is especially tricky and can resemble both classes
Accuracy alone does not tell you whether to trust the model on any single decision. This project produces a Reliability Report Card (calibration + performance), learns a recommended abstention threshold that targets a chosen auto-decision coverage, and ships a Streamlit dashboard that presents everything in a decision-ready format.
The goal is to show how a detector can be turned into a decision-support system, not just a metric in a notebook.
This project can:
- Load and normalize a text/label dataset with automatic column detection
- Build word-level and character-level TF-IDF baseline models
- Calibrate predicted probabilities (sigmoid or isotonic)
- Select the primary model using validation macro-F1
- Evaluate accuracy, macro-F1, ECE, and Brier score on held-out test data
- Sweep confidence thresholds to build a coverage-vs-performance curve
- Recommend an abstention threshold for a target auto-decision coverage
- Save machine-readable metrics, policy, curves, and predictions
- Generate reliability figures (confusion matrix, reliability diagram, coverage, confidence)
- Persist the fitted model and score new pasted text live in the dashboard
- Provide a Streamlit dashboard for report-card review and triage
- Run unit tests and a GitHub Actions CI quality gate
This project does not:
- Detect AI-generated text in production
- Use a large, representative, or adversarial dataset
- Guarantee detection decisions are fair, robust, or deployable
- Replace human reviewers or policy teams
- Provide real-time or streaming inference
- Include drift monitoring or automatic retraining
A production detector would need stronger models, governance, live monitoring, adversarial testing, and human escalation workflows.
- Automatic column detection for text and label columns, robust across pandas 2 and 3
- Word + character TF-IDF baselines that are fast and explainable
- Probability calibration (sigmoid / isotonic) for trustworthy confidence
- Validation-based model selection by macro-F1
- Calibration metrics (ECE, Brier) alongside accuracy and macro-F1
- Coverage curve sweeping thresholds for the coverage-vs-performance tradeoff
- Abstention policy artifact with a recommended threshold for target coverage
- Live single-text inference via a persisted model bundle (
outputs/model.joblib) - Machine-readable artifacts (JSON/CSV) for analysis, audits, and the dashboard
- Reliability figures for the report card
- Streamlit dashboard for review and triage
- Unit tests and GitHub Actions CI (ruff, black, mypy, pytest on Python 3.10–3.12)
Text / label dataset (CSV)
↓
Column detection + cleaning
↓
Stratified train/val/test split
↓
Word + char TF-IDF model pipelines
↓
Probability calibration (sigmoid / isotonic)
↓
Validation macro-F1 → primary model selection
↓
Test metrics (accuracy, macro-F1, ECE, Brier)
↓
Threshold sweep → coverage curve → recommended abstention policy
↓
Artifacts + figures → Streamlit report card & triage
Detector-Reliability-Report-Card/
│
├── .github/
│ └── workflows/
│ └── ci.yml
│
├── app/
│ └── app.py
│
├── data/
│ └── raw/
│ └── ai_human_detection.csv
│
├── outputs/
│ ├── metrics_overall.json
│ ├── abstention_policy.json
│ ├── coverage_curve.csv
│ └── test_predictions.csv
│
├── reports/
│ └── figures/
│ ├── confusion_matrix.png
│ ├── reliability_diagram.png
│ ├── coverage_vs_accuracy.png
│ └── probability_histograms.png
│
├── src/
│ ├── __init__.py
│ ├── pipeline.py
│ ├── inference.py
│ ├── io_utils.py
│ ├── clean.py
│ ├── split.py
│ ├── features.py
│ ├── models.py
│ ├── metrics.py
│ └── reporting.py
│
├── tests/
│ ├── test_clean.py
│ ├── test_metrics.py
│ ├── test_split.py
│ └── test_pipeline_smoke.py
│
├── pyproject.toml
├── requirements.txt
├── .gitignore
├── README.md
└── LICENSE
git clone https://github.com/AmirhosseinHonardoust/Detector-Reliability-Report-Card.git
cd Detector-Reliability-Report-CardOn Windows CMD:
python -m venv .venv
.venv\Scripts\activateOn macOS/Linux:
python -m venv .venv
source .venv/bin/activatepip install -r requirements.txtRun the pipeline to generate report artifacts:
python -m src.pipeline --input data/raw/ai_human_detection.csvExpected outcome:
outputs/populated with JSON/CSV artifactsreports/figures/populated with PNG plots- terminal prints a recommended threshold + estimated coverage (example: threshold ≈ 0.61, coverage ≈ 0.71)
Launch the dashboard:
streamlit run app/app.pyReproducibility note: runs are deterministic for a fixed random seed and dependency set, but calibration internals can shift slightly between scikit-learn versions, so ECE/Brier and the committed
outputs/*may differ marginally across environments. Pin exact versions for byte-identical artifacts.
At minimum the pipeline needs:
- a text column (the content)
- a label column (ground-truth class)
Typical labels used by this project:
humanaipost_edited_ai
Column names are detected automatically: text / content / sentence for text and label / class / human_or_ai / target for labels, with a length/cardinality fallback when names differ. Post-edited AI behaves like an in-between distribution, confusing with ai when edits are light and with human when edits are heavy, which is why macro-F1 and confusion analysis are emphasized.
| Step | What happens |
|---|---|
| A. Clean | Detect text/label columns, normalize labels, drop empty text |
| B. Split | Stratified train/val/test split preserving class balance |
| C. Model | Train word-level and character-level TF-IDF baselines |
| D. Calibrate | Reshape probabilities so "0.8" behaves like ~80% correct |
| E. Select | Pick the primary model by validation macro-F1 |
| F. Evaluate | Accuracy, macro-F1, ECE, and Brier on held-out test data |
| G. Sweep | Evaluate many thresholds to build the coverage curve |
| H. Save | Write metrics, policy, curves, predictions, and figures |
Baselines are used because they are quick to train, easy to debug, and a strong reference point before heavier models. Calibration matters because raw scores are often miscalibrated, and thresholding on miscalibrated confidence is dangerous.
| Metric | What it measures | Why it matters here |
|---|---|---|
| Accuracy | Overall correctness rate | Can hide class imbalance and which mistakes matter |
| Macro-F1 | F1 per class, averaged equally | Prevents an "it's accurate" conclusion driven by the easy class |
| ECE | Gap between confidence and empirical accuracy | High ECE means the confidence numbers are misleading |
| Brier | Squared error of predicted probabilities | Rewards both correctness and probability sharpness |
For ECE and Brier, lower is better. A model can be accurate but miscalibrated (dangerous thresholds) or moderately accurate but well-calibrated (safer abstention behavior); decision-safe deployment needs both.
Example results from the included example dataset:
| Metric | Example value |
|---|---|
| Accuracy | 0.739 |
| Macro-F1 | 0.614 |
| ECE | 0.096 |
| Brier | 0.372 |
| Recommended threshold | 0.61 |
| Estimated coverage | 0.710 |
| Estimated accuracy at threshold | 0.816 |
These values come from a small example dataset and should not be read as real-world detection performance. They are reproducible with the default seed but may shift slightly across scikit-learn/NumPy versions.
Coverage is the fraction of cases the model auto-decides. A higher threshold keeps only confident predictions (coverage drops); a lower threshold decides more often (coverage rises). Performance is evaluated on the decided subset, which usually improves as the threshold rises because only easy, high-confidence examples remain.
The real deployment tradeoff:
- High coverage → less review cost, more wrong auto-decisions
- Low coverage → safer auto-decisions, higher review burden
This project makes that tradeoff measurable and explicit, and recommends a threshold for your chosen target coverage. The abstain rule is: abstain if max_proba < threshold, or if the word and char models disagree and max_proba < threshold + 0.05.
Launch the app:
streamlit run app/app.pyThe dashboard turns offline artifacts into a clean decision interface across four tabs.
One screen that answers "should we trust this model?": top-line metrics (accuracy, macro-F1, ECE, Brier) and a 2×2 grid of the figures, plus the recommended abstention policy.
Explore threshold tradeoffs interactively: what threshold gives ~70% auto-decisions, how much performance is lost as coverage rises, and where diminishing returns begin.
Runs the saved model on text you paste and shows the decision-safe output: predicted class, confidence, auto-decide vs abstain, and a probability breakdown. The model is loaded from outputs/model.joblib, which the pipeline writes on each run (click Run / Refresh once if it is missing).
Documents the policy philosophy and upgrade path: accuracy is not trust, calibration and ECE, coverage as a product metric, and recommended next steps.
The pipeline prints a recommended threshold for your target coverage. For example, "threshold = 0.61, coverage ≈ 0.71" means that auto-deciding when confidence ≥ 0.61 auto-decides about 71% of cases on similar data, sending the remaining ~29% to review.
Coverage estimates are only valid if future data resembles the evaluation data, calibration remains stable, and the class mix does not drift heavily. That is why drift monitoring is part of the production-grade upgrade path.
This project avoids common failure patterns:
- "The model is 80% accurate so we trust it." Not safe; accuracy can mask overconfidence and minority-class failures.
- "Set threshold to 0.9 and ship." Not safe unless calibration supports it; an overconfident 0.9 is not truly 90% reliable.
- "We'll just review random samples." Not efficient; abstention focuses review on uncertain cases where humans add the most value.
Run the quality gate locally (config lives in pyproject.toml):
pip install ruff black mypy pytest
ruff check src app tests
black --check src app tests
mypy src app tests
pytest -qThe GitHub Actions workflow runs the same gate on Python 3.10, 3.11, and 3.12 for every push and pull request, covering:
- linting (ruff)
- formatting (black)
- type checking (mypy)
- unit tests and an end-to-end smoke test (pytest)
CI is defined in:
.github/workflows/ci.yml
The project separates responsibilities across small, single-purpose modules:
| Module | Purpose |
|---|---|
src/pipeline.py |
Orchestration: train → evaluate → save artifacts/plots/model |
src/inference.py |
Load the saved model and score raw text |
src/io_utils.py |
CSV/JSON read and write helpers |
src/clean.py |
Column detection + text/label normalization |
src/split.py |
Stratified train/val/test split |
src/features.py |
Word/char TF-IDF vectorizer configs |
src/models.py |
Baseline + calibrated model builders |
src/metrics.py |
Accuracy, macro-F1, ECE, Brier, coverage curve |
src/reporting.py |
Figure generation |
app/app.py |
Streamlit dashboard |
This project has important limitations:
- The dataset is small and illustrative, not representative
- Results do not prove real-world AI-text detection performance
- Models are TF-IDF baselines, not state-of-the-art detectors
- No drift monitoring, streaming inference, or retraining is included
- No fairness, robustness, or adversarial review is included
- The abstention policy is an example, not an approved business rule
The project is strongest as a portfolio demonstration of reliability-workflow design.
This repository is intended for:
- learning calibration and abstention workflows
- demonstrating coverage-vs-performance tradeoffs
- practicing model evaluation and reporting
- portfolio demonstration
It should not be used as-is for:
- real content-moderation or integrity decisions
- penalizing users or accounts
- academic-integrity enforcement
- any high-stakes automated decision
Any real deployment would require stronger models, expert review, monitoring, and governance.
- Add slice audits (language, domain, length, post-edit intensity)
- Add drift monitoring (confidence, class mix, calibration over time)
- Add cost-aware abstention (minimize error cost + review cost)
- Add stronger models and richer features
- Add a model card and data statement
- Python
- pandas
- NumPy
- scikit-learn
- joblib
- matplotlib
- Streamlit
- Plotly
- pytest
- ruff / black / mypy
- GitHub Actions
Streamlit use_container_width deprecation. Use width="stretch" in st.image() and st.plotly_chart(). This repo's layout is designed around width="stretch" so figures align cleanly.
"Run pipeline first." If the dashboard reports missing outputs, click Run / Refresh in the sidebar or run the pipeline from the command line.
Outputs not updating. Ensure out_dir and figures_dir are correct and the app points to the same project root.
Amir Honardoust
GitHub: @AmirhosseinHonardoust
This project is intended for educational and portfolio purposes.
If you use or modify this project, please keep the responsible-use notes and limitations clear.







