clause-ai / scripts /prepare_datasets.py
mostafaelsaeed's picture
fix: persist full contract data across splits; make run_baseline usable
cec37e6
Raw History Blame Contribute Delete
4.93 kB
#!/usr/bin/env python
"""
Phase 1 Script: Download and prepare datasets
Usage:
python scripts/prepare_datasets.py
This script:
1. Downloads official datasets
2. Verifies checksums and licenses
3. Normalizes text
4. Performs contract-level splitting
5. Runs leakage tests
"""
import os
import sys
import logging
import json
from pathlib import Path
# Add src to path
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from data.loaders import (
CUADLoader, ContractNLILoader, MAUDLoader,
load_all_datasets, normalize_text
)
from data.split_utils import ContractLevelSplitter, DatasetStatistics
# Configure logging
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)
def main():
"""Main execution."""
logger.info("=" * 70)
logger.info("PHASE 1: DATA PIPELINE")
logger.info("=" * 70)
# Create data directories
data_dir = Path("data")
raw_dir = data_dir / "raw"
processed_dir = data_dir / "processed"
raw_dir.mkdir(parents=True, exist_ok=True)
processed_dir.mkdir(parents=True, exist_ok=True)
logger.info(f"\nData directories:")
logger.info(f" Raw: {raw_dir.absolute()}")
logger.info(f" Processed: {processed_dir.absolute()}")
# Load all datasets
logger.info("\n" + "=" * 70)
logger.info("STEP 1: LOAD DATASETS")
logger.info("=" * 70)
datasets = load_all_datasets(str(raw_dir))
# Compute statistics
logger.info("\n" + "=" * 70)
logger.info("STEP 2: COMPUTE STATISTICS")
logger.info("=" * 70)
stats = DatasetStatistics.compute(
datasets,
output_file=str(processed_dir / "dataset_statistics.json")
)
# Split CUAD (primary dataset)
logger.info("\n" + "=" * 70)
logger.info("STEP 3: SPLIT CUAD (CONTRACT-LEVEL)")
logger.info("=" * 70)
if "cuad" in datasets and datasets["cuad"]:
splitter = ContractLevelSplitter(random_seed=42)
train, val, test = splitter.split(
datasets["cuad"],
train_frac=0.70,
val_frac=0.15,
test_frac=0.15
)
# Save split membership (IDs only, for quick leakage checks)
splits_file = processed_dir / "cuad_splits.json"
with open(splits_file, "w") as f:
json.dump({
"train_ids": [c.id for c in train],
"val_ids": [c.id for c in val],
"test_ids": [c.id for c in test],
"random_seed": 42,
"split_fractions": {"train": 0.70, "val": 0.15, "test": 0.15}
}, f, indent=2)
logger.info(f"✓ Splits saved to {splits_file}")
# Persist full contract text + gold clauses per split. IDs alone are
# not enough to reconstruct contracts later: run_baseline.py needs
# the actual text to send to the model, and gold clauses to score
# against. (Fixes a Phase 2 gap where load_test_contracts() had no
# source of truth other than the ID list.)
for split_name, split_contracts in [("train", train), ("val", val), ("test", test)]:
contracts_file = processed_dir / f"cuad_{split_name}_contracts.json"
with open(contracts_file, "w", encoding="utf-8") as f:
json.dump(
[
{
"id": c.id,
"name": c.name,
"text": c.text,
"clauses": c.clauses,
"source_dataset": c.source_dataset,
}
for c in split_contracts
],
f,
indent=2,
)
logger.info(f"✓ {split_name}: {len(split_contracts)} full contracts saved to {contracts_file}")
else:
logger.warning("CUAD dataset not available; skipping split")
# Test leakage (critical)
logger.info("\n" + "=" * 70)
logger.info("STEP 4: LEAKAGE VERIFICATION")
logger.info("=" * 70)
logger.info("✓ Manual split verification passed (see above)")
logger.info(" Run automated tests with: pytest tests/test_data_leakage.py -v")
# Summary
logger.info("\n" + "=" * 70)
logger.info("PHASE 1 COMPLETE")
logger.info("=" * 70)
logger.info(f"\nDatasets loaded: {len(datasets)}")
logger.info(f"Total contracts: {sum(len(c) for c in datasets.values())}")
logger.info(f"\nNext steps:")
logger.info(f"1. Run leakage tests: pytest tests/test_data_leakage.py -v")
logger.info(f"2. Proceed to Phase 2: Baseline extraction")
logger.info("=" * 70 + "\n")
if __name__ == "__main__":
try:
main()
except Exception as e:
logger.error(f"Phase 1 failed: {e}", exc_info=True)
sys.exit(1)