File size: 4,189 Bytes
1b2d52d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
"""
Generate NLI (premise, hypothesis, label) triples for fine-tuning
typeform/distilbert-base-uncased-mnli on legal clause classification.

Sources:
  - scripts/train_mlp._DATA (160 synthetic full-text clause sentences)
  - data/clause_training_data.csv (keyword-level examples)

Output: data/nli_training_data.jsonl

Usage:
    cd ldv-backend && python3 scripts/generate_nli_training_data.py
"""
from __future__ import annotations

import csv
import json
import sys
from pathlib import Path

sys.path.insert(0, str(Path(__file__).parent.parent))
from scripts.train_mlp import _DATA as SYNTH_DATA  # noqa: E402

# Hypothesis → expected NLI label for each clause class
_HYPOTHESES: dict[str, list[tuple[str, str]]] = {
    "abusive_clause": [
        ("This clause is abusive and heavily favors one party.", "entailment"),
        ("This is a balanced and fair contractual provision.", "contradiction"),
    ],
    "payment_risk": [
        ("This clause imposes excessive payment penalties or interest rates.", "entailment"),
        ("This clause describes standard and reasonable payment terms.", "contradiction"),
    ],
    "missing_mandatory": [
        ("This clause is incomplete, a placeholder, or missing key terms.", "entailment"),
        ("This clause is clearly defined and complete.", "contradiction"),
    ],
    "normal": [
        ("This clause is abusive and heavily favors one party.", "contradiction"),
        ("This clause imposes excessive payment penalties or interest rates.", "contradiction"),
        ("This clause is incomplete, a placeholder, or missing key terms.", "contradiction"),
        ("This is a standard and complete contractual provision.", "entailment"),
    ],
}


def _from_master_reasons(datasets_dir: Path) -> list[dict]:
    """Pull Reason text from dangerous_clauses_MASTER.csv as abusive_clause premises."""
    rows: list[dict] = []
    path = datasets_dir / "dangerous_clauses_MASTERv2.csv"
    if not path.exists():
        path = datasets_dir / "dangerous_clauses_MASTER.csv"  # ponytail: fallback to v1
    if not path.exists():
        return rows
    with open(path, newline="", encoding="utf-8-sig") as f:
        for row in csv.DictReader(f):
            reason = (row.get("Reason") or "").strip()
            if not reason or len(reason) < 20:
                continue
            for hyp, nli_label in _HYPOTHESES["abusive_clause"]:
                rows.append({"premise": reason, "hypothesis": hyp, "label": nli_label})
    return rows


def generate(out_path: Path, csv_path: Path, datasets_dir: Path | None = None) -> None:
    rows: list[dict] = []

    for text, label in SYNTH_DATA:
        for hyp, nli_label in _HYPOTHESES.get(label, []):
            rows.append({"premise": text, "hypothesis": hyp, "label": nli_label})

    if csv_path.exists():
        with open(csv_path, newline="", encoding="utf-8") as f:
            for row in csv.DictReader(f):
                text  = row.get("text", "").strip()
                label = row.get("label", "").strip()
                if not text or label not in _HYPOTHESES:
                    continue
                for hyp, nli_label in _HYPOTHESES[label]:
                    rows.append({"premise": text, "hypothesis": hyp, "label": nli_label})

    if datasets_dir:
        master_rows = _from_master_reasons(datasets_dir)
        rows.extend(master_rows)
        if master_rows:
            print(f"  +{len(master_rows)} rows from dangerous_clauses_MASTER.csv (Reason field)")

    out_path.parent.mkdir(parents=True, exist_ok=True)
    with open(out_path, "w", encoding="utf-8") as f:
        for row in rows:
            f.write(json.dumps(row, ensure_ascii=False) + "\n")

    counts: dict[str, int] = {}
    for r in rows:
        counts[r["label"]] = counts.get(r["label"], 0) + 1

    print(f"Wrote {len(rows)} NLI triples → {out_path}")
    for k in sorted(counts):
        print(f"  {k}: {counts[k]}")


if __name__ == "__main__":
    base = Path(__file__).parent.parent
    generate(
        out_path=base / "data" / "nli_training_data.jsonl",
        csv_path=base / "data" / "clause_training_data.csv",
        datasets_dir=base.parent / "datasets",
    )