risk-clause-backend / src /preprocessing /dataset_validation.py
Pratham Mohan
initial commit
993fce6
Raw
History Blame Contribute Delete
7.12 kB
import os
import numpy as np
import pandas as pd
from scipy.sparse import load_npz
from sklearn.metrics.pairwise import cosine_similarity
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
PROJECT_ROOT = os.path.dirname(os.path.dirname(SCRIPT_DIR))
SEG = os.path.join(PROJECT_ROOT, "data", "segmented")
EMB = os.path.join(PROJECT_ROOT, "data", "embeddings")
df = pd.read_csv(os.path.join(SEG, "clauses_features.csv"))
embeddings = np.load(os.path.join(EMB, "sbert_embeddings.npy"))
tfidf = load_npz(os.path.join(EMB, "tfidf_matrix.npz"))
labels = np.load(os.path.join(EMB, "labels_encoded.npy"))
lines = []
def log(msg=""):
print(msg)
lines.append(msg)
log("=" * 60)
log("DATASET VALIDATION REPORT")
log("=" * 60)
# ── Basic stats ───────────────────────────────────────────
log("\n[1] CORPUS STATS")
log(f" Total clauses : {len(df)}")
log(f" Unique source docs : {df['source_doc'].nunique()}")
log(f" Domains : {dict(df['domain'].value_counts())}")
log(f" Avg word count : {df['word_count'].mean():.1f}")
log(f" Avg token count : {df['token_count'].mean():.1f}")
# ── Label distribution ────────────────────────────────────
log("\n[2] LABEL DISTRIBUTION")
counts = df['risk_label'].value_counts()
for label, cnt in counts.items():
pct = cnt / len(df) * 100
bar = "β–ˆ" * (cnt // 40)
log(f" {label:<12} {cnt:>5} ({pct:.1f}%) {bar}")
ratio = counts.max() / counts.min()
log(f"\n Imbalance ratio : {ratio:.2f}x {'βœ“ good' if ratio < 1.5 else '⚠ check'}")
# ── Feature stats ─────────────────────────────────────────
log("\n[3] LINGUISTIC FEATURE STATS")
feat_cols = ["modal_score", "consequence_score", "conditional_score",
"has_negation", "obligation_count", "penalty_flag"]
for col in feat_cols:
if col in df.columns:
log(f" {col:<25} mean={df[col].mean():.3f} std={df[col].std():.3f}")
# ── 4. Embedding sanity ──────────────────────────────────────
log("\n[4] EMBEDDING SANITY CHECKS")
log(f" SBERT shape : {embeddings.shape}")
log(f" TF-IDF shape : {tfidf.shape}")
log(f" SBERT mean norm : {np.linalg.norm(embeddings, axis=1).mean():.3f}")
log(f" Any NaN in SBERT : {np.isnan(embeddings).any()}")
log(f" Labels shape : {labels.shape}")
log(f" Labels aligned : {len(labels) == len(df)}")
# ── Inter-class separability ──────────────────────────────
log("\n[5] INTER-CLASS SEPARABILITY (SBERT cosine similarity)")
label_list = ["Critical", "High", "Medium", "Low"]
log(" Intra-class (higher = more cohesive):")
for label in label_list:
idx = df[df['risk_label'] == label].index.tolist()
if len(idx) < 2:
continue
sample = embeddings[idx[:100]]
sim = cosine_similarity(sample)
np.fill_diagonal(sim, 0)
log(f" {label:<12} {sim.mean():.3f}")
log("\n Cross-class (lower = better separation):")
for i, l1 in enumerate(label_list):
for l2 in label_list[i+1:]:
idx1 = df[df['risk_label'] == l1].index[:50]
idx2 = df[df['risk_label'] == l2].index[:50]
if len(idx1) == 0 or len(idx2) == 0:
continue
sim = cosine_similarity(embeddings[idx1], embeddings[idx2])
log(f" {l1} vs {l2:<12} {sim.mean():.3f}")
# ── Domain x label coverage ───────────────────────────────
log("\n[6] DOMAIN x LABEL COVERAGE")
pivot = df.groupby(['domain', 'risk_label']).size().unstack(fill_value=0)
for col in ["Critical", "High", "Medium", "Low"]:
if col not in pivot.columns:
pivot[col] = 0
log(pivot[["Critical", "High", "Medium", "Low"]].to_string())
# ── Readiness checklist ───────────────────────────────────
log("\n[7] READINESS CHECKLIST")
checks = {
"4 risk labels present" : df['risk_label'].nunique() == 4,
"Min 500 per class" : counts.min() >= 500,
"SBERT no NaN" : not np.isnan(embeddings).any(),
"Embeddings aligned" : len(labels) == len(df),
"TF-IDF features > 1000" : tfidf.shape[1] > 1000,
"Linguistic features exist" : all(c in df.columns for c in feat_cols),
"Imbalance ratio < 2x" : ratio < 2.0,
}
all_pass = True
for check, result in checks.items():
status = "βœ“" if result else "βœ—"
log(f" {status} {check}")
if not result:
all_pass = False
log(f"\n {'βœ“ ALL CHECKS PASSED' if all_pass else 'βœ— FIX THE ISSUES ABOVE'}")
# Save validation report
report_path = os.path.join(SEG, "validation_report.txt")
with open(report_path, "w", encoding="utf-8") as f:
f.write("\n".join(lines))
print(f"\nβœ“ Validation report saved β†’ {report_path}")
# ============================================================
# FINAL EXPORT
# ============================================================
master_cols = [
"clause_id", "source_doc", "domain",
"raw_text", "clean_text",
"word_count", "token_count",
"pattern_label", "risk_label", "risk_score",
"modal_score", "consequence_score", "conditional_score",
"has_negation", "obligation_count", "penalty_flag"
]
master_cols = [c for c in master_cols if c in df.columns]
master = df[master_cols].copy()
master_path = os.path.join(SEG, "master_dataset.csv")
master.to_csv(master_path, index=False)
print(f"\nβœ“ master_dataset.csv β†’ {master_path}")
print(f" Shape : {master.shape}")
print(f" Columns : {list(master.columns)}")
print(f"\n data/segmented/")
print(f" master_dataset.csv β€” {master.shape[0]} clauses x {master.shape[1]} columns")
print(f" clauses_balanced.csv β€” balanced training set (4000 clauses)")
print(f" clauses_labeled.csv β€” full labeled set")
print(f" validation_report.txt β€” quality report")
print(f"\n data/embeddings/")
print(f" sbert_embeddings.npy β€” {embeddings.shape} (dense semantic)")
print(f" tfidf_matrix.npz β€” {tfidf.shape} (sparse lexical)")
print(f" tfidf_vectorizer.pkl β€” fitted vectorizer for inference")
print(f" labels_encoded.npy β€” integer labels {labels.shape}")
print(f" label_encoder.pkl β€” class mapping")
print(f" embedding_index.csv β€” clause_id alignment")
print(f"\n Risk label mapping:")
print(f" Critical β†’ termination, criminal, damages")
print(f" High β†’ suspension, breach, prohibited")
print(f" Medium β†’ warning, obligation, compliance")
print(f" Low β†’ definitions, advisory, informational")