Repository: aphp/edsnlp (language: python)

Issue:
Detection of successive empty sections
## Description

Using the beta pipeline "eds.sections", I encountered a bug in detecting sections preceded by an empty section. Indeed, if a section is preceded by an empty section, it is not detected and its content is labeled as belonging to the first section. 

Empty sections are quite common in documents, so this could lead to errors in section labelling of entities. 

## How to reproduce the bug

For instance, in the following example, sections "Antécédents :" and "Conclusion" are not distinguished. Therefore, all the content of "Conclusion" section is tagged as "Antécédents".

```python
import spacy

nlp = spacy.blank("eds")

nlp.add_pipe("eds.sentences")
nlp.add_pipe("eds.normalizer")
nlp.add_pipe("eds.sections")

# Definition matcher
regex = dict(
    # Myolyse
    rhabdomyolyse = "rhabdom[yi]ol[yi]se",
    myolyse = "m[yi]ol[yi]se"
)

nlp.add_pipe("eds.matcher", 
             config = dict(
                 regex=regex, 
                 attr="NORM",
                 ignore_excluded=True,
             ),
            )

text = """
Antécédents : 
Conclusion : 
Patient va mieux

Au total:
sortie du patient
"""

doc.spans["sections"]
```

## Your Environment

- Operating System:
- Python Version Used: 3.10.0
- spaCy Version Used: 3.4.1
- EDS-NLP Version Used: 0.6.1
- Environment Information:



Discussion / hints:
Thank you for letting us aware of this ! I'll look into it

Fix patch:
diff --git a/changelog.md b/changelog.md
index 6367ce00e..7663cac4d 100644
--- a/changelog.md
+++ b/changelog.md
@@ -1,5 +1,11 @@
 # Changelog
 
+## Unreleased
+
+### Fixed
+
+- Prevent section titles from capturing surrounding tokens, causing overlaps (#113)
+
 ## v0.6.2 (2022-08-02)
 
 ### Added
diff --git a/docs/pipelines/misc/sections.md b/docs/pipelines/misc/sections.md
index 687d2fd08..56e646e2a 100644
--- a/docs/pipelines/misc/sections.md
+++ b/docs/pipelines/misc/sections.md
@@ -68,7 +68,7 @@ text = "CRU du 10/09/2021\n" "Motif :\n" "Patient admis pour suspicion de COVID"
 doc = nlp(text)
 
 doc.spans["section_titles"]
-# Out: [Motif :]
+# Out: [Motif]
 ```
 
 ## Configuration
diff --git a/edsnlp/pipelines/misc/sections/sections.py b/edsnlp/pipelines/misc/sections/sections.py
index c421fe231..0f5c39455 100644
--- a/edsnlp/pipelines/misc/sections/sections.py
+++ b/edsnlp/pipelines/misc/sections/sections.py
@@ -79,7 +79,9 @@ def __init__(
         self.add_patterns = add_patterns
         if add_patterns:
             for k, v in sections.items():
-                sections[k] = [r"\n[^\n]{0,5}" + ent + r"[^\n]{0,5}\n" for ent in v]
+                sections[k] = [
+                    r"(?<=\n[^\n]{0,5})" + ent + r"(?=[^\n]{0,5}\n)" for ent in v
+                ]
 
         super().__init__(
             nlp,
@@ -120,13 +122,6 @@ def __call__(self, doc: Doc) -> Doc:
         """
         titles = filter_spans(self.process(doc))
 
-        if self.add_patterns:
-            # Remove preceding newline
-            titles = [
-                Span(doc, title.start + 1, title.end - 1, label=title.label_)
-                for title in titles
-            ]
-
         sections = []
 
         for t1, t2 in zip(titles[:-1], titles[1:]):


Test patch:
diff --git a/tests/pipelines/misc/test_sections.py b/tests/pipelines/misc/test_sections.py
index 682f07d9b..f7c1374b1 100644
--- a/tests/pipelines/misc/test_sections.py
+++ b/tests/pipelines/misc/test_sections.py
@@ -15,6 +15,15 @@
     "Pas d'anomalie détectée."
 )
 
+empty_sections_text = """
+Antécédents :
+Conclusion :
+<ent section=conclusion>Patient</ent> va mieux
+
+Au total:
+sortie du patient
+"""
+
 
 def test_section_detection(doc):
     assert doc.spans["sections"]
@@ -63,3 +72,29 @@ def test_sections(blank_nlp, sections_factory):
             assert (
                 getattr(ent._, modifier.key) == modifier.value
             ), f"{modifier.key} labels don't match."
+
+
+def test_empty_sections(blank_nlp, sections_factory):
+
+    blank_nlp.add_pipe("normalizer")
+
+    sections = sections_factory()
+
+    text, entities = parse_example(example=empty_sections_text)
+
+    doc = blank_nlp(text)
+    doc.ents = [
+        doc.char_span(ent.start_char, ent.end_char, label="ent") for ent in entities
+    ]
+
+    doc = sections(doc)
+
+    for section in doc.spans["sections"]:
+        for ent in section.ents:
+            ent._.section = section.label_
+
+    for entity, ent in zip(entities, doc.ents):
+        for modifier in entity.modifiers:
+            assert (
+                getattr(ent._, modifier.key) == modifier.value
+            ), f"{modifier.key} labels don't match."