Spaces:
Runtime error
Runtime error
File size: 9,073 Bytes
9cf9a2a 7435134 ef3c54d 9cf9a2a ef3c54d 9cf9a2a ef3c54d 9cf9a2a ef3c54d 9cf9a2a ef3c54d 9cf9a2a ef3c54d 9cf9a2a ef3c54d 9cf9a2a | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 | """
Integration test for article extraction in document processing pipeline.
Verifies that extract_articles utility can be imported and used
as intended in the process_law task.
"""
import pytest
from app.utils.text_chunker import extract_articles
class TestArticleExtractionIntegration:
"""Test article extraction integration scenarios."""
def test_real_world_cameroonian_law_format(self):
"""Test extraction with realistic Cameroonian legal document format."""
# Simulated Cameroonian legal document
law_text = """
LOI N° 2023-001 DU 15 JANVIER 2023
PORTANT RÉGIME DES SOCIÉTÉS COMMERCIALES AU CAMEROUN
CHAPITRE I - DISPOSITIONS GÉNÉRALES
Article 1. Objet de la loi
La présente loi fixe le régime juridique applicable aux sociétés commerciales
au Cameroun conformément aux dispositions de l'OHADA et aux spécificités nationales.
Article 2. Champ d'application territorial
Les dispositions de la présente loi s'appliquent sur l'ensemble du territoire
de la République du Cameroun et concernent toutes les sociétés commerciales.
Article 3. Définitions et terminologie
Au sens de la présente loi, on entend par société commerciale toute personne
morale constituée conformément aux dispositions du présent texte et de l'OHADA.
CHAPITRE II - CONSTITUTION DES SOCIÉTÉS
Article 4. Conditions de constitution
Toute société commerciale doit être constituée par un acte écrit et immatriculée
au registre du commerce et du crédit mobilier dans les conditions fixées par la loi.
Article 5. Capital social minimum
Le capital social minimum des sociétés commerciales est fixé par décret
en fonction de la forme juridique et du secteur d'activité concerné.
"""
chunks = extract_articles(law_text)
# Le premier chunk porte l'en-tete du texte (intitule de la loi), sous
# le numero PREAMBULE ou LEGAL_BASIS : l'extraction ne jette aucun
# caractere du document. Les articles proprement dits sont donc filtres.
assert chunks[0]['number'] in ('PREAMBULE', 'LEGAL_BASIS')
assert 'LOI N° 2023-001' in chunks[0]['content']
articles = [c for c in chunks if c['number'].isdigit()]
# Verify extraction
assert len(articles) == 5
assert [a['number'] for a in articles] == ['1', '2', '3', '4', '5']
# Verify structure
assert articles[0]['title'] == 'Objet de la loi'
assert 'OHADA' in articles[0]['content']
assert articles[3]['number'] == '4'
assert articles[3]['title'] == 'Conditions de constitution'
# La section est rattachee a chaque article
assert articles[0]['section'].startswith('CHAPITRE I')
assert articles[3]['section'].startswith('CHAPITRE II')
# Verify all have required fields
for article in articles:
assert 'number' in article
assert 'content' in article
assert 'position' in article
assert 'word_count' in article
assert 'char_count' in article
assert article['word_count'] > 0
assert article['char_count'] > 0
def test_short_document_is_extracted(self):
"""Un texte de deux articles est extrait, pas rejete."""
short_text = """
Article 1. Premier article
Contenu du premier article avec suffisamment de texte pour validation.
Article 2. Deuxième article seulement
Contenu du deuxième article, court mais parfaitement valide.
"""
# L'ancienne regle "minimum 3 articles" a ete retiree du chunker, et
# elle ne doit pas revenir : la majorite du corpus est faite de decrets
# de nomination a deux articles ("Article 1er : est nomme... / Article
# 2 : le present decret sera enregistre..."). La retablir refuserait
# l'essentiel des documents.
articles = extract_articles(short_text)
assert [a['number'] for a in articles] == ['1', '2']
assert all(a['char_count'] > 0 for a in articles)
def test_empty_document_raises(self):
"""Un document sans contenu exploitable leve toujours."""
with pytest.raises(ValueError):
extract_articles(" \n \n ")
def test_pipeline_processes_large_document(self):
"""Test that pipeline can handle large legal documents."""
# Generate a large document (100 articles)
articles_content = []
for i in range(1, 101):
articles_content.append(
f"Article {i}. Titre de l'article {i}\n"
f"Contenu de l'article {i} avec suffisamment de texte pour validation. "
f"Les dispositions légales s'appliquent conformément aux normes."
)
law_text = "\n\n".join(articles_content)
articles = extract_articles(law_text)
assert len(articles) == 100
assert articles[0]['number'] == '1'
assert articles[99]['number'] == '100'
# Verify positions are sequential
positions = [a['position'] for a in articles]
assert positions == list(range(100))
def test_database_ready_format(self):
"""Test that extracted articles are ready for database insertion."""
law_text = """
Article 1. Premier article
Contenu du premier article avec suffisamment de texte.
Article 2. Deuxième article
Contenu du deuxième article avec suffisamment de texte.
Article 3. Troisième article
Contenu du troisième article avec suffisamment de texte.
"""
articles = extract_articles(law_text)
# Verify database-ready format
for article in articles:
# All fields match database schema
assert isinstance(article['number'], str)
assert len(article['number']) <= 20 # VARCHAR(20) in DB
if article['title']:
assert isinstance(article['title'], str)
assert len(article['title']) <= 200 # VARCHAR(200) in DB
assert isinstance(article['content'], str)
assert isinstance(article['position'], int)
assert isinstance(article['word_count'], int)
assert isinstance(article['char_count'], int)
# parent_id is Optional[str]
assert article['parent_id'] is None or isinstance(article['parent_id'], str)
def test_hierarchical_articles_for_database(self):
"""Test hierarchical articles produce correct parent_id for database."""
law_text = """
Article 1. Article principal
Contenu de l'article principal avec suffisamment de texte.
Article 1.1. Premier sous-article
Contenu du premier sous-article avec suffisamment de texte.
Article 1.2. Deuxième sous-article
Contenu du deuxième sous-article avec suffisamment de texte.
Article 2. Autre article
Contenu d'un autre article avec suffisamment de texte.
"""
articles = extract_articles(law_text)
# Verify parent-child relationships
assert articles[0]['number'] == '1'
assert articles[0]['parent_id'] is None
assert articles[1]['number'] == '1.1'
assert articles[1]['parent_id'] == '1'
assert articles[2]['number'] == '1.2'
assert articles[2]['parent_id'] == '1'
assert articles[3]['number'] == '2'
assert articles[3]['parent_id'] is None
def test_non_strict_mode_for_partial_documents(self):
"""Test non-strict mode for incomplete/draft documents."""
partial_text = """
Article 1. Premier article incomplet
Contenu du premier article avec suffisamment de texte pour validation.
Article 2. Deuxième article incomplet
Contenu du deuxième article avec suffisamment de texte également.
"""
# Non-strict mode should accept <3 articles
articles = extract_articles(partial_text, strict=False)
assert len(articles) == 2
assert articles[0]['number'] == '1'
assert articles[1]['number'] == '2'
def test_performance_target_met(self):
"""Test that extraction meets <500ms performance target for 1000 articles."""
import time
# Generate document with 100 articles (scaled test)
articles_content = []
for i in range(1, 101):
articles_content.append(
f"Article {i}. Titre {i}\n"
f"Contenu de l'article {i} avec du texte. " * 5
)
law_text = "\n\n".join(articles_content)
start = time.time()
articles = extract_articles(law_text)
elapsed = time.time() - start
assert len(articles) == 100
# 100 articles should complete in <100ms (scales to 1000 in <1s)
assert elapsed < 0.5, f"Extraction took {elapsed:.3f}s (target: <0.5s for 100 articles)"
|