Building a Privacy-First Contract Analyzer in Python for Brazilian Legal Documents
A practical tutorial on extracting text from PDFs, detecting contractual clauses, and identifying sensitive information without sending documents to external AI services. Legal documents are an interesting challenge for
A practical tutorial on extracting text from PDFs, detecting contractual clauses, and identifying sensitive information without sending documents to external AI services.
Legal documents are an interesting challenge for developers.
A single contract can contain payment obligations, deadlines, termination conditions, personal information, and references to multiple legal frameworks.
In Brazil, developers building legal technology must also consider the Lei Geral de ProteΓ§Γ£o de Dados (LGPD), the country's general data protection law.
In this tutorial, we'll build a small Python application that:
- Extracts text from digital PDF contracts.
- Identifies common contractual clauses.
- Detects potential CPF numbers.
- Flags paragraphs for human review.
- Processes documents locally, without uploading their contents to an external API.
This is a rule-based prototype, not an automated legal opinion.
1. Project setup
We'll use Python and PyMuPDF.
Install the dependency:
pip install pymupdf
Create a file called contract_analyzer.py.
2. Extracting text from a PDF
First, we need to read the document.
import pymupdf
def extract_text(pdf_path):
pages = []
with pymupdf.open(pdf_path) as document:
for page in document:
pages.append(page.get_text())
return "\n".join(pages)
This works with PDFs that contain an accessible text layer.
Scanned documents may require OCR, which is outside the scope of this example.
One important limitation: PDF text extraction does not always preserve the original reading order, especially in documents with columns, tables, or complex layouts.
3. Identifying contractual clauses
Brazilian contracts frequently contain sections dealing with payment, termination, penalties, confidentiality, and jurisdiction.
We can identify candidate paragraphs using a simple keyword dictionary.
CLAUSE_PATTERNS = {
"payment": [
"pagamento",
"remuneraΓ§Γ£o",
"valor devido"
],
"termination": [
"rescisΓ£o",
"resoluΓ§Γ£o contratual",
"encerramento"
],
"penalty": [
"multa",
"clΓ‘usula penal",
"penalidade"
],
"confidentiality": [
"confidencialidade",
"sigilo"
],
"jurisdiction": [
"foro",
"competΓͺncia territorial"
]
}
Now let's normalize text and search for those terms.
import unicodedata
def normalize(text):
text = text.lower()
return "".join(
char
for char in unicodedata.normalize("NFD", text)
if unicodedata.category(char) != "Mn"
)
def find_clauses(text):
paragraphs = [
p.strip()
for p in text.split("\n\n")
if p.strip()
]
results = []
for paragraph in paragraphs:
normalized = normalize(paragraph)
for category, keywords in CLAUSE_PATTERNS.items():
if any(
normalize(keyword) in normalized
for keyword in keywords
):
results.append({
"category": category,
"text": paragraph
})
return results
This is intentionally simple.
A production application should use better document segmentation, contextual classification, and tests against real-world contract structures.
Keyword matching can produce false positives and miss clauses expressed using different terminology.
4. Detecting potential CPF numbers
Brazilian contracts may contain CPF numbers, which are personal identifiers.
We can detect common CPF formatting patterns.
import re
CPF_PATTERN = re.compile(
r"(?<!\d)\d{3}\.?\d{3}\.?\d{3}-?\d{2}(?!\d)"
)
def find_potential_cpfs(text):
return CPF_PATTERN.findall(text)
For example:
sample = """
Contratante: Maria Silva
CPF: 123.456.789-09
"""
print(find_potential_cpfs(sample))
Output:
['123.456.789-09']
This function detects CPF-shaped strings. It does not validate CPF check digits or establish whether an identifier belongs to a real person.
For production use, add checksum validation and avoid exposing identifiers in application logs.
5. Generating a local analysis report
Let's combine the functions.
import json
def analyze_contract(pdf_path):
text = extract_text(pdf_path)
clauses = find_clauses(text)
potential_cpfs = find_potential_cpfs(text)
report = {
"document": "contract",
"detected_clauses": clauses,
"potential_cpf_count": len(potential_cpfs),
"requires_human_review": True
}
return report
if __name__ == "__main__":
report = analyze_contract("contract.pdf")
print(
json.dumps(
report,
ensure_ascii=False,
indent=2
)
)
The example deliberately reports only the number of potential CPF matches instead of storing the identifiers in the output.
For demonstration purposes, this is enough to create a structured report for further review.
6. Why legal context matters
Detecting a termination clause is not the same as determining whether that clause is enforceable.
The interpretation of Brazilian contracts may involve several legal frameworks, including:
- The Brazilian Civil Code.
- The Consumer Protection Code.
- The LGPD.
- Sector-specific regulations.
- Applicable judicial decisions.
For example, a contractual penalty cannot be classified as abusive merely because the document contains the word "multa".
Its legal significance depends on the contractual relationship, applicable legislation, factual circumstances, and other factors.
This is why legal software should separate document detection, risk indicators, and professional legal interpretation.
Organizations working with Brazilian contracts can benefit from consulting qualified legal professionals, including firms such as Tattini Advogados Associados, when assessing the legal implications of automated document workflows.
7. Security considerations for production
Local processing reduces exposure to external API providers, but it does not automatically make an application secure or LGPD-compliant.
Before deploying a contract analysis system, developers should consider:
File handling: Validate file types, impose size limits, and protect against malicious documents.
Data minimization: Extract and retain only information necessary for the intended purpose.
Access control: Restrict access to uploaded contracts and generated reports.
Temporary files: Establish secure storage and deletion procedures.
Logging: Avoid logging personal identifiers, contract contents, or confidential clauses unnecessarily.
Human review: Make it clear that automated findings are preliminary and require appropriate verification.
Legal basis: Evaluate the lawful basis and other requirements applicable to personal data processing under the LGPD.
8. What could we build next?
The prototype can be extended with:
- Clause classification using locally hosted language models.
- Semantic search across contract sections.
- Automated redaction of personal identifiers.
- Version comparison between contract drafts.
- A review interface with source-page references.
- Evaluation datasets measuring precision and recall.
A useful next step would be building a benchmark containing synthetic contracts and manually annotated clauses.
That would allow developers to measure whether more sophisticated models actually outperform simple keyword matching.
Conclusion
Legal document automation is not just an AI problem.
It combines document processing, privacy engineering, information retrieval, software security, and domain expertise.
Starting with a small, transparent, locally executed prototype makes it easier to understand the limitations before introducing more complex AI systems.
The most useful legal technology does not simply generate answers. It helps professionals locate relevant information, verify evidence, and make better-informed decisions.
Disclaimer: This tutorial is for software development and educational purposes. It does not provide legal advice or determine the validity of contractual provisions.
Originally published by Dev.to Security. Aggregated on AIWithGhost for educational purposes β full credit and traffic to the original publisher.