e-Discovery プラットフォームのアーキテクチャ: 取り込み、処理、AI レビュー
2024 年にはプラットフォームに人工知能が導入される 電子情報開示 法的な わずか 1 年で 19% から 79% に急上昇しました。このデータは統計上の興味深いものではありません。 法律事務所、企業、司法当局の管理方法における構造的変革 民事および刑事訴訟における文書証拠。訴訟に何百万もの電子メールやメッセージが関係する場合 Slack、SharePoint のドキュメントとログ ファイル、による手動レビューの従来のモデル パラリーガルはもはや経済的にも物流的にも持続可能ではありません。
L'電子情報開示 (電子証拠開示)および訴訟当事者が行うプロセス 法的証拠の特定、収集、保存、処理、レビュー、作成 電子形式。米国では連邦民事訴訟規則 (FRCP) によって規制されており、ヨーロッパでは 国家レベルでも同様の枠組みから。最新のプラットフォームはペタバイト規模のデータを処理する必要があります。 法的に有効な保管過程を尊重し、レビューされる文書の量を減らす 非常に高い再現率を維持します。関連する文書が失われることはありません。
この記事では、エンタープライズ グレードの電子情報開示プラットフォームの完全なアーキテクチャを構築します。 から 大量摂取 異種ドキュメントの 分散処理、 からの 重複排除 al 予測コーディング AI モデルを使用すると、増加 にEDRM XML 形式でエクスポートする。すべて実際の Python コード例と分析が含まれています 市場の主要なプラットフォームの一部。
この記事で学べること
- EDRM (Electronic Discovery Reference Model) モデルとそのフェーズ
- 大規模な取り込みのためのマイクロサービス アーキテクチャ: Kafka、Elasticsearch、MinIO
- 処理パイプライン: テキスト抽出、メタデータ、MD5/SHA 重複排除および準重複排除
- テクノロジー支援レビュー (TAR) と継続的アクティブラーニング (CAL)
- scikit-learn と文章トランスフォーマーを使用した予測コーディング
- 加工保管管理と不変の監査証跡
- プラットフォームの比較: Relativity、DISCO、Everlaw、Logiccull
- EDRM XML エクスポートとケース管理システムとの統合
LegalTechとAIシリーズにおける位置づけ
| # | アイテム | 集中 |
|---|---|---|
| 1 | 契約分析のための NLP | OCR、NER、条項分類 |
| 2 | あなたはここにいます — e-Discovery アーキテクチャ | 取り込み、処理、AI レビュー |
| 3 | コンプライアンスの自動化 | ルールエンジンとRegTech |
| 4 | スマートコントラクト | 堅牢性、Vyper、強制力 |
| 5 | 生成AIによる要約 | LLM、RAG、出力検証 |
| 6 | 法学検索エンジン | ベクトル埋め込みとセマンティック検索 |
| 7 | デジタル署名と eIDAS 2.0 | PKI、タイムスタンプ、ワークフロー |
| 8 | GDPR コンプライアンス システム | プライバシーバイデザイン、DSR、データマッピング |
| 9 | 法務AI副操縦士 | 法定コーパス、ガードレール上の RAG |
| 10 | データ統合リーガルテック | ECLI、API 裁判所システム、XBRL |
EDRM モデル: 電子情報開示の参照フレームワーク
L'電子ディスカバリー参照モデル (EDRM) そしてそれを説明する事実上の標準 電子証拠開示プロセスの各段階。 2005 年に開発され、継続的に更新されているモデル すべてのエンタープライズ プラットフォームがサポートする必要がある 9 つの連続したフェーズを定義しています。
| EDRM フェーズ | 説明 | 技術コンポーネント |
|---|---|---|
| 1. 情報ガバナンス | 保持ポリシー、データ分類、データマップ | MDM、ポリシーエンジン、CMDB |
| 2. 識別 | 潜在的なカストディアンと関連データソースを見つける | クローラー、ディレクトリ スキャン、LDAP クエリ |
| 3.保存 | 訴訟ホールド: データの改ざんを防ぐためにデータを凍結する | 保留管理、不変ストレージ、通知ワークフロー |
| 4.コレクション | 保管管理を伴う法医学的収集 | フォレンジックコレクター、ハッシュ検証、保管ログ |
| 5.加工 | テキスト抽出、メタデータ、重複排除、NIST フィルタリング | Apache Tika、Elasticsearch 取り込みパイプライン |
| 6.復習 | 関連性/権限の分類、TAR/CAL | 予測コーディング、アクティブラーニング、レビュープラットフォーム |
| 7. 分析 | パターン、タイムライン、エンティティネットワーク、トピックモデリング | グラフ分析、NLP、LDA/BERトピック |
| 8. 生産 | 合意された形式でエクスポート (TIFF、ネイティブ、PDF) | EDRM XML エクスポート、ベイツ番号付け、墨消しエンジン |
| 9. プレゼンテーション | トライアルプレゼンテーション、証言録取書、視覚的なタイムライン | トライアルディレクター、展示管理 |
エンタープライズ電子情報開示のためのマイクロサービス アーキテクチャ
最新の電子情報開示プラットフォームは一枚岩であってはなりません。データ量はさまざまです 大きな原因の場合はギガバイトから数百テラバイトに達します。建築はこうでなければなりません 弾力的に拡張可能、フォールトトレラントであり、不変の監査証跡を確保します。 証拠の資格要件を満たしていること。統合されたアーキテクチャ パターンの組み合わせ イベントストリーミング、オブジェクトストレージ、分散検索エンジン。
# 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:
大規模なドキュメント取り込みパイプライン
取り込みは最も重要なフェーズです。異種ソース (電子メール サーバー、ファイル) からドキュメントを収集する必要があります。 共有、クラウド ストレージ、SaaS アプリケーションなど)フォレンジックの整合性を維持しながら。取得した各文書は、 持っています 検証可能な暗号ハッシュ そして、いつ、どのように、そして 集められました。
"""
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()
Apache Tika による処理とコンテンツ抽出
パイプラインの処理と技術の中心。各文書をテキストに変換する必要がある 検索可能、抽出されたメタデータ (作成者、日付、電子メール スレッド、ドキュメントのプロパティ) および正規化 一般的なスキームで。 アパッチ ティカ 1,500 を超える異なるファイル形式を管理し、 これは、電子情報開示におけるコンテンツ抽出の事実上の標準となっています。
"""
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': []
}
)
重複排除: 正確およびほぼ重複の検出
一般的な電子情報開示コレクションでは、文書の 40 ~ 70% が重複または重複に近いものになります。そこには 重複排除 レビューコストを削減するだけでなく、 法的要件: 同じ電子メールの同一のコピーを何千も作成することは、規則に違反します。 発見が難しくなり、取引相手のコストが増加します。重複排除には 2 つのレベルがあります。
電子情報開示の重複排除レベル
- 正確な重複排除 (ハッシュベース): 同一の SHA-256 を持つドキュメントは完全な複製です。 より関連性の高いメタデータを含む「保管コピー」を保持し、他のものを複製としてリンクします。
- ニア重複 (MinHash/LSH): 類似しているが同一ではない内容の文書 (署名を追加した電子メール、下書きバージョン)。地域性を考慮した MinHash などのアルゴリズム ハッシュ化により、Jaccard 類似度が 0.85 を超えるドキュメントが識別されます。
- 電子メールのスレッド化: メールを会話 (スレッド) にグループ化する すべてのコンテキストを含む最新のメッセージのみを表示することで、レビューの量を減らします。
"""
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
テクノロジー支援レビューと予測コーディング
審査段階は歴史的に最も費用がかかります。上級弁護士はすべての文書に目を通し、 それを関連性、無関係性、または特権的(弁護士と依頼者の特権の対象となる)に分類します。 の テクノロジー支援レビュー (TAR) con 継続的アクティブラーニング (CAL) このコストを大幅に削減します。モデルはレビュー担当者の決定と優先順位から学習します。 最も関連性の高い文書を抽出し、次の割合でレビューを停止できます。 予想されるリコールがしきい値 (通常、TREC Legal Track 基準に従って 75%) を超えています。
"""
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)
電子情報開示プラットフォームの比較
e-Discovery プラットフォームの市場は統合されていますが、次のような圧力の下で急速に進化しています。 AIの。以下は、2025 年から 2026 年に利用可能な主なソリューションの比較です。
| プラットフォーム | 強み | 制限事項 | 価格モデル | AIの機能 |
|---|---|---|---|---|
| 相対性理論 | エンタープライズ標準、広大なエコシステム、成熟した API | セットアップの複雑さ、コストの高さ | SaaS + セルフホスト型、GB あたり | RelevanceAI、概念検索、異常検知 |
| ディスク | クラウドネイティブ、最新の UX、ネイティブに統合された AI | 相対性理論よりも小さなエコシステム | サブスクリプション + 使用量 | DISCO AI: 予測コーディング、自動タグ付け、ドキュメントの Q&A |
| エバーロー | リアルタイムのコラボレーション、優れた UX、トライアル対応 | 大規模なケース向けに機能が少ない | GBあたり/月 | EverAI: 予測コーディング、要約、証言録取 Q&A |
| ロジカル | セルフサービス、透明性のある価格設定、迅速なオンボーディング | 非常に複雑なケースにはあまり適さない | GB またはサブスクリプションごと | 自動タグ付け、検索支援、高度な重複排除 |
| 明らかにする | 高度なAI(ブレインスペース)、独自のNLP | 急な学習曲線 | エンタープライズライセンス | トピックのクラスタリング、コンセプト検索、異常検出 |
EDRM XML エクスポートとドキュメント作成
最終段階は、合意された形式に従って取引相手に文書を作成することです。 EDRM XML 標準は、すべてのドキュメントを交換するための XML スキーマを定義します。 メタデータを使用して、あらゆるプラットフォームへのアップロードを容易にします。通常は書類が来ます との製品 通し番号付け (独自のプログレッシブナンバリング)と編集スタッフと 特権コンテンツに適用されます。
"""
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)
重要な法的考慮事項
- 所有権の剥奪: その後の合理的な証拠の隠滅または改ざん 予見可能な法的手続きは、不利な推論を含む厳しい制裁につながる可能性があります 指示(陪審に対し、証拠隠滅は不利なものであったとみなすよう指示する)。
- 特典レビュー: 弁護士と依頼者の特権または業務の対象となる文書 製品の原則は、製造前に特定され、草案が作成される必要があります。プロダクション 特権文書の誤使用に対しては、クローバック契約によって異議を申し立てることができます (FRE 502(d))。
- 国境を越えたデータのプライバシー: ヨーロッパの従業員からデータを収集し、 Discovery USA では詳細な GDPR 分析が必要です。米国と EU のデータ プライバシー フレームワーク (2023) 転送は簡素化されましたが、デューデリジェンスは引き続き不可欠です。
- AI の防御力: 裁判所は、使用される TAR 手法についての透明性を要求します。 選択したプロトコルとリコール閾値は文書化され、防御可能でなければなりません。
Chain of Custody のデータベース スキーマ
リレーショナル データベースとプラットフォームの法的バックボーン。すべてのドキュメントに対するすべてのアクション タイムスタンプ、ユーザー、アクションの理由を追跡する必要があります。この監査証跡は、 不変であること: レコードを遡って削除または変更することはできません。
-- 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
);
結論と次のステップ
エンタープライズ グレードの電子情報開示プラットフォームを構築するには、次の 3 つのバランスをとるアーキテクチャが必要です。 多くの場合、命令は互いに緊張関係にあります。 技術的パフォーマンス (何百万もの処理 適切な時間内に文書を作成します)、 法的な正しさ (管理過程 不変、リコールは文書化可能、権限レビューは防御可能) e 使いやすさ (査読者はデータサイエンティストではなく弁護士です)。
私たちが調査した主要コンポーネント — SHA-256 によるフォレンジック取り込み、処理 Apache Tika でデプロイ、MinHash/LSH で重複排除、Active で予測コーディング 学習 — 2025 ~ 2026 年の分野の最先端を表します。 AIの導入 TAR を実験的技術から市場標準に変換: プラットフォームの 79% 現在ではそれをネイティブに統合しています。
シリーズの次の記事では、 コンプライアンスエンジン 動的ルール エンジンを使用して、リアルタイムで規制監視を自動化します。
リソースと洞察
- EDRM (電子ディスカバリー参照モデル): edrm.net
- Relativity プラットフォームのドキュメント: relativity.com/artificial-intelligence
- TREC 法的追跡リコール ガイドライン:measure.it リコール基準
- FRE 502(d): 誤って作成された特権文書の取り消し協定
- datasketch - MinHash/LSH 用の Python ライブラリ
- 文変換: 予測コーディング用の埋め込み







