Architektura platformy e-Discovery: příjem, zpracování a kontrola umělé inteligence
V roce 2024 přijetí umělé inteligence v platformách e-Discovery legální a během jediného roku vyskočila z 19 % na 79 %. Tyto údaje nejsou statistickou kuriozitou: odrážejí ji strukturální transformace ve způsobu, jakým právní firmy, společnosti a soudní orgány řídí listinné důkazy v civilním a trestním řízení. Když soudní spor zahrnuje miliony e-mailů, zpráv Slack, dokumenty a soubory protokolu SharePoint, tradiční model ruční kontroly paralegal již není ekonomicky ani logisticky udržitelné.
L'e-Discovery (Electronic Discovery) a proces, kterým strany řízení právně identifikovat, shromažďovat, uchovávat, zpracovávat, kontrolovat a vytvářet listinné důkazy v elektronickém formátu. Ve Spojených státech a regulované Federálními pravidly občanského soudního řízení (FRCP), v Evropě z podobných rámců na národní úrovni. Moderní platformy potřebují zpracovat petabajty dat, respektovat právně platné řetězce úschovy a snížit objem dokumentů, které mají být přezkoumány udržování velmi vysoké zpětné dostupnosti: žádný relevantní dokument nemůže být ztracen.
V tomto článku vytváříme kompletní architekturu podnikové platformy e-Discovery: od masivní požití heterogenních dokumentů distribuované zpracování, z deduplikace al prediktivní kódování s modely AI, nahoru doexport ve formátu EDRM XML. Vše se skutečnými příklady a analýzou kódu Pythonu z předních platforem na trhu.
Co se dozvíte v tomto článku
- Model EDRM (Electronic Discovery Reference Model) a jeho fáze
- Architektura mikroslužeb pro masivní příjem: Kafka, Elasticsearch, MinIO
- Průběh zpracování: extrakce textu, metadata, deduplikace MD5/SHA a téměř dedup
- Technologie Assisted Review (TAR) a kontinuální aktivní učení (CAL)
- Prediktivní kódování se scikit-learn a větnými transformátory
- Řízení spotřebitelského řetězce a neměnná auditní stopa
- Srovnání platforem: Relativity, DISCO, Everlaw, Logikcull
- EDRM XML export a integrace se systémy správy případů
Pozice v řadě LegalTech a AI
| # | Položka | Soustředit |
|---|---|---|
| 1 | NLP pro analýzu smluv | OCR, NER, klasifikace vět |
| 2 | Jste zde — e-Discovery Architecture | Příjem, zpracování, kontrola AI |
| 3 | Compliance Automation | Engine pravidel a RegTech |
| 4 | Chytré smlouvy | Solidnost, Vyper, vymahatelnost |
| 5 | Shrnutí pomocí generativní umělé inteligence | LLM, RAG, ověření výstupu |
| 6 | Právní vyhledávač | Vektorové vložení a sémantické vyhledávání |
| 7 | Digitální podpis a eIDAS 2.0 | PKI, timestamping, workflow |
| 8 | Systémy dodržování GDPR | Privacy by design, DSR, mapování dat |
| 9 | Legální AI Copilot | RAG na právní korpus, mantinely |
| 10 | Integrace dat LegalTech | ECLI, API soudní systémy, XBRL |
Model EDRM: Referenční rámec pro e-Discovery
L'Referenční model elektronického zjišťování (EDRM) a de facto standard, který popisuje fáze procesu elektronického objevování. Model byl vyvinutý v roce 2005 a neustále aktualizován definuje devět po sobě jdoucích fází, které musí podporovat každá podniková platforma.
| EDRM fáze | Popis | Technická složka |
|---|---|---|
| 1. Správa informací | Zásady uchovávání, klasifikace dat, mapa dat | MDM, engine politik, CMDB |
| 2. Identifikace | Najděte potenciální správce a relevantní zdroje dat | Crawler, skenování adresářů, dotaz LDAP |
| 3. Konzervace | Blokování z právního hlediska: zmrazte data, abyste se vyhnuli okradení | Správa držení, neměnné úložiště, pracovní postup upozornění |
| 4.Sbírka | Forenzní sbírka s spotřebitelským řetězcem | Forenzní sběratel, ověření hashe, protokol úschovy |
| 5.Zpracování | Extrakce textu, metadata, dedup, NIST filtrování | Apache Tika, Elasticsearch ingest potrubí |
| 6.Recenze | Klasifikace relevance/privilegia, TAR/CAL | Prediktivní kódování, aktivní učení, platforma pro recenze |
| 7. Analýza | Vzor, časová osa, síť entit, modelování témat | Grafová analytika, NLP, LDA/BERTopic |
| 8. Výroba | Export v dohodnutém formátu (TIFF, nativní, PDF) | EDRM XML export, Batesovo číslování, redakční engine |
| 9. Prezentace | Zkušební prezentace, výpovědi, vizuální časové osy | Ředitel zkoušek, správa exponátů |
Microservices Architecture pro Enterprise e-Discovery
Moderní platforma e-Discovery nemůže být monolit. Objemy dat se liší od několika málo gigabajtů až stovek terabajtů pro velké případy. Architektura musí být elasticky škálovatelné, odolné vůči chybám a zajišťují neměnné auditní záznamy splňovat požadavky na způsobilost pro důkazy. Kombinuje se konsolidovaný architektonický vzor streamování událostí, ukládání objektů a distribuovaný vyhledávač.
# docker-compose.yml per ambiente e-Discovery locale
version: '3.9'
services:
# Message broker per ingestion asincrona
kafka:
image: confluentinc/cp-kafka:7.5.0
environment:
KAFKA_BROKER_ID: 1
KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181
KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://kafka:9092
KAFKA_AUTO_CREATE_TOPICS_ENABLE: 'true'
KAFKA_NUM_PARTITIONS: 12
depends_on: [zookeeper]
zookeeper:
image: confluentinc/cp-zookeeper:7.5.0
environment:
ZOOKEEPER_CLIENT_PORT: 2181
# Object storage per documenti originali e derivati
minio:
image: minio/minio:RELEASE.2024-01-16T16-07-38Z
command: server /data --console-address ":9001"
environment:
MINIO_ROOT_USER: ediscovery
MINIO_ROOT_PASSWORD: ${MINIO_PASSWORD}
volumes:
- minio_data:/data
# Search engine per full-text e metadata search
elasticsearch:
image: docker.elastic.co/elasticsearch/elasticsearch:8.12.0
environment:
- discovery.type=single-node
- xpack.security.enabled=true
- ELASTIC_PASSWORD=${ES_PASSWORD}
- ES_JAVA_OPTS=-Xms2g -Xmx2g
volumes:
- es_data:/usr/share/elasticsearch/data
# Worker per processing documenti
tika:
image: apache/tika:2.9.1-full
ports:
- "9998:9998"
# Database relazionale per metadata e catena custodia
postgres:
image: postgres:16-alpine
environment:
POSTGRES_DB: ediscovery
POSTGRES_USER: ediscovery
POSTGRES_PASSWORD: ${PG_PASSWORD}
volumes:
minio_data:
es_data:
Masivní potrubí příjmu dokumentů
Zpracování je nejkritičtější fází: musí shromažďovat dokumenty z heterogenních zdrojů (e-mailový server, soubory sdílení, cloudové úložiště, aplikace SaaS) při zachování forenzní integrity. Každý získaný dokument musí mít ověřitelný kryptografický hash a neměnný záznam kdy a jak a byly shromážděny.
"""
ediscovery/ingestion/collector.py
Collettore forense con chain of custody
"""
import hashlib
import json
import uuid
from datetime import datetime, timezone
from pathlib import Path
from typing import Optional
import boto3
from kafka import KafkaProducer
import psycopg2
class ForensicCollector:
"""
Raccoglie documenti con hash SHA-256 e registra
la catena di custodia in PostgreSQL.
"""
def __init__(self, config: dict):
self.minio = boto3.client(
's3',
endpoint_url=config['minio_url'],
aws_access_key_id=config['minio_user'],
aws_secret_access_key=config['minio_password']
)
self.producer = KafkaProducer(
bootstrap_servers=config['kafka_brokers'],
value_serializer=lambda v: json.dumps(v).encode('utf-8')
)
self.pg_conn = psycopg2.connect(config['postgres_dsn'])
self.bucket = 'ediscovery-originals'
def collect_file(
self,
file_path: Path,
matter_id: str,
custodian_id: str,
collector_id: str
) -> dict:
"""
Raccoglie un file con verifica integrita e registra custody event.
Restituisce il documento event per il topic Kafka.
"""
# 1. Calcola hash SHA-256 prima del trasferimento
sha256 = self._compute_sha256(file_path)
md5 = self._compute_md5(file_path)
file_size = file_path.stat().st_size
# 2. Genera Document ID univoco
doc_id = str(uuid.uuid4())
# 3. Upload su MinIO con metadata
s3_key = f"matters/{matter_id}/originals/{doc_id}/{file_path.name}"
self.minio.upload_file(
str(file_path),
self.bucket,
s3_key,
ExtraArgs={
'Metadata': {
'doc-id': doc_id,
'sha256': sha256,
'custodian-id': custodian_id,
'matter-id': matter_id
}
}
)
# 4. Verifica integrita post-upload
response = self.minio.head_object(Bucket=self.bucket, Key=s3_key)
uploaded_size = response['ContentLength']
if uploaded_size != file_size:
raise ValueError(
f"Integrita compromessa: atteso {file_size} bytes, "
f"caricato {uploaded_size} bytes"
)
# 5. Registra custody event in PostgreSQL
collection_timestamp = datetime.now(timezone.utc).isoformat()
custody_event = {
'event_id': str(uuid.uuid4()),
'doc_id': doc_id,
'matter_id': matter_id,
'custodian_id': custodian_id,
'collector_id': collector_id,
'event_type': 'COLLECTION',
'timestamp': collection_timestamp,
'source_path': str(file_path),
'sha256': sha256,
'md5': md5,
'file_size': file_size,
's3_key': s3_key
}
self._record_custody_event(custody_event)
# 6. Pubblica su Kafka per processing asincrono
document_event = {
'doc_id': doc_id,
'matter_id': matter_id,
'custodian_id': custodian_id,
's3_key': s3_key,
'filename': file_path.name,
'file_size': file_size,
'sha256': sha256,
'collection_timestamp': collection_timestamp,
'status': 'COLLECTED'
}
self.producer.send('ediscovery.documents.collected', document_event)
return document_event
def _compute_sha256(self, file_path: Path) -> str:
h = hashlib.sha256()
with open(file_path, 'rb') as f:
for chunk in iter(lambda: f.read(65536), b''):
h.update(chunk)
return h.hexdigest()
def _compute_md5(self, file_path: Path) -> str:
h = hashlib.md5()
with open(file_path, 'rb') as f:
for chunk in iter(lambda: f.read(65536), b''):
h.update(chunk)
return h.hexdigest()
def _record_custody_event(self, event: dict) -> None:
with self.pg_conn.cursor() as cur:
cur.execute("""
INSERT INTO custody_events (
event_id, doc_id, matter_id, custodian_id,
collector_id, event_type, timestamp,
source_path, sha256, md5, file_size, s3_key
) VALUES (
%(event_id)s, %(doc_id)s, %(matter_id)s, %(custodian_id)s,
%(collector_id)s, %(event_type)s, %(timestamp)s,
%(source_path)s, %(sha256)s, %(md5)s, %(file_size)s, %(s3_key)s
)
""", event)
self.pg_conn.commit()
Zpracování a extrakce obsahu pomocí Apache Tika
Zpracování a technické srdce potrubí. Každý dokument musí být převeden na text prohledávatelná, extrahovaná metadata (autor, data, vlákno e-mailu, vlastnosti dokumentu) a normalizovaná ve společném schématu. Apache Tika spravuje více než 1500 různých formátů souborů, což z něj dělá de facto standard pro extrakci obsahu v e-Discovery.
"""
ediscovery/processing/processor.py
Worker di processing documenti con Apache Tika
"""
import json
import requests
from kafka import KafkaConsumer, KafkaProducer
from elasticsearch import Elasticsearch
import boto3
class DocumentProcessor:
"""
Consumer Kafka che processa ogni documento raccolto:
estrae testo e metadata con Tika, indicizza su ES.
"""
TIKA_URL = "http://tika:9998"
def __init__(self, config: dict):
self.consumer = KafkaConsumer(
'ediscovery.documents.collected',
bootstrap_servers=config['kafka_brokers'],
group_id='document-processor',
value_deserializer=lambda v: json.loads(v.decode('utf-8'))
)
self.producer = KafkaProducer(
bootstrap_servers=config['kafka_brokers'],
value_serializer=lambda v: json.dumps(v).encode('utf-8')
)
self.es = Elasticsearch(
config['elasticsearch_url'],
basic_auth=('elastic', config['es_password'])
)
self.minio = boto3.client('s3', endpoint_url=config['minio_url'])
def process_documents(self) -> None:
for message in self.consumer:
event = message.value
try:
processed = self._process_document(event)
self._index_document(processed)
self.producer.send(
'ediscovery.documents.processed',
{**event, **processed, 'status': 'PROCESSED'}
)
except Exception as exc:
self.producer.send(
'ediscovery.documents.errors',
{**event, 'error': str(exc), 'status': 'ERROR'}
)
def _process_document(self, event: dict) -> dict:
# Scarica documento da MinIO
response = self.minio.get_object(
Bucket='ediscovery-originals',
Key=event['s3_key']
)
file_content = response['Body'].read()
# Estrai testo con Tika (PUT /tika)
tika_response = requests.put(
f"{self.TIKA_URL}/tika",
data=file_content,
headers={
'Accept': 'text/plain',
'Content-Type': 'application/octet-stream'
},
timeout=120
)
extracted_text = tika_response.text
# Estrai metadata con Tika (PUT /meta)
meta_response = requests.put(
f"{self.TIKA_URL}/meta",
data=file_content,
headers={
'Accept': 'application/json',
'Content-Type': 'application/octet-stream'
},
timeout=60
)
metadata = meta_response.json()
return {
'extracted_text': extracted_text,
'text_length': len(extracted_text),
'tika_metadata': metadata,
'author': metadata.get('dc:creator', ''),
'created_date': metadata.get('dcterms:created', ''),
'modified_date': metadata.get('dcterms:modified', ''),
'content_type': metadata.get('Content-Type', ''),
'language': metadata.get('language', ''),
'page_count': metadata.get('xmpTPg:NPages', 0)
}
def _index_document(self, doc: dict) -> None:
"""Indicizza documento su Elasticsearch per ricerca full-text."""
self.es.index(
index=f"ediscovery-{doc['matter_id']}",
id=doc['doc_id'],
document={
'doc_id': doc['doc_id'],
'matter_id': doc['matter_id'],
'custodian_id': doc['custodian_id'],
'filename': doc['filename'],
'content': doc['extracted_text'],
'author': doc.get('author', ''),
'created_date': doc.get('created_date'),
'modified_date': doc.get('modified_date'),
'content_type': doc.get('content_type', ''),
'language': doc.get('language', ''),
'page_count': doc.get('page_count', 0),
'file_size': doc['file_size'],
'sha256': doc['sha256'],
'collection_timestamp': doc['collection_timestamp'],
'status': 'PROCESSED',
'review_status': 'UNREVIEWED',
'relevance_score': None,
'privilege': False,
'tags': []
}
)
Deduplikace: Přesná a téměř duplicitní detekce
V typické kolekci e-Discovery je 40–70 % dokumentů duplikáty nebo téměř duplikáty. Tam deduplikace je nezbytné nejen snížit náklady na přezkoumání, ale je to a zákonný požadavek: vytváření tisíců identických kopií stejného e-mailu porušuje pravidla odhalení a zvyšuje náklady protistrany. Existují dvě úrovně deduplikace:
Úrovně deduplikace v e-Discovery
- Přesné odstranění duplicit (na základě hash): Dokumenty s identickým SHA-256 jsou přesné duplikáty. Ponecháte si „custodial copy“ s relevantnějšími metadaty a propojíte ostatní jako duplikáty.
- Near-Dedup (MinHash/LSH): Dokumenty s podobným, ale ne identickým obsahem (e-mail s přidaným podpisem, verze konceptů). Algoritmy jako MinHash s Locality-Sensitive Hašování identifikuje dokumenty s podobností Jaccard > 0,85.
- E-mailové vlákno: Seskupování e-mailů do konverzací (vlákna) pro snížit objem recenzí tím, že budete prezentovat pouze nejnovější zprávu se všemi souvislostmi.
"""
ediscovery/processing/deduplication.py
Near-deduplication con MinHash e LSH
"""
from datasketch import MinHash, MinHashLSH
import hashlib
from typing import Optional
class DeduplicationEngine:
def __init__(self, num_perm: int = 128, threshold: float = 0.85):
self.lsh = MinHashLSH(threshold=threshold, num_perm=num_perm)
self.num_perm = num_perm
self.processed: dict[str, str] = {} # sha256 -> doc_id
def is_exact_duplicate(self, sha256: str) -> Optional[str]:
"""Restituisce doc_id del duplicato esatto o None."""
return self.processed.get(sha256)
def register_document(self, doc_id: str, sha256: str, text: str) -> None:
"""Registra un documento nel sistema di dedup."""
self.processed[sha256] = doc_id
minhash = self._compute_minhash(text)
self.lsh.insert(doc_id, minhash)
def find_near_duplicates(self, text: str, query_doc_id: str) -> list[str]:
"""
Trova near-duplicati del testo con Jaccard sim >= threshold.
Restituisce lista di doc_id simili.
"""
minhash = self._compute_minhash(text)
results = self.lsh.query(minhash)
return [r for r in results if r != query_doc_id]
def _compute_minhash(self, text: str) -> MinHash:
minhash = MinHash(num_perm=self.num_perm)
# Shingling a livello di parola (3-gram)
tokens = text.lower().split()
shingles = [
' '.join(tokens[i:i+3])
for i in range(len(tokens) - 2)
]
for shingle in shingles:
minhash.update(shingle.encode('utf8'))
return minhash
Technologie asistovaná kontrola a prediktivní kódování
Revizní fáze je historicky nejdražší: starší právníci čtou každý dokument klasifikovat jej jako relevantní, irelevantní nebo privilegovaný (krytý právy advokáta a klienta). The Technologie asistovaná kontrola (TAR) con Průběžné aktivní učení (CAL) drasticky snižuje tyto náklady: model se učí z rozhodnutí a priorit recenzentů nejpravděpodobnější relevantní dokumenty, což vám umožní zastavit kontrolu, když rychlost očekávané stažení překračuje prahovou hodnotu (obvykle 75 % podle standardu TREC Legal Track).
"""
ediscovery/review/predictive_coding.py
Predictive Coding con Sentence Transformers e Active Learning
"""
from sentence_transformers import SentenceTransformer
from sklearn.linear_model import LogisticRegression
from sklearn.preprocessing import StandardScaler
import numpy as np
from typing import Optional
class PredictiveCodingEngine:
"""
Implementa TAR 2.0 (Continuous Active Learning).
Il reviewer classifica documenti, il modello re-addestra
e riordina la review queue.
"""
def __init__(self, model_name: str = 'all-mpnet-base-v2'):
self.encoder = SentenceTransformer(model_name)
self.classifier = LogisticRegression(
class_weight='balanced',
max_iter=1000
)
self.scaler = StandardScaler()
self.training_texts: list[str] = []
self.training_labels: list[int] = [] # 1=rilevante, 0=non rilevante
self.is_trained = False
def add_review_decision(
self,
doc_text: str,
is_relevant: bool
) -> None:
"""Aggiunge una decisione di review al training set."""
self.training_texts.append(doc_text)
self.training_labels.append(1 if is_relevant else 0)
# Re-addestra quando ci sono abbastanza esempi (>=10 per classe)
pos_count = sum(self.training_labels)
neg_count = len(self.training_labels) - pos_count
if pos_count >= 5 and neg_count >= 5:
self._retrain()
def _retrain(self) -> None:
embeddings = self.encoder.encode(
self.training_texts,
batch_size=32,
show_progress_bar=False
)
embeddings_scaled = self.scaler.fit_transform(embeddings)
self.classifier.fit(embeddings_scaled, self.training_labels)
self.is_trained = True
def predict_relevance(
self,
texts: list[str]
) -> list[dict]:
"""
Predice la rilevanza di una lista di documenti.
Restituisce lista di {text_index, relevance_prob} ordinata per score desc.
"""
if not self.is_trained:
# Prima del training, restituisce ordine casuale
return [
{'index': i, 'relevance_prob': 0.5}
for i in range(len(texts))
]
embeddings = self.encoder.encode(
texts,
batch_size=32,
show_progress_bar=False
)
embeddings_scaled = self.scaler.transform(embeddings)
probabilities = self.classifier.predict_proba(
embeddings_scaled
)[:, 1] # probabilità classe positiva
results = [
{'index': i, 'relevance_prob': float(p)}
for i, p in enumerate(probabilities)
]
return sorted(results, key=lambda x: x['relevance_prob'], reverse=True)
def estimate_recall(
self,
reviewed_count: int,
total_count: int,
relevant_found: int
) -> float:
"""
Stima il recall atteso usando il metodo seed+sample
secondo TREC Legal Track guidelines.
Semplificazione: usa il tasso di prevalenza osservato.
"""
if reviewed_count == 0:
return 0.0
prevalence = relevant_found / reviewed_count
estimated_total_relevant = prevalence * total_count
if estimated_total_relevant == 0:
return 1.0
return min(relevant_found / estimated_total_relevant, 1.0)
Porovnání platforem e-Discovery
Trh s platformami e-Discovery je konsolidovaný, ale rychle se vyvíjí pod tlakem AI. Zde je srovnání hlavních řešení dostupných v letech 2025–2026:
| Platforma | Silné stránky | Omezení | Cenový model | Funkce AI |
|---|---|---|---|---|
| Relativita | Podnikové standardy, rozsáhlý ekosystém, vyspělá API | Složitost nastavení, vysoké náklady | SaaS + vlastní hostování, za GB | RelevanceAI, konceptuální vyhledávání, detekce anomálií |
| DISK | Cloudové, moderní UX, nativně integrovaná AI | Menší ekosystém než Relativita | Předplatné + použití | DISCO AI: prediktivní kódování, automatické označování, otázky a odpovědi na dokumenty |
| Everlaw | Spolupráce v reálném čase, vynikající UX, připraveno na zkoušku | Méně funkcí pro hromadné případy | Za GB/měsíc | EverAI: prediktivní kódování, shrnutí, ukládání otázek a odpovědí |
| Logikcull | Samoobsluha, transparentní ceny, rychlé nalodění | Méně vhodné pro velmi složité případy | Za GB nebo předplatné | Automatické značkování, asistované vyhledávání, pokročilé odstraňování souborů |
| Odhalit | Pokročilá AI (Brainspace), proprietární NLP | Strmá křivka učení | Podnikové licencování | Shlukování témat, hledání konceptů, detekce anomálií |
EDRM XML export a produkce dokumentů
Poslední fází je výroba dokumentů protistraně dle dohodnutého formátu. Standard EDRM XML definuje schéma XML pro výměnu dokumentů se všemi jejich metadata, což usnadňuje nahrávání na jakoukoli platformu. Dokumenty obvykle přicházejí produkty s Batesovo číslování (unikátní progresivní číslování) a s redakcí použito na privilegovaný obsah.
"""
ediscovery/production/edrm_exporter.py
Generazione export EDRM XML con Bates numbering
"""
import xml.etree.ElementTree as ET
from datetime import datetime, timezone
from typing import Iterator
def generate_edrm_xml(
documents: list[dict],
matter_id: str,
bates_prefix: str = "PROD",
start_bates: int = 1
) -> str:
"""
Genera EDRM XML per un set di documenti prodotti.
Ogni documento riceve un Bates number univoco.
"""
root = ET.Element('Root')
root.set('DataInterchangeType', 'Processed')
root.set('DateCreated', datetime.now(timezone.utc).isoformat())
root.set('Encoding', 'UTF-8')
root.set('MajorVersion', '1')
root.set('MinorVersion', '2')
batch = ET.SubElement(root, 'Batch')
documents_el = ET.SubElement(batch, 'Documents')
for idx, doc in enumerate(documents):
bates_num = f"{bates_prefix}{str(start_bates + idx).zfill(7)}"
doc_el = ET.SubElement(documents_el, 'Document')
doc_el.set('DocID', doc['doc_id'])
# Tags con metadata
tags_el = ET.SubElement(doc_el, 'Tags')
def add_tag(name: str, value: str, data_type: str = 'Text') -> None:
tag = ET.SubElement(tags_el, 'Tag')
tag.set('TagName', name)
tag.set('TagValue', value)
tag.set('TagDataType', data_type)
add_tag('BatesNumber', bates_num)
add_tag('DocID', doc['doc_id'])
add_tag('MatterID', matter_id)
add_tag('Custodian', doc.get('custodian_id', ''))
add_tag('FileName', doc.get('filename', ''))
add_tag('DateCollected', doc.get('collection_timestamp', ''), 'DateTime')
add_tag('DateCreated', doc.get('created_date', ''), 'DateTime')
add_tag('Author', doc.get('author', ''))
add_tag('FileSize', str(doc.get('file_size', 0)), 'LongInteger')
add_tag('SHA256', doc.get('sha256', ''))
add_tag('ContentType', doc.get('content_type', ''))
add_tag('ReviewStatus', doc.get('review_status', ''))
add_tag('IsPrivileged', str(doc.get('privilege', False)), 'Boolean')
add_tag(
'RelevanceScore',
str(round(doc.get('relevance_score', 0), 4)),
'Decimal'
)
return ET.tostring(root, encoding='unicode', xml_declaration=True)
Kritické právní aspekty
- Vyvlastnění: Zničení nebo pozměnění důkazů poté a přiměřeně předvídatelné soudní řízení může vést k přísným sankcím, včetně nepříznivého závěru pokyny (pověřující porotu předpokládat, že zničené důkazy byly nepříznivé).
- Recenze oprávnění: Dokumenty, na které se vztahuje privilegium advokáta-klienta nebo práce Produktová doktrína musí být identifikována a navržena před výrobou. Produkce náhodné použití privilegovaných dokumentů může být napadeno dohodami o zpětném získání (FRE 502(d)).
- Přeshraniční ochrana osobních údajů: Sbírejte data od evropských zaměstnanců pro objev USA vyžaduje hloubkovou analýzu GDPR. Rámec ochrany osobních údajů mezi USA a EU (2023) zjednodušil převody, ale náležitá péče zůstává zásadní.
- AI obranyschopnost: Soudy vyžadují transparentnost ohledně používaných metod TAR. Zvolený protokol a prahová hodnota odvolání musí být zdokumentovány a obhajitelné.
Schéma databáze pro spotřebitelský řetězec
Relační databáze a právní páteř platformy. Každá akce na každém dokumentu musí být sledovány s časovým razítkem, uživatelem a důvodem akce. Tato auditní stopa musí být neměnný: žádný záznam nelze zpětně vymazat ani upravit.
-- Schema PostgreSQL per e-Discovery con audit trail immutabile
-- Matters (cause/procedimenti)
CREATE TABLE matters (
matter_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
matter_name TEXT NOT NULL,
matter_number TEXT UNIQUE NOT NULL,
client_id UUID NOT NULL,
status TEXT NOT NULL DEFAULT 'ACTIVE',
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
closed_at TIMESTAMPTZ
);
-- Custodians (soggetti i cui dati vengono raccolti)
CREATE TABLE custodians (
custodian_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
matter_id UUID REFERENCES matters(matter_id),
full_name TEXT NOT NULL,
email TEXT NOT NULL,
department TEXT,
hold_applied BOOLEAN NOT NULL DEFAULT FALSE,
hold_date TIMESTAMPTZ
);
-- Documents (indice master dei documenti)
CREATE TABLE documents (
doc_id UUID PRIMARY KEY,
matter_id UUID REFERENCES matters(matter_id),
custodian_id UUID REFERENCES custodians(custodian_id),
filename TEXT NOT NULL,
file_size BIGINT NOT NULL,
sha256 CHAR(64) NOT NULL,
md5 CHAR(32) NOT NULL,
s3_key TEXT NOT NULL,
content_type TEXT,
is_duplicate_of UUID REFERENCES documents(doc_id),
is_near_dup_of UUID REFERENCES documents(doc_id),
collection_timestamp TIMESTAMPTZ NOT NULL,
status TEXT NOT NULL DEFAULT 'COLLECTED',
review_status TEXT NOT NULL DEFAULT 'UNREVIEWED',
relevance_score NUMERIC(5,4),
privilege BOOLEAN NOT NULL DEFAULT FALSE,
bates_number TEXT UNIQUE,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
-- Custody events (audit trail immutabile)
-- Constraint: nessuna UPDATE/DELETE permessa
CREATE TABLE custody_events (
event_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
doc_id UUID REFERENCES documents(doc_id),
matter_id UUID REFERENCES matters(matter_id),
custodian_id UUID,
collector_id UUID,
event_type TEXT NOT NULL, -- COLLECTION, PROCESSING, REVIEW, PRODUCTION, etc.
timestamp TIMESTAMPTZ NOT NULL,
source_path TEXT,
sha256 CHAR(64),
md5 CHAR(32),
file_size BIGINT,
s3_key TEXT,
user_id UUID,
notes TEXT
);
-- Impedisce UPDATE e DELETE sulla tabella eventi
CREATE RULE no_update_custody_events AS
ON UPDATE TO custody_events DO INSTEAD NOTHING;
CREATE RULE no_delete_custody_events AS
ON DELETE TO custody_events DO INSTEAD NOTHING;
-- Review decisions
CREATE TABLE review_decisions (
decision_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
doc_id UUID REFERENCES documents(doc_id),
reviewer_id UUID NOT NULL,
decision TEXT NOT NULL, -- RELEVANT, NOT_RELEVANT, PRIVILEGED, NEEDS_REDACTION
confidence TEXT, -- HIGH, MEDIUM, LOW
review_timestamp TIMESTAMPTZ NOT NULL DEFAULT NOW(),
notes TEXT
);
Závěry a další kroky
Vybudování podnikové platformy e-Discovery vyžaduje architekturu, která vyvažuje tři imperativy často ve vzájemném napětí: technický výkon (zpracovat miliony dokumentů v přiměřené době), právní správnost (opatrovatelský řetězec neměnný, odvolat dokumentovatelný, privilegovaný přezkum obhajitelný) e použitelnost (recenzenti jsou právníci, nikoli datoví vědci).
Klíčové komponenty, které jsme prozkoumali — forenzní zpracování pomocí SHA-256, zpracování nasazeno s Apache Tika, deduplikace s MinHash/LSH a prediktivní kódování s Active Učení — představují nejmodernější odvětví v letech 2025–2026. Přijetí AI transformovala TAR z experimentální technologie na tržní standard: 79 % platforem dnes ji nativně integruje.
V dalším článku seriálu uvidíme, jak postavit a Compliance Engine s dynamickými pravidly pro automatizaci regulačního monitorování v reálném čase.
Zdroje a statistiky
- EDRM (Electronic Discovery Reference Model): edrm.net
- Dokumentace platformy relativity: relativity.com/artificial-intelligence
- TREC Legal Track Recall Guidelines: standard pro měření.it
- FRE 502(d): Dohody o zpětném získání náhodně vytvořených privilegovaných dokumentů
- datasketch - Python knihovna pro MinHash/LSH
- větné transformátory: vložení pro prediktivní kódování







