fix(enrichers): split_sentences y extract_iocs_text leen entities.notes
El campo `notes` es lo que el usuario escribe en el panel Note del
Inspector (doble click sobre el nodo) — sitio canonico para texto
largo. Antes los enrichers leian metadata.text/description/query como
prioridad, dejando notes ignorado y forzando al usuario a inyectar
texto via la UI metadata-extra (poco descubrible).
Cambios:
- Ambos run.py abren la BD y leen `entities.notes` por SQL antes de
fallback a node_name. metadata.text/description/query ya no se
consultan (KISS — solo notes y name).
- conftest.make_node admite kwarg `notes` para inyectar contenido
en la columna notes desde tests.
- Tests actualizados: SAMPLE_TEXT y los IoC dumps van por `notes=`
en lugar de `metadata={"text": ...}`.
- Renombrado el test que verificaba prioridad: ahora se llama
`*_uses_notes_priority` y verifica notes > name.
Tests verdes WSL (44) y Windows (33 + 11 skipped).
This commit is contained in:
@@ -2,9 +2,10 @@
|
||||
"""Enricher extract_iocs_text — variante offline de extract_text_entities.
|
||||
|
||||
A diferencia de extract_text_entities, este enricher NO depende de un
|
||||
markdown cacheado (fetch_webpage previo). Lee el texto directamente del
|
||||
nodo (`metadata.text` > `metadata.description` > `metadata.query` >
|
||||
`node_name`) y aplica el pipeline `extract_iocs` del registry sobre el.
|
||||
markdown cacheado (fetch_webpage previo). Lee el texto directamente
|
||||
del nodo (prioridad: `entities.notes` > `node_name`) y aplica el
|
||||
pipeline `extract_iocs` del registry sobre el. El campo `notes` es lo
|
||||
que el usuario escribe en el panel Note (doble click sobre el nodo).
|
||||
|
||||
Sin red, sin dependencias externas — pensado para probar la app
|
||||
cuando DDG bloquea con captcha o cuando se trabaja en un entorno
|
||||
@@ -76,11 +77,27 @@ def has_group_id_column(conn: sqlite3.Connection) -> bool:
|
||||
return False
|
||||
|
||||
|
||||
def read_text(metadata: dict, node_name: str) -> str:
|
||||
for key in ("text", "description", "query"):
|
||||
v = metadata.get(key)
|
||||
if isinstance(v, str) and v.strip():
|
||||
return v.strip()
|
||||
def read_text(ops_db_path: str, node_id: str, node_name: str) -> str:
|
||||
"""Lee `entities.notes` y cae al `node_name` si esta vacio.
|
||||
|
||||
`notes` es el campo donde el usuario escribe via el panel Note
|
||||
(doble click sobre el nodo). Es el sitio canonico para texto largo.
|
||||
"""
|
||||
notes = ""
|
||||
try:
|
||||
c = sqlite3.connect(ops_db_path)
|
||||
try:
|
||||
row = c.execute(
|
||||
"SELECT notes FROM entities WHERE id=?", (node_id,)
|
||||
).fetchone()
|
||||
if row and isinstance(row[0], str):
|
||||
notes = row[0]
|
||||
finally:
|
||||
c.close()
|
||||
except sqlite3.Error:
|
||||
notes = ""
|
||||
if notes and notes.strip():
|
||||
return notes.strip()
|
||||
return (node_name or "").strip()
|
||||
|
||||
|
||||
@@ -130,10 +147,11 @@ def main() -> int:
|
||||
return 7
|
||||
|
||||
progress(0.10, "reading")
|
||||
text = read_text(metadata, node_name)
|
||||
text = read_text(ops_db_path, node_id, node_name)
|
||||
if not text:
|
||||
msg = ("nodo sin texto. Esperaba metadata.text / description / "
|
||||
"query, o un name con contenido")
|
||||
msg = ("nodo sin texto. Escribe el contenido en el panel Note "
|
||||
"del nodo (doble click para abrir) o pon un name con "
|
||||
"contenido")
|
||||
log(msg)
|
||||
print(json.dumps({"error": msg, "entities_added": 0,
|
||||
"relations_added": 0}))
|
||||
|
||||
@@ -9,10 +9,10 @@ Wire protocol estandar (issue 0026):
|
||||
- exit code 0 = ok, !=0 = error.
|
||||
|
||||
Lectura del texto (en orden de prioridad):
|
||||
1. metadata.text (campo canonico de un nodo Text)
|
||||
2. metadata.description
|
||||
3. metadata.query (compatible con nodos creados desde la barra de busqueda)
|
||||
4. node_name (fallback minimo)
|
||||
1. `entities.notes` (lo que el usuario escribe en el panel Note
|
||||
via doble click — sitio canonico de texto
|
||||
largo)
|
||||
2. node_name (titulo del nodo, fallback minimo)
|
||||
|
||||
Si tras esto el texto es < min_length, falla con exit 2 y mensaje claro.
|
||||
|
||||
@@ -79,12 +79,29 @@ def has_group_id_column(conn: sqlite3.Connection) -> bool:
|
||||
return False
|
||||
|
||||
|
||||
def read_text(metadata: dict, node_name: str) -> str:
|
||||
"""Resuelve el texto a partir del orden de prioridad documentado."""
|
||||
for key in ("text", "description", "query"):
|
||||
v = metadata.get(key)
|
||||
if isinstance(v, str) and v.strip():
|
||||
return v.strip()
|
||||
def read_text(ops_db_path: str, node_id: str, node_name: str) -> str:
|
||||
"""Resuelve el texto a procesar.
|
||||
|
||||
Prioridad:
|
||||
1. `entities.notes` del nodo (lo que el usuario escribe en el panel
|
||||
Note via doble click). Es el sitio canonico para texto largo.
|
||||
2. `node_name` (titulo del nodo) como fallback minimo.
|
||||
"""
|
||||
notes = ""
|
||||
try:
|
||||
c = sqlite3.connect(ops_db_path)
|
||||
try:
|
||||
row = c.execute(
|
||||
"SELECT notes FROM entities WHERE id=?", (node_id,)
|
||||
).fetchone()
|
||||
if row and isinstance(row[0], str):
|
||||
notes = row[0]
|
||||
finally:
|
||||
c.close()
|
||||
except sqlite3.Error:
|
||||
notes = ""
|
||||
if notes and notes.strip():
|
||||
return notes.strip()
|
||||
return (node_name or "").strip()
|
||||
|
||||
|
||||
@@ -223,11 +240,11 @@ def main() -> int:
|
||||
return 7
|
||||
|
||||
progress(0.10, "reading")
|
||||
text = read_text(metadata, node_name)
|
||||
text = read_text(ops_db_path, node_id, node_name)
|
||||
if len(text) < min_length:
|
||||
msg = (f"texto demasiado corto ({len(text)} chars < {min_length}). "
|
||||
f"Esperaba metadata.text / description / query, o un name "
|
||||
f"con mas contenido")
|
||||
f"Escribe el texto en el panel Note del nodo (doble click "
|
||||
f"para abrir) o pon un name mas largo")
|
||||
log(msg)
|
||||
print(json.dumps({"error": msg, "entities_added": 0,
|
||||
"relations_added": 0}))
|
||||
|
||||
Reference in New Issue
Block a user