| """ |
| scripts/train_mlp.py — Train the Sydeco clause-risk MLP (legal_mlp.pkl). |
| |
| Generates synthetic labeled clause data bootstrapped from the L1 rule patterns, |
| then trains a TF-IDF + MLPClassifier sklearn Pipeline and saves it to the path |
| expected by sydeco_engine.py. |
| |
| Usage |
| ----- |
| cd ldv-backend |
| python3 scripts/train_mlp.py |
| |
| Output |
| ------ |
| ~/Desktop/sydeco_ai_core_bundle/models/legal_mlp.pkl |
| |
| Override paths: |
| SYDECO_MLP_PATH=/tmp/legal_mlp.pkl python3 scripts/train_mlp.py |
| SYDECO_CSV_PATH=/path/to/clauses.csv python3 scripts/train_mlp.py |
| |
| CSV format (two columns, with header row): |
| text,label |
| "The party waives all rights.",abusive_clause |
| "Payment within 30 days.",normal |
| |
| Valid labels: abusive_clause | payment_risk | missing_mandatory | normal |
| """ |
| from __future__ import annotations |
|
|
| import csv |
| import os |
| import pickle |
| from pathlib import Path |
|
|
| from sklearn.feature_extraction.text import TfidfVectorizer |
| from sklearn.metrics import classification_report |
| from sklearn.model_selection import train_test_split |
| from sklearn.neural_network import MLPClassifier |
| from sklearn.pipeline import Pipeline |
|
|
| |
| |
|
|
| _DATA: list[tuple[str, str]] = [ |
|
|
| |
| ("The party herewith waives all legal rights to dispute this agreement.", "abusive_clause"), |
| ("Client irrevocably waives all rights to seek any legal recourse.", "abusive_clause"), |
| ("The signatory waives all legal rights including the right to a fair hearing.", "abusive_clause"), |
| ("Party B waives any and all legal rights under applicable law.", "abusive_clause"), |
| ("All legal rights are waived by the undersigned upon signing.", "abusive_clause"), |
| ("The company may modify this agreement at any time without notice to the other party.", "abusive_clause"), |
| ("Provider can amend these terms at any time without prior notification.", "abusive_clause"), |
| ("We may change contract terms without notice at our sole discretion.", "abusive_clause"), |
| ("Party A may unilaterally change the terms of this contract without consent.", "abusive_clause"), |
| ("The service provider shall modify these terms whenever deemed appropriate without informing the client.", "abusive_clause"), |
| ("No liability whatsoever shall be incurred by the service provider under any circumstances.", "abusive_clause"), |
| ("The company bears no liability for any damages, losses, or claims of any kind.", "abusive_clause"), |
| ("Provider accepts no liability whatsoever for consequential or direct damages.", "abusive_clause"), |
| ("Vendor shall not be liable for any and all damages regardless of cause.", "abusive_clause"), |
| ("Under no circumstances shall the company be responsible for any harm to the client.", "abusive_clause"), |
| ("All profits shall be allocated exclusively to Party A regardless of Party B's contribution.", "abusive_clause"), |
| ("One party receives all the benefits while the other bears all risks and losses.", "abusive_clause"), |
| ("All financial benefits go to the investor; the operating partner assumes all losses.", "abusive_clause"), |
| ("Investor bears no loss under any scenario while retaining all profit rights.", "abusive_clause"), |
| ("Party A is entitled to all gains and is exempt from all losses arising from this contract.", "abusive_clause"), |
| ("The counterparty shall have no right to challenge or contest any decision made by the company.", "abusive_clause"), |
| ("Client surrenders all rights to legal proceedings relating to this agreement.", "abusive_clause"), |
| ("Le prestataire peut modifier le contrat à tout moment sans préavis.", "abusive_clause"), |
| ("L'investisseur ne supporte aucune perte quelle que soit la situation.", "abusive_clause"), |
| ("La partie renonce à tous ses droits de recours juridiques.", "abusive_clause"), |
| ("Alle winsten worden toegekend aan één partij; verliezen worden volledig gedragen door de andere partij.", "abusive_clause"), |
| ("De dienstverlener is in geen enkel geval aansprakelijk voor schade.", "abusive_clause"), |
| ("Pihak pertama melepaskan semua hak hukum yang dimilikinya berdasarkan perjanjian ini.", "abusive_clause"), |
| ("Investor tidak menanggung kerugian dalam keadaan apapun.", "abusive_clause"), |
| ("Penyedia layanan dapat mengubah perjanjian ini kapan saja tanpa pemberitahuan.", "abusive_clause"), |
| ("The provider may terminate services immediately and without cause at its sole discretion.", "abusive_clause"), |
| ("Company may suspend or terminate the agreement without any reason and at any time.", "abusive_clause"), |
| ("Client waives any right to compensation in the event of early termination by the provider.", "abusive_clause"), |
| ("The service provider bears no responsibility for acts of negligence by its employees.", "abusive_clause"), |
| ("All disputes shall be resolved exclusively in favour of the service provider.", "abusive_clause"), |
| ("The client forfeits all claims upon accepting these terms.", "abusive_clause"), |
| ("Party A retains the right to cancel without notice and without liability.", "abusive_clause"), |
| ("Doet afstand van alle juridische rechten op grond van deze overeenkomst.", "abusive_clause"), |
| ("Aucune responsabilité quelle qu'en soit la cause ne peut être engagée.", "abusive_clause"), |
| ("The company may at its sole discretion change pricing without prior notice.", "abusive_clause"), |
|
|
| |
| ("A penalty of 15% per day shall apply to all late payments.", "payment_risk"), |
| ("Late payment will incur a penalty fee of 20% per day on the outstanding amount.", "payment_risk"), |
| ("Interest of 25% per month shall accrue on any unpaid balance.", "payment_risk"), |
| ("A surcharge of 50% per day is applied automatically upon payment default.", "payment_risk"), |
| ("Client will be charged 30% interest per month on overdue invoices.", "payment_risk"), |
| ("Failure to pay by the due date results in a daily penalty of 12%.", "payment_risk"), |
| ("Late payment fee: 18% per day, compounded daily until settled.", "payment_risk"), |
| ("All outstanding amounts attract a 40% per month interest charge.", "payment_risk"), |
| ("Pénalité de 15% par jour s'applique en cas de paiement tardif.", "payment_risk"), |
| ("Un intérêt de 20% par mois est dû sur tout solde impayé.", "payment_risk"), |
| ("Denda 15% per hari akan dikenakan atas keterlambatan pembayaran.", "payment_risk"), |
| ("Boete van 20% per dag op het uitstaande bedrag bij te late betaling.", "payment_risk"), |
| ("Interest rate of 60% per annum applies on unpaid invoices past due date.", "payment_risk"), |
| ("Each day of delayed payment incurs an additional charge of 10% of the invoice total.", "payment_risk"), |
| ("Overdue invoices will automatically attract a 35% late payment penalty per month.", "payment_risk"), |
| ("A compound late payment interest rate of 24% per month will apply without further notice.", "payment_risk"), |
| ("The client must pay a daily penalty of 10% for each day beyond the payment deadline.", "payment_risk"), |
| ("Failure to pay within 3 days triggers a penalty of 50% of the total amount due.", "payment_risk"), |
| ("Late payment charges of 15% per week are automatically added to outstanding balances.", "payment_risk"), |
| ("An immediate surcharge of 20% is applied the day after the payment due date.", "payment_risk"), |
| ("Outstanding balances accrue interest at 120% per annum, charged daily.", "payment_risk"), |
| ("Any delay in payment results in a penalty of 25% per month, non-negotiable.", "payment_risk"), |
| ("Overdue accounts are subject to a late fee of 10% compounded monthly.", "payment_risk"), |
| ("The provider reserves the right to charge 18% interest per month on unpaid amounts.", "payment_risk"), |
| ("Payment received after the due date will be subject to a 45% penalty charge.", "payment_risk"), |
| ("A late payment penalty of 11% per day applies automatically from the day after due date.", "payment_risk"), |
| ("The interest accruing on late payments is set at 36% per annum, billed monthly.", "payment_risk"), |
| ("Rente van 15% per maand wordt in rekening gebracht op achterstallige betalingen.", "payment_risk"), |
| ("Bunga keterlambatan sebesar 20% per bulan akan dikenakan secara otomatis.", "payment_risk"), |
| ("Tout retard de paiement entraîne une pénalité de 10% par semaine.", "payment_risk"), |
|
|
| |
| ("Governing law: TBD — to be confirmed by both parties at a later stage.", "missing_mandatory"), |
| ("Dispute resolution: see attached schedule [MISSING].", "missing_mandatory"), |
| ("[Insert applicable jurisdiction here]", "missing_mandatory"), |
| ("Payment terms: to be agreed upon separately.", "missing_mandatory"), |
| ("Termination provisions: to be determined.", "missing_mandatory"), |
| ("The limitation of liability clause will be inserted prior to execution.", "missing_mandatory"), |
| ("Venue for disputes: [TO BE COMPLETED].", "missing_mandatory"), |
| ("Confidentiality terms: refer to separate NDA not yet executed.", "missing_mandatory"), |
| ("[Governing law clause to be added]", "missing_mandatory"), |
| ("Force majeure: TBD.", "missing_mandatory"), |
| ("Payment schedule: see Exhibit A [not attached].", "missing_mandatory"), |
| ("The parties shall agree on termination conditions before contract commencement.", "missing_mandatory"), |
| ("Liability cap: amount to be determined by mutual agreement.", "missing_mandatory"), |
| ("[This section intentionally left blank pending legal review]", "missing_mandatory"), |
| ("Jurisdiction: to be discussed and confirmed at a later date.", "missing_mandatory"), |
| ("Late payment provisions: refer to addendum [draft pending].", "missing_mandatory"), |
| ("Dispute resolution mechanism: to be negotiated between the parties.", "missing_mandatory"), |
| ("Section 7 — Intellectual Property: [DRAFT — INCOMPLETE]", "missing_mandatory"), |
| ("Terms of payment will be outlined in a side letter to be executed separately.", "missing_mandatory"), |
| ("Notice provisions: [insert notice details here].", "missing_mandatory"), |
| ("Warranty terms: to be finalised by the legal team.", "missing_mandatory"), |
| ("The applicable law governing this agreement has not yet been determined.", "missing_mandatory"), |
| ("[Termination clause pending review by counsel]", "missing_mandatory"), |
| ("Indemnification: refer to Schedule B [not yet drafted].", "missing_mandatory"), |
| ("The parties have not yet agreed on the venue for arbitration.", "missing_mandatory"), |
| ("Limitation of liability: see separate agreement [not enclosed].", "missing_mandatory"), |
| ("Governing jurisdiction: TBC prior to execution.", "missing_mandatory"), |
| ("This clause is reserved for future inclusion upon mutual agreement.", "missing_mandatory"), |
| ("Payment provisions: to be determined based on project scope.", "missing_mandatory"), |
| ("The termination clause has not yet been agreed upon by the parties.", "missing_mandatory"), |
|
|
| |
| ("This agreement is entered into as of the date first written above between the parties.", "normal"), |
| ("Either party may terminate this agreement upon 30 days written notice to the other party.", "normal"), |
| ("Payment shall be made within 30 days of the date of invoice by bank transfer.", "normal"), |
| ("This agreement shall be governed by the laws of Belgium.", "normal"), |
| ("Any disputes arising from this contract shall be referred to the courts of Brussels.", "normal"), |
| ("Both parties agree to maintain the confidentiality of all proprietary information.", "normal"), |
| ("The service provider shall deliver the agreed services within the specified timeframe.", "normal"), |
| ("This contract constitutes the entire agreement between the parties on this subject.", "normal"), |
| ("Intellectual property created under this agreement shall belong to the commissioning party.", "normal"), |
| ("Force majeure events shall suspend the obligations of the affected party for the duration thereof.", "normal"), |
| ("The parties may amend this agreement in writing with mutual consent of both parties.", "normal"), |
| ("Notices shall be sent by registered mail or email to the address specified herein.", "normal"), |
| ("Each party shall bear its own legal costs in connection with this agreement.", "normal"), |
| ("This agreement may not be assigned without the prior written consent of both parties.", "normal"), |
| ("The parties confirm that they have read and understood the terms of this agreement.", "normal"), |
| ("In the event of a breach, the non-breaching party shall give written notice of the breach.", "normal"), |
| ("Invoices shall be issued monthly and paid within 30 calendar days of receipt.", "normal"), |
| ("The service provider warrants that the services will be performed with reasonable skill and care.", "normal"), |
| ("Both parties shall comply with all applicable laws and regulations in performing this agreement.", "normal"), |
| ("This agreement shall remain in force for a period of one year from the date of signing.", "normal"), |
| ("Ce contrat est régi par le droit belge et tout litige sera soumis aux tribunaux de Bruxelles.", "normal"), |
| ("Het contract is onderworpen aan Belgisch recht en Nederlands recht.", "normal"), |
| ("Perjanjian ini tunduk pada hukum Republik Indonesia.", "normal"), |
| ("The landlord agrees to maintain the property in a habitable condition throughout the lease term.", "normal"), |
| ("The tenant shall pay rent on the first day of each calendar month by bank transfer.", "normal"), |
| ("Rental deposit of two months shall be held in a separate escrow account.", "normal"), |
| ("The property shall be returned in the same condition as received, subject to fair wear and tear.", "normal"), |
| ("The lessee shall not sublet the premises without prior written consent of the lessor.", "normal"), |
| ("The employer shall pay the employee a monthly gross salary as agreed in Schedule A.", "normal"), |
| ("The employee is entitled to 20 days of annual paid leave per calendar year.", "normal"), |
| ("Either party may terminate the employment with one month notice during the probation period.", "normal"), |
| ("The parties agree to resolve disputes through mediation before initiating court proceedings.", "normal"), |
| ("The contractor shall provide the services on a best-efforts basis within agreed timelines.", "normal"), |
| ("All deliverables shall conform to the specifications set out in Annexe 1.", "normal"), |
| ("The total contract value shall not exceed EUR 50,000 without prior written approval.", "normal"), |
| ("The parties shall keep all information exchanged under this agreement strictly confidential.", "normal"), |
| ("This agreement is binding on the parties and their respective successors and assigns.", "normal"), |
| ("Severability: if any provision is found invalid, the remaining provisions continue in force.", "normal"), |
| ("The borrower shall repay the loan in equal monthly instalments over 36 months.", "normal"), |
| ("Annual indexation shall follow the official consumer price index of Belgium.", "normal"), |
| ] |
|
|
|
|
| |
|
|
| _VALID_LABELS = {"abusive_clause", "payment_risk", "missing_mandatory", "normal"} |
|
|
| def load_csv(csv_path: Path) -> list[tuple[str, str]]: |
| """Load (text, label) pairs from a CSV file with 'text' and 'label' columns.""" |
| rows: list[tuple[str, str]] = [] |
| skipped = 0 |
| with open(csv_path, newline="", encoding="utf-8") as f: |
| reader = csv.DictReader(f) |
| if not {"text", "label"}.issubset(reader.fieldnames or []): |
| raise ValueError(f"CSV must have 'text' and 'label' columns, got: {reader.fieldnames}") |
| for i, row in enumerate(reader, start=2): |
| text = row["text"].strip() |
| label = row["label"].strip() |
| if not text or label not in _VALID_LABELS: |
| skipped += 1 |
| continue |
| rows.append((text, label)) |
| if skipped: |
| print(f" [CSV] skipped {skipped} rows (empty text or unknown label)") |
| return rows |
|
|
|
|
| |
|
|
| def train(save_path: Path, csv_path: Path | None = None) -> None: |
| data = list(_DATA) |
|
|
| if csv_path and csv_path.exists(): |
| csv_rows = load_csv(csv_path) |
| print(f"CSV : {csv_path} ({len(csv_rows)} rows loaded)") |
| data.extend(csv_rows) |
| elif csv_path: |
| print(f"CSV : {csv_path} not found — using synthetic data only") |
|
|
| texts = [d[0] for d in data] |
| labels = [d[1] for d in data] |
|
|
| label_set = sorted(set(labels)) |
| print(f"Classes : {label_set}") |
| print(f"Samples : {len(texts)}") |
| for lbl in label_set: |
| print(f" {lbl}: {labels.count(lbl)}") |
|
|
| X_train, X_test, y_train, y_test = train_test_split( |
| texts, labels, test_size=0.2, random_state=42, stratify=labels |
| ) |
|
|
| pipeline = Pipeline([ |
| ("tfidf", TfidfVectorizer( |
| ngram_range=(1, 3), |
| max_features=8000, |
| sublinear_tf=True, |
| min_df=1, |
| )), |
| ("clf", MLPClassifier( |
| hidden_layer_sizes=(128, 64), |
| activation="relu", |
| max_iter=800, |
| random_state=42, |
| )), |
| ]) |
|
|
| print("\nTraining …") |
| pipeline.fit(X_train, y_train) |
|
|
| y_pred = pipeline.predict(X_test) |
| print("\nValidation report:") |
| print(classification_report(y_test, y_pred, labels=label_set, zero_division=0)) |
|
|
| train_acc = pipeline.score(X_train, y_train) |
| test_acc = pipeline.score(X_test, y_test) |
| print(f"Train accuracy : {train_acc:.3f}") |
| print(f"Test accuracy : {test_acc:.3f}") |
|
|
| save_path.parent.mkdir(parents=True, exist_ok=True) |
| with open(save_path, "wb") as f: |
| pickle.dump(pipeline, f) |
| print(f"\nSaved → {save_path}") |
|
|
|
|
| if __name__ == "__main__": |
| default = Path.home() / "Desktop/sydeco_ai_core_bundle/models/legal_mlp.pkl" |
| out_path = Path(os.getenv("SYDECO_MLP_PATH", str(default))) |
|
|
| default_csv = Path(__file__).parent.parent / "data" / "clause_training_data.csv" |
| csv_env = os.getenv("SYDECO_CSV_PATH") |
| csv_path = Path(csv_env) if csv_env else default_csv |
|
|
| train(out_path, csv_path) |
|
|