mirror of
https://github.com/semantica-agi/semantica.git
synced 2026-09-12 04:01:35 +00:00
- Robust ID extraction in CentralityCalculator, CommunityDetector, and ConnectivityAnalyzer - Support for direct Entity objects and dictionaries as node identifiers - Improved Entity hashability in utils/types.py - Added integration test to verify fix and prevent regression
231 lines
9.9 KiB
Python
231 lines
9.9 KiB
Python
|
|
import unittest
|
|
import os
|
|
import json
|
|
import tempfile
|
|
from unittest.mock import MagicMock, patch
|
|
|
|
# Import semantica modules
|
|
# We use try-except to handle potential missing optional dependencies in the test environment
|
|
try:
|
|
from semantica.ingest import FileIngestor, WebIngestor, DBIngestor, FeedIngestor
|
|
from semantica.parse import DocumentParser, PDFParser, StructuredDataParser, JSONParser
|
|
from semantica.semantic_extract import NERExtractor, RelationExtractor, TripletExtractor, SemanticAnalyzer
|
|
from semantica.kg import GraphBuilder, GraphAnalyzer, CentralityCalculator, CommunityDetector
|
|
from semantica.kg import ConnectivityAnalyzer, TemporalGraphQuery, TemporalPatternDetector
|
|
from semantica.ontology import OntologyGenerator, ClassInferrer, PropertyGenerator, OntologyValidator
|
|
from semantica.reasoning import Reasoner, ExplanationGenerator
|
|
from semantica.export import JSONExporter, RDFExporter, OWLExporter, ReportGenerator
|
|
# Visualization might require matplotlib/networkx which might be missing or headless
|
|
from semantica.visualization import KGVisualizer, OntologyVisualizer, AnalyticsVisualizer
|
|
except ImportError as e:
|
|
print(f"Skipping imports due to missing dependencies: {e}")
|
|
|
|
class TestDiseaseNetworkAnalysis(unittest.TestCase):
|
|
|
|
def setUp(self):
|
|
self.temp_dir = tempfile.mkdtemp()
|
|
self.disease_file = os.path.join(self.temp_dir, "disease_data.json")
|
|
|
|
# Sample disease data from the notebook
|
|
self.disease_data = {
|
|
"diseases": [
|
|
{
|
|
"disease_name": "Type 2 Diabetes",
|
|
"icd10_code": "E11",
|
|
"related_diseases": ["Hypertension", "Cardiovascular Disease", "Obesity"],
|
|
"symptoms": ["Increased thirst", "Frequent urination", "Fatigue"],
|
|
"treatments": ["Metformin", "Insulin", "Lifestyle changes"],
|
|
"prevalence": "High"
|
|
},
|
|
{
|
|
"disease_name": "Hypertension",
|
|
"icd10_code": "I10",
|
|
"related_diseases": ["Type 2 Diabetes", "Cardiovascular Disease", "Kidney Disease"],
|
|
"symptoms": ["High blood pressure", "Headaches", "Dizziness"],
|
|
"treatments": ["ACE inhibitors", "Beta blockers", "Lifestyle changes"],
|
|
"prevalence": "Very High"
|
|
}
|
|
]
|
|
}
|
|
|
|
with open(self.disease_file, 'w') as f:
|
|
json.dump(self.disease_data, f, indent=2)
|
|
|
|
def tearDown(self):
|
|
import shutil
|
|
shutil.rmtree(self.temp_dir)
|
|
|
|
def test_pipeline_execution(self):
|
|
"""
|
|
Replicates the logic of cookbook/use_cases/healthcare/02_Disease_Network_Analysis.ipynb
|
|
"""
|
|
# --- Step 1: Ingest ---
|
|
file_ingestor = FileIngestor()
|
|
json_parser = JSONParser()
|
|
|
|
# We mock WebIngestor/DBIngestor to avoid external calls
|
|
web_ingestor = MagicMock()
|
|
web_ingestor.ingest_url.return_value = {"content": "Mock API Content"}
|
|
|
|
# Ingest file
|
|
file_objects = file_ingestor.ingest_file(self.disease_file, read_content=True)
|
|
self.assertIsNotNone(file_objects)
|
|
|
|
# Parse
|
|
parsed_data = json_parser.parse(self.disease_file)
|
|
self.assertIsNotNone(parsed_data)
|
|
|
|
# --- Step 2: Extract ---
|
|
# The notebook manually extracts entities/relationships from the parsed JSON
|
|
# It instantiates extractors but doesn't use them for the main logic shown
|
|
# We instantiate them to ensure they can be instantiated
|
|
try:
|
|
ner_extractor = NERExtractor(method="pattern") # Use pattern to avoid spacy model load if missing
|
|
relation_extractor = RelationExtractor()
|
|
except Exception as e:
|
|
print(f"Warning: Could not instantiate extractors: {e}")
|
|
|
|
disease_entities = []
|
|
disease_relationships = []
|
|
|
|
# Extraction logic copied from notebook
|
|
if parsed_data and parsed_data.data:
|
|
diseases = parsed_data.data.get("diseases", []) if isinstance(parsed_data.data, dict) else []
|
|
|
|
for disease in diseases:
|
|
if isinstance(disease, dict):
|
|
disease_name = disease.get("disease_name", "")
|
|
|
|
disease_entities.append({
|
|
"id": disease_name,
|
|
"type": "Disease",
|
|
"name": disease_name,
|
|
"properties": {
|
|
"icd10_code": disease.get("icd10_code", ""),
|
|
"prevalence": disease.get("prevalence", "")
|
|
}
|
|
})
|
|
|
|
# Related diseases
|
|
for related in disease.get("related_diseases", []):
|
|
disease_entities.append({
|
|
"id": related,
|
|
"type": "Disease",
|
|
"name": related,
|
|
"properties": {}
|
|
})
|
|
disease_relationships.append({
|
|
"source": disease_name,
|
|
"target": related,
|
|
"type": "related_to",
|
|
"properties": {}
|
|
})
|
|
|
|
# Symptoms
|
|
for symptom in disease.get("symptoms", []):
|
|
disease_entities.append({
|
|
"id": symptom,
|
|
"type": "Symptom",
|
|
"name": symptom,
|
|
"properties": {}
|
|
})
|
|
disease_relationships.append({
|
|
"source": disease_name,
|
|
"target": symptom,
|
|
"type": "has_symptom",
|
|
"properties": {}
|
|
})
|
|
|
|
# Treatments
|
|
for treatment in disease.get("treatments", []):
|
|
disease_entities.append({
|
|
"id": treatment,
|
|
"type": "Treatment",
|
|
"name": treatment,
|
|
"properties": {}
|
|
})
|
|
disease_relationships.append({
|
|
"source": disease_name,
|
|
"target": treatment,
|
|
"type": "treated_with",
|
|
"properties": {}
|
|
})
|
|
|
|
self.assertTrue(len(disease_entities) > 0)
|
|
self.assertTrue(len(disease_relationships) > 0)
|
|
|
|
# --- Step 3: Build KG ---
|
|
builder = GraphBuilder(merge_entities=True, entity_resolution_strategy="exact")
|
|
ontology_generator = OntologyGenerator()
|
|
class_inferrer = ClassInferrer()
|
|
property_generator = PropertyGenerator()
|
|
ontology_validator = OntologyValidator()
|
|
|
|
# Combine entities and relationships into a source structure for the builder
|
|
sources = [{"entities": disease_entities, "relationships": disease_relationships}]
|
|
disease_kg = builder.build(sources)
|
|
self.assertIn("entities", disease_kg)
|
|
self.assertIn("relationships", disease_kg)
|
|
|
|
disease_ontology = ontology_generator.generate_ontology({
|
|
"entities": disease_entities,
|
|
"relationships": disease_relationships
|
|
})
|
|
self.assertIn("classes", disease_ontology)
|
|
|
|
# --- Step 4: Analyze ---
|
|
graph_analyzer = GraphAnalyzer()
|
|
centrality_calculator = CentralityCalculator()
|
|
community_detector = CommunityDetector()
|
|
connectivity_analyzer = ConnectivityAnalyzer()
|
|
|
|
metrics = graph_analyzer.compute_metrics(disease_kg)
|
|
self.assertIsNotNone(metrics)
|
|
|
|
centrality_result = centrality_calculator.calculate_degree_centrality(disease_kg)
|
|
self.assertIn("centrality", centrality_result)
|
|
|
|
communities = community_detector.detect_communities(disease_kg)
|
|
# communities might be a list or dict depending on implementation/algorithm
|
|
self.assertTrue(len(communities) > 0) # Should have found some communities or at least one
|
|
|
|
connectivity = connectivity_analyzer.analyze_connectivity(disease_kg)
|
|
self.assertIn("components", connectivity)
|
|
|
|
# --- Step 5: Predict Outcomes (Reasoning) ---
|
|
# Inference logic updated to use Reasoner facade
|
|
reasoner = Reasoner()
|
|
reasoner.add_rule("IF related_to(?a, ?b) AND related_to(?b, ?c) THEN comorbid(?a, ?c)")
|
|
|
|
# Add some facts for testing
|
|
reasoner.add_fact("related_to(Diabetes, Hypertension)")
|
|
reasoner.add_fact("related_to(Hypertension, Obesity)")
|
|
|
|
outcome_predictions = reasoner.infer_facts([])
|
|
self.assertIn("comorbid(Diabetes, Obesity)", outcome_predictions)
|
|
|
|
# --- Step 6: Export/Report ---
|
|
# Mocking exporters to avoid file writing issues or just testing they run
|
|
json_exporter = JSONExporter()
|
|
report_generator = ReportGenerator()
|
|
|
|
out_file = os.path.join(self.temp_dir, "disease_kg.json")
|
|
json_exporter.export_knowledge_graph(disease_kg, out_file)
|
|
self.assertTrue(os.path.exists(out_file))
|
|
|
|
report_data = {
|
|
"summary": "Test Summary",
|
|
"diseases_analyzed": 10,
|
|
"relationships": 20,
|
|
"predictions": len(outcome_predictions),
|
|
"quality_score": 0.95
|
|
}
|
|
|
|
report = report_generator.generate_report(report_data, format="markdown")
|
|
self.assertIsInstance(report, str)
|
|
self.assertIn("Test Summary", report)
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|