Skip to content

Changelog

All notable changes to Contractex are documented in this file.

The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.

Unreleased

[0.5.1] - 2026-09-13

Changed

  • The PyPI description (README) now uses the documentation home page text. The README holds that text once, and the docs home page includes it, so the two cannot drift apart. No code changes.

[0.5.0] - 2026-09-12

The first release since 0.3.1. It includes the unpublished 0.4.0 work below, fixes privacy defects that affect every earlier version, and adds reproducible benchmarks.

Breaking changes

  • pip install contractex no longer installs any LLM vendor SDK, pandas or openpyxl. Install the extra for your provider: openai, anthropic, google (now google-genai), ollama (renamed from local). export provides pandas/openpyxl.
  • No default provider. ContractExtractor(), every model-calling task and extract_contract() previously fell back to OpenAI gpt-4o; they now raise ValueError unless given a provider. Bare vendor names ("openai", "anthropic") are rejected; pass a full model name.
  • PDF loading uses pypdfium2 (BSD-3-Clause / Apache-2.0) instead of PyMuPDF (AGPL-3.0). PDFLoader.get_metadata() no longer reports is_encrypted.
  • Python 3.11 or newer is required (was 3.9).
  • The Anthropic extra is pinned to anthropic<1.0: SDK 1.0 removed a parameter AnthropicProvider passes on every call.
  • Removed extras that installed nothing used: cloud, chroma, stream, datasets, retrieval, eval (PyYAML is now a core dependency).

Security

Privacy defects present in every published version:

  • Without Presidio, email addresses, US Social Security numbers, card numbers and IBANs were never redacted (regex scores fell below the thresholds). Zero-width characters, Unicode dashes, fullwidth digits, homoglyphs and overlapping matches also let personal data through.
  • Nothing called the privacy router. Tasks, pipelines and RAG sent text straight to the provider, so a secret document reached the model. Every built-in model call now goes through the router, and the comparison task enforces its second document's profile.
  • The router redacted only the first call per document, treated any class named *Local* as local, and read a profile that was a dict (for example a LegalDoc reloaded from JSON) as public.
  • ENCRYPT redaction stored plaintext originals in the serialisable map.
  • PrivacyMetrics gates could pass with no router supplied, and scored crashing detectors and routers as successes.

Added

  • benchmarks/: one command (python -m benchmarks) measures chunk integrity and provenance on the CUAD v1 test split (102 contracts, CC BY 4.0) and PII detection and privacy routing on a synthetic fixture. Results are committed, embedded in the README and docs, and CI fails if they differ from a fresh run.
  • PrivacyAwareLLMRouter.guard(provider, *docs) and LegalTask.llm_for(*docs): a provider wrapper that enforces the strictest profile of the given documents on every call, streaming included.
  • ProvenanceTracker.register_chunks(..., source_text=...) locates chunks in the source, so SourceSpan offsets are correct.
  • ContractExtractor.extract_from_text() and estimate_extraction_cost_from_text().
  • LegalRAGPipeline.ingest() accepts LegalDoc objects with privacy profiles.
  • Documentation site rewritten; every code example is executed by the test suite. SECURITY.md, issue and pull request templates.

Changed

  • ClauseAwareChunker returns exact substrings of the source, enforces max_chunk_size, starts overlap at a sentence or word boundary, and keeps headings with their text. A numbered heading must be followed by a capital letter, ( or a quote.
  • Redaction placeholders are numbered in document order.
  • Package metadata names the author and the current repository.
  • pyproject.toml is the only packaging configuration; the wheel now ships the storage SQL files and the playbook schema.

Fixed

  • The contract_extraction, risk_analysis and classification tasks failed on every input; LegalRAGPipeline.ingest() failed on every source.
  • ProvenanceTracker offsets did not index the source document.
  • The storage configuration defaulted to the maintainer's database user.

Deprecated

  • CUADBenchmark: it cannot load the current CUAD release. Use benchmarks/.

Removed

  • setup.py, requirements.txt, pytest.ini, install.sh, .readthedocs.yaml, demo.ipynb, ARCHITECTURE.md, CLAUSE_RETRIEVAL_GUIDE.md, and example scripts that did not run.
  • Unsourced accuracy and cost figures from the documentation.

[0.4.0] - never published

Dated 2026-04-21 in this changelog but never uploaded to PyPI; these changes ship in the next release. The ReadTheDocs site mentioned below was never live.

Added

Layer 1 — Deterministic structural parse (contractex.structure)

  • ContractStructureParser.parse(text) -> DocumentStructure — zero LLM calls; handles 8 numbering schemes (numeric, deep-numeric, alpha-numeric, named-numeric, lettered, Article I/II/III, ALL-CAPS blocks, schedules/exhibits)
  • DefinedTermsRegistry — two-pass defined-terms extraction and usage-site mapping; no LLM required
  • CrossReferenceResolver — resolves section cross-references; exposes unresolved_refs as a data-quality signal
  • DocumentStructure — root container with sections, defined_terms, cross_references, signature_blocks, schedules, recitals, governing_law_hint, warnings; resolve_ref(), iter_all_sections()
  • Emits typed ParseWarning (codes: NO_SECTIONS_FOUND, DUPLICATE_SECTION_NUMBER, MIXED_NUMBERING_SCHEMES) instead of silently failing
  • parse_structure(text) top-level entry point exported from contractex.structure

Playbooks (contractex.playbooks)

  • Playbook / PlaybookRule / RiskSeverity — versioned, composable risk rule sets; YAML serialize/deserialize via to_yaml_file() / from_yaml_file()
  • StandardNDAPlaybook — 7 rules covering indemnification, liability cap, scope, term, return of information, non-compete, governing law
  • SaaSPlaybook — 7 rules covering liability cap, breach notification, auto-renewal, change of control, audit rights, SLA, customer data IP
  • contractex/playbooks/schema.yaml — YAML schema template for custom playbooks

Layer 3 — Analysis (contractex.analysis)

  • RiskAnalyzer(playbook) — deterministic playbook-based risk scoring; analyze(result) -> list[RiskFlag], missing_clauses(result) -> list[str]; zero LLM calls
  • ObligationTimeline — obligation deadline tracking with upcoming(days), all_resolved(), unresolved(); to_ical() exports standards-compliant .ics for Google Calendar / Outlook; handles ISO dates, US dates, and relative phrases ("within 30 days", "sixty (60) days after")
  • compare_contracts(result_v1, result_v2) -> ContractDiff — clause-level diff using difflib.SequenceMatcher; ContractDiff.summary() reports added, removed, and modified clauses

Eval (contractex.eval)

  • CUADBenchmark(extractor) — evaluates extraction quality against the CUAD dataset (510 commercial contracts, 41 clause types); run(n_contracts, split, progress) -> BenchmarkResult
  • BenchmarkResult — per-type precision, recall, F1, avg_confidence; macro_precision(), macro_recall(), macro_f1(), summary() (formatted table), calibration_plot(save_path)
  • CalibrationAnalyzer — builds reliability diagrams and computes Expected Calibration Error (ECE) from labeled extraction results

Prompt versioning

  • Version constants added to all four prompt modules (CLAUSE_EXTRACTION_PROMPT_VERSION, FINANCIAL_EXTRACTION_PROMPT_VERSION, PARTY_EXTRACTION_PROMPT_VERSION, RISK_ANALYSIS_PROMPT_VERSION)
  • PROMPT_VERSIONS: dict[str, str] exported from contractex.prompts
  • prompt_versions: dict[str, str] field added to ContractMetadata
  • ContractExtractor.extract() now populates result.metadata.prompt_versions at runtime

Documentation

  • MkDocs + Material documentation system (mkdocs.yml, 15 pages across Getting Started, How-To Guides, API Reference, Explanation)
  • .readthedocs.yaml — Read the Docs v2 config; hosted at https://contractex.readthedocs.io
  • GitHub Actions docs.ymlmkdocs build --strict on every push to main

Changed

  • contractex/__init__.py — updated public API; new four-layer module docstring; exports parse_structure, DocumentStructure, Section, DefinedTerm, RiskAnalyzer, ObligationTimeline, compare_contracts, Playbook, StandardNDAPlaybook, SaaSPlaybook, CUADBenchmark, CalibrationAnalyzer
  • pyproject.tomldocs extra updated to mkdocs-material>=9.0, mkdocstrings[python]>=0.24, mkdocs-autorefs>=0.5, mkdocs-minify-plugin>=0.7; Documentation URL updated to https://contractex.readthedocs.io
  • README.md — trimmed to 56-line front door with prominent docs link

Removed

  • examples/fastapi_service.py — ContractEx is a library; users implement their own service layer

[0.3.1] - 2026-04-20

Fixed

  • Resolved all 41 mypy type-check errors across 11 source files that caused CI failures after the 0.3.0 release
  • rag/pipeline.py: added cast import; correctly cast loader.load() return to LegalDoc; cast query() return in query_async; fixed chunk.textchunk (chunker returns list[str], not chunk objects)
  • storage/graph.py: widened _graph annotation from Any | None to Any (always initialised in __init__)
  • privacy/detector.py: widened _presidio_analyzer annotation from Any | None to Any
  • privacy/profile.py: used cast(Literal[...], ...) for llm_routing assignment in model_post_init
  • tasks/ner.py: corrected LegalNER keyword argument modelmodel_name
  • tasks/classification.py: removed non-existent model_name kwarg from CUADClassifier constructor
  • core/extractors.py: made _create_provider a @staticmethod, resolving unbound-method call-arg errors in timeline, summarization, obligations, and comparison task modules
  • Installed types-requests stub package to resolve import-untyped mypy error in loaders/source_adapter.py

0.3.0 - 2026-04-20

Added

Authority taxonomy — contractex.taxonomy.authority

  • AuthorityLevel IntEnum covering thirteen levels from CONSTITUTIONAL (100) through BLOG_NEWS (5) and UNKNOWN (1), with normalised property mapping to [0.01, 1.00], label, and is_binding() predicate
  • AuthorityProfile Pydantic model with authority_weight property (10 % of normalised level when superseded), is_superseded / superseded_by fields, for_level() class method, and optional JurisdictionTag attachment

Structured jurisdiction model — contractex.taxonomy.jurisdiction

  • JurisdictionTag Pydantic model replacing flat jurisdiction: str; ISO 3166-1 alpha-2 country codes, optional region and court system, applicability literal (binding / persuasive / informational)
  • is_broader_than(), conflicts_with() (same country, different region → conflict signal), and matches() (hard-filter helper for RAG) methods
  • from_string() class method parses "US-CA", "DE-Federal", "EU", etc.; backward-compatible __str__

RAG conflict detection — contractex.rag.conflict

  • ConflictType enum: JURISDICTION_CONFLICT, AUTHORITY_CONFLICT, TEMPORAL_CONFLICT, UNSETTLED_QUESTION
  • Conflict Pydantic model with severity field (high / medium / low)
  • ConflictDetector — exhaustive pairwise comparison across retrieved source docs; detect(docs, query) returns all conflicts; build_conflict_prompt_addendum(conflicts) appends structured warning to RAG prompts, preventing false-consensus summarisation

RAG pipeline enhancements — contractex.rag.pipeline

  • LegalRAGPipeline constructor: alpha / beta / gamma blending weights (defaults 0.60 / 0.30 / 0.10) and optional conflict_detector (created automatically by default)
  • ingest() now writes authority_weight, publication_year, jurisdiction_country, jurisdiction_region, and is_superseded into vector-store metadata; stores LegalDoc in an internal registry for conflict detection
  • query() new jurisdiction_filter: JurisdictionTag | None parameter: hard-filters mismatched jurisdictions and superseded sources; applies α·semantic + β·authority + γ·recency weighted rescoring; runs ConflictDetector and appends conflict addendum to the LLM prompt; returns RAGResponse with conflicts and authority_range populated
  • _stream_query() threads conflicts, authority_range, and source_docs through every yielded partial response
  • RAGResponse two new fields: conflicts: list[Conflict], authority_range: tuple[float, float]
  • contractex.rag exports: Conflict, ConflictType, ConflictDetector

LLM streaming — contractex.llm

  • BaseLLMProvider.stream_complete() default implementation (single-chunk fallback) and stream_complete_async() async wrapper
  • OpenAIProvider.stream_complete() — native streaming via OpenAI stream=True
  • AnthropicProvider.stream_complete() — native streaming via client.messages.stream() context manager

Privacy eval harness — contractex.eval

  • EvalCase three new fields: expected_pii_entities, should_be_blocked, expected_redaction_count
  • PrivacyCaseResult — per-case PII precision / recall / F1, blocking correctness, redaction count accuracy
  • PrivacyMetrics — suite-level aggregate with report(), assert_min_pii_recall(), assert_perfect_blocking()
  • EvalHarness.run_privacy() — extractor-agnostic runner accepting pii_detector_fn, redactor_fn, router_fn

Storage

  • contractex/storage/schema_v2.sql — PostgreSQL schema v2: legal_docs, extracted_fields, document_chunks (pgvector), audit_log; clauses backward-compat VIEW; gdpr_erase_document() PL/pgSQL function using HMAC-SHA256 pseudonymisation
  • contractex/storage/migrations/v1_to_v2.sql — transactional migration with guard, archive preservation, and full index + trigger recreation

Package extras

  • privacypresidio-analyzer, presidio-anonymizer, cryptography
  • ragsentence-transformers
  • graphnetworkx, neo4j
  • stream — no extra deps (marker for streaming-capable installs)
  • all updated to include all new extras

Changed

  • LegalDoc gains jurisdiction_tag: JurisdictionTag | None and authority_profile: AuthorityProfile | None fields alongside the existing jurisdiction: str | None (backward compatible); computed properties effective_jurisdiction_tag and authority_weight
  • contractex/taxonomy/__init__.py now exports JurisdictionTag, AuthorityLevel, AuthorityProfile

0.2.0 - 2026-04-20 — released to PyPI

Added

Network source adapters — contractex.loaders.source_adapter

  • SourceAdapter — abstract base extending DocumentLoader with ETag/Last-Modified change detection, exponential-backoff retry, and SHA-256 content hashing
  • URLLoader — fetches arbitrary HTTP/HTTPS URLs; strips HTML via stdlib parser; delegates PDF URLs to PyMuPDF; supports conditional GET (304 Not Modified)
  • APILoader — fetches JSON REST APIs; extracts text via dot-path ("data.opinion.text"); handles RFC 5988 Link-header and JSON next-key pagination; supports Bearer/API-key auth
  • FetchCache / FetchResult dataclasses for provenance-aware fetch state
  • DocType enum covering statutes, regulations, case opinions, identity documents, government forms, contracts, pleadings, correspondence
  • SourceSpan — precise field provenance: chunk ID, source URL, page, character offsets, snippet (auto-truncated to 300 chars)
  • LegalDocumentMetadata — fetch provenance (ETag, Last-Modified, retrieval timestamp, content hash) plus processing metadata
  • LegalDocument — general-purpose extraction model with extracted_fields, field_confidences, provenance dict, set_field() / add_provenance() helpers, provenance_coverage property, and JSON/dict serialisation

Provenance tracking — contractex.utils.provenance

  • ChunkRecord — dataclass with deterministic chunk ID (index + 8-char content hash), global character offsets, page number
  • ProvenanceTracker — registers text chunks and resolves extracted values back to SourceSpan objects; two-pass resolution: exact substring O(n) then Jaccard token-overlap fallback; annotate() / annotate_all() one-liner helpers; coverage() statistics

Audit logging — contractex.utils.audit

  • AuditEventType taxonomy: document_ingested, document_loaded, fields_extracted, field_rejected, review_requested, review_completed, document_deleted, access_denied, pipeline_error
  • AuditEvent — Pydantic model with auto-UUID, UTC timestamp, per-field arrays, confidence score, flexible metadata
  • NullAuditBackend — no-op sink for testing
  • JSONLAuditBackend — append-only newline-delimited JSON; thread-safe via threading.Lock; auto-creates parent directories; read_all() class method for inspection
  • PostgresAuditBackend — writes to audit_log table; autocommit mode; auto-DDL on first use; thread-safe
  • AuditLogger — thread-safe facade with log_ingestion(), log_extraction(), log_review_request(), log_review_completion(), log_deletion(), log_error() convenience methods; backend failures are re-emitted via standard logging — never raised to callers; factory methods from_jsonl(), from_postgres(), null()

Confidence routing — contractex.utils.routing

  • RoutingDecision enum: AUTO_ACCEPT, HUMAN_REVIEW, AUTO_REJECT
  • ReviewItem — single routed field with decision, reason, confidence, and optional SourceSpan
  • RoutingResult — bucketed outcome with accepted dict, review_queue (sorted by confidence ascending), rejected list; needs_review, fully_accepted, acceptance_rate, review_field_names, rejected_field_names properties; summary() string
  • ConfidenceRouter — global and per-field threshold overrides; route_field(), route_document(LegalDocument), route_dict() interfaces

Eval harness — contractex.eval

  • EvalCase — labeled test case with input_path / input_text, expected_fields, field_weights, tags
  • EvalSuite — named collection with YAML (from_yaml()) and JSON (from_json()) loaders, filter_by_tag() / filter_by_doc_type() helpers
  • FieldResult — pass/fail with weighted score per field; case-insensitive string comparison; strict bool equality
  • CaseResult — per-case aggregate with score_ratio, passed, failed_fields
  • ExtractionMetrics — suite-level aggregate with field_accuracy (weighted), case_accuracy, per-field stats table; assert_min_field_accuracy() / assert_min_case_accuracy() for pytest CI gates; report() formatted summary
  • EvalHarness — extractor-agnostic runner accepting any (EvalCase) -> dict callable; fail_fast mode; error capture; per-case timing

Infrastructure

  • contractex.utils.__init__ exports all new utilities
  • contractex.core.__init__ exports LegalDocument, LegalDocumentMetadata, DocType, SourceSpan
  • contractex.__init__ top-level exports for ProvenanceTracker, ConfidenceRouter, AuditLogger, LegalDocument, DocType, SourceSpan
  • pyproject.toml new optional extras: network (requests), eval (pyyaml)
  • 198 unit tests across 6 new test files; all network calls mocked; no database required

0.1.1 - 2026-02-13

Added

  • Google Gemini LLM provider (GoogleProvider) with support for gemini-2.0-flash, gemini-2.5-pro, and other Gemini models
  • Plain text document loader (TextLoader) for .txt files with automatic encoding detection
  • Support for .txt files in AutoLoader for simplified contract loading

0.1.0 - 2026-02-13

Added

  • Full LLM extraction pipeline (_extract_from_chunks) supporting OpenAI, Anthropic, and local Ollama models
  • Multi-phase extraction: contract metadata + parties (Phase 1), clause + financial per-chunk (Phase 2), deduplication (Phase 3)
  • CUAD taxonomy with 41 clause types embedded in prompt templates for accurate LLM classification
  • Exponential-backoff retry logic in all three LLM providers (rate limits, network errors, 5xx responses)
  • ContractExtractor.estimate_extraction_cost() for pre-flight cost/token estimation with per-phase breakdown
  • LLM-based risk analysis wired into RiskAnalyzer alongside existing keyword rule engine
  • Internal Pydantic schemas (LLMContractInfoResponse, LLMClausesResponse, etc.) bridging LLM output to public models
  • Parallel chunk processing via ThreadPoolExecutor (up to 4 workers)
  • Graceful degradation: per-chunk LLM failures add warnings to ContractMetadata without crashing
  • Pydantic models for Contract, Party, Clause, FinancialTerm, RiskFlag, ContractMetadata
  • Storage layer with PostgreSQL + pgvector
  • Hybrid clause retrieval with Reciprocal Rank Fusion reranking
  • Document loaders for PDF and DOCX
  • Clause-aware and semantic chunking strategies
  • Export utilities (JSON, CSV, Excel)
  • LangChain compatibility layer
  • Batch processing and async extraction support
  • Confidence scoring and validation
  • Comprehensive examples (basic, advanced, batch, local LLM, FastAPI, storage, NER, datasets)
  • CI/CD with GitHub Actions (tests, type checking, linting, PyPI publish workflow)
  • CONTRIBUTING.md with dev setup and contribution guidelines