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 contractexno longer installs any LLM vendor SDK, pandas or openpyxl. Install the extra for your provider:openai,anthropic,google(nowgoogle-genai),ollama(renamed fromlocal).exportprovides pandas/openpyxl.- No default provider.
ContractExtractor(), every model-calling task andextract_contract()previously fell back to OpenAIgpt-4o; they now raiseValueErrorunless 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 reportsis_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 parameterAnthropicProviderpasses 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
secretdocument 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 aLegalDocreloaded from JSON) aspublic. - ENCRYPT redaction stored plaintext originals in the serialisable map.
PrivacyMetricsgates 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)andLegalTask.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, soSourceSpanoffsets are correct.ContractExtractor.extract_from_text()andestimate_extraction_cost_from_text().LegalRAGPipeline.ingest()acceptsLegalDocobjects with privacy profiles.- Documentation site rewritten; every code example is executed by the test suite.
SECURITY.md, issue and pull request templates.
Changed¶
ClauseAwareChunkerreturns exact substrings of the source, enforcesmax_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.tomlis the only packaging configuration; the wheel now ships the storage SQL files and the playbook schema.
Fixed¶
- The
contract_extraction,risk_analysisandclassificationtasks failed on every input;LegalRAGPipeline.ingest()failed on every source. ProvenanceTrackeroffsets 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. Usebenchmarks/.
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 requiredCrossReferenceResolver— resolves section cross-references; exposesunresolved_refsas a data-quality signalDocumentStructure— root container withsections,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 fromcontractex.structure
Playbooks (contractex.playbooks)¶
Playbook/PlaybookRule/RiskSeverity— versioned, composable risk rule sets; YAML serialize/deserialize viato_yaml_file()/from_yaml_file()StandardNDAPlaybook— 7 rules covering indemnification, liability cap, scope, term, return of information, non-compete, governing lawSaaSPlaybook— 7 rules covering liability cap, breach notification, auto-renewal, change of control, audit rights, SLA, customer data IPcontractex/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 callsObligationTimeline— obligation deadline tracking withupcoming(days),all_resolved(),unresolved();to_ical()exports standards-compliant.icsfor 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 usingdifflib.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) -> BenchmarkResultBenchmarkResult— per-typeprecision,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 fromcontractex.promptsprompt_versions: dict[str, str]field added toContractMetadataContractExtractor.extract()now populatesresult.metadata.prompt_versionsat 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.yml—mkdocs build --stricton every push tomain
Changed¶
contractex/__init__.py— updated public API; new four-layer module docstring; exportsparse_structure,DocumentStructure,Section,DefinedTerm,RiskAnalyzer,ObligationTimeline,compare_contracts,Playbook,StandardNDAPlaybook,SaaSPlaybook,CUADBenchmark,CalibrationAnalyzerpyproject.toml—docsextra updated tomkdocs-material>=9.0,mkdocstrings[python]>=0.24,mkdocs-autorefs>=0.5,mkdocs-minify-plugin>=0.7; Documentation URL updated to https://contractex.readthedocs.ioREADME.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: addedcastimport; correctly castloader.load()return toLegalDoc; castquery()return inquery_async; fixedchunk.text→chunk(chunker returnslist[str], not chunk objects)storage/graph.py: widened_graphannotation fromAny | NonetoAny(always initialised in__init__)privacy/detector.py: widened_presidio_analyzerannotation fromAny | NonetoAnyprivacy/profile.py: usedcast(Literal[...], ...)forllm_routingassignment inmodel_post_inittasks/ner.py: correctedLegalNERkeyword argumentmodel→model_nametasks/classification.py: removed non-existentmodel_namekwarg fromCUADClassifierconstructorcore/extractors.py: made_create_providera@staticmethod, resolving unbound-methodcall-argerrors intimeline,summarization,obligations, andcomparisontask modules- Installed
types-requestsstub package to resolveimport-untypedmypy error inloaders/source_adapter.py
0.3.0 - 2026-04-20¶
Added¶
Authority taxonomy — contractex.taxonomy.authority¶
AuthorityLevelIntEnumcovering thirteen levels fromCONSTITUTIONAL(100) throughBLOG_NEWS(5) andUNKNOWN(1), withnormalisedproperty mapping to[0.01, 1.00],label, andis_binding()predicateAuthorityProfilePydantic model withauthority_weightproperty (10 % of normalised level when superseded),is_superseded/superseded_byfields,for_level()class method, and optionalJurisdictionTagattachment
Structured jurisdiction model — contractex.taxonomy.jurisdiction¶
JurisdictionTagPydantic model replacing flatjurisdiction: str; ISO 3166-1 alpha-2 country codes, optional region and court system,applicabilityliteral (binding/persuasive/informational)is_broader_than(),conflicts_with()(same country, different region → conflict signal), andmatches()(hard-filter helper for RAG) methodsfrom_string()class method parses"US-CA","DE-Federal","EU", etc.; backward-compatible__str__
RAG conflict detection — contractex.rag.conflict¶
ConflictTypeenum:JURISDICTION_CONFLICT,AUTHORITY_CONFLICT,TEMPORAL_CONFLICT,UNSETTLED_QUESTIONConflictPydantic model withseverityfield (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¶
LegalRAGPipelineconstructor:alpha/beta/gammablending weights (defaults0.60/0.30/0.10) and optionalconflict_detector(created automatically by default)ingest()now writesauthority_weight,publication_year,jurisdiction_country,jurisdiction_region, andis_supersededinto vector-store metadata; storesLegalDocin an internal registry for conflict detectionquery()newjurisdiction_filter: JurisdictionTag | Noneparameter: hard-filters mismatched jurisdictions and superseded sources; appliesα·semantic + β·authority + γ·recencyweighted rescoring; runsConflictDetectorand appends conflict addendum to the LLM prompt; returnsRAGResponsewithconflictsandauthority_rangepopulated_stream_query()threadsconflicts,authority_range, andsource_docsthrough every yielded partial responseRAGResponsetwo new fields:conflicts: list[Conflict],authority_range: tuple[float, float]contractex.ragexports:Conflict,ConflictType,ConflictDetector
LLM streaming — contractex.llm¶
BaseLLMProvider.stream_complete()default implementation (single-chunk fallback) andstream_complete_async()async wrapperOpenAIProvider.stream_complete()— native streaming via OpenAIstream=TrueAnthropicProvider.stream_complete()— native streaming viaclient.messages.stream()context manager
Privacy eval harness — contractex.eval¶
EvalCasethree new fields:expected_pii_entities,should_be_blocked,expected_redaction_countPrivacyCaseResult— per-case PII precision / recall / F1, blocking correctness, redaction count accuracyPrivacyMetrics— suite-level aggregate withreport(),assert_min_pii_recall(),assert_perfect_blocking()EvalHarness.run_privacy()— extractor-agnostic runner acceptingpii_detector_fn,redactor_fn,router_fn
Storage¶
contractex/storage/schema_v2.sql— PostgreSQL schema v2:legal_docs,extracted_fields,document_chunks(pgvector),audit_log;clausesbackward-compat VIEW;gdpr_erase_document()PL/pgSQL function using HMAC-SHA256 pseudonymisationcontractex/storage/migrations/v1_to_v2.sql— transactional migration with guard, archive preservation, and full index + trigger recreation
Package extras¶
privacy—presidio-analyzer,presidio-anonymizer,cryptographyrag—sentence-transformersgraph—networkx,neo4jstream— no extra deps (marker for streaming-capable installs)allupdated to include all new extras
Changed¶
LegalDocgainsjurisdiction_tag: JurisdictionTag | Noneandauthority_profile: AuthorityProfile | Nonefields alongside the existingjurisdiction: str | None(backward compatible); computed propertieseffective_jurisdiction_tagandauthority_weightcontractex/taxonomy/__init__.pynow exportsJurisdictionTag,AuthorityLevel,AuthorityProfile
0.2.0 - 2026-04-20 — released to PyPI¶
Added¶
Network source adapters — contractex.loaders.source_adapter¶
SourceAdapter— abstract base extendingDocumentLoaderwith ETag/Last-Modified change detection, exponential-backoff retry, and SHA-256 content hashingURLLoader— 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 JSONnext-key pagination; supports Bearer/API-key authFetchCache/FetchResultdataclasses for provenance-aware fetch state
General legal document model — contractex.core.legal_document¶
DocTypeenum covering statutes, regulations, case opinions, identity documents, government forms, contracts, pleadings, correspondenceSourceSpan— 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 metadataLegalDocument— general-purpose extraction model withextracted_fields,field_confidences,provenancedict,set_field()/add_provenance()helpers,provenance_coverageproperty, and JSON/dict serialisation
Provenance tracking — contractex.utils.provenance¶
ChunkRecord— dataclass with deterministic chunk ID (index + 8-char content hash), global character offsets, page numberProvenanceTracker— registers text chunks and resolves extracted values back toSourceSpanobjects; 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¶
AuditEventTypetaxonomy:document_ingested,document_loaded,fields_extracted,field_rejected,review_requested,review_completed,document_deleted,access_denied,pipeline_errorAuditEvent— Pydantic model with auto-UUID, UTC timestamp, per-field arrays, confidence score, flexible metadataNullAuditBackend— no-op sink for testingJSONLAuditBackend— append-only newline-delimited JSON; thread-safe viathreading.Lock; auto-creates parent directories;read_all()class method for inspectionPostgresAuditBackend— writes toaudit_logtable;autocommitmode; auto-DDL on first use; thread-safeAuditLogger— thread-safe facade withlog_ingestion(),log_extraction(),log_review_request(),log_review_completion(),log_deletion(),log_error()convenience methods; backend failures are re-emitted via standardlogging— never raised to callers; factory methodsfrom_jsonl(),from_postgres(),null()
Confidence routing — contractex.utils.routing¶
RoutingDecisionenum:AUTO_ACCEPT,HUMAN_REVIEW,AUTO_REJECTReviewItem— single routed field with decision, reason, confidence, and optionalSourceSpanRoutingResult— bucketed outcome withaccepteddict,review_queue(sorted by confidence ascending),rejectedlist;needs_review,fully_accepted,acceptance_rate,review_field_names,rejected_field_namesproperties;summary()stringConfidenceRouter— global and per-field threshold overrides;route_field(),route_document(LegalDocument),route_dict()interfaces
Eval harness — contractex.eval¶
EvalCase— labeled test case withinput_path/input_text,expected_fields,field_weights, tagsEvalSuite— named collection with YAML (from_yaml()) and JSON (from_json()) loaders,filter_by_tag()/filter_by_doc_type()helpersFieldResult— pass/fail with weighted score per field; case-insensitive string comparison; strict bool equalityCaseResult— per-case aggregate withscore_ratio,passed,failed_fieldsExtractionMetrics— suite-level aggregate withfield_accuracy(weighted),case_accuracy, per-field stats table;assert_min_field_accuracy()/assert_min_case_accuracy()for pytest CI gates;report()formatted summaryEvalHarness— extractor-agnostic runner accepting any(EvalCase) -> dictcallable;fail_fastmode; error capture; per-case timing
Infrastructure¶
contractex.utils.__init__exports all new utilitiescontractex.core.__init__exportsLegalDocument,LegalDocumentMetadata,DocType,SourceSpancontractex.__init__top-level exports forProvenanceTracker,ConfidenceRouter,AuditLogger,LegalDocument,DocType,SourceSpanpyproject.tomlnew 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
AutoLoaderfor 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
RiskAnalyzeralongside 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
ContractMetadatawithout 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.mdwith dev setup and contribution guidelines