Add comprehensive progress tracking with Jupyter/Colab support

- Enhanced progress tracker with automatic Jupyter/Colab detection
- Added detailed progress tracking to all deduplication modules
- Added detailed progress tracking to all semantic_extract modules
- Progress tracker now always enabled automatically
- Shows remaining items, percentages, ETA, and processing rates
- Works in both Jupyter notebooks and Google Colab
- Dynamic update intervals based on dataset size
- Improved display handling for Colab compatibility
This commit is contained in:
KaifAhmad1
2025-12-29 13:11:22 +05:30
parent 0c27f0fcd9
commit d2d6adafdb
17 changed files with 1148 additions and 225 deletions
@@ -140,7 +140,7 @@
{
"data": {
"text/html": [
"<div style='font-family: monospace;'><h4>🧠 Semantica - 📊 Current Progress</h4><table style='width: 100%; border-collapse: collapse;'><tr><th>Status</th><th>Action</th><th>Module</th><th>Submodule</th><th>Progress</th><th>ETA</th><th>Rate</th><th>Time</th></tr><tr><td>❌</td><td>Semantica is parsing</td><td>🔍 parse</td><td>DocumentParser</td><td>-</td><td>-</td><td>-</td><td>0.00s</td></tr><tr><td>❌</td><td>Semantica is parsing</td><td>🔍 parse</td><td>DocumentParser</td><td>-</td><td>-</td><td>-</td><td>0.00s</td></tr><tr><td>❌</td><td>Semantica is parsing</td><td>🔍 parse</td><td>DocumentParser</td><td>-</td><td>-</td><td>-</td><td>0.00s</td></tr><tr><td>❌</td><td>Semantica is parsing</td><td>🔍 parse</td><td>DocumentParser</td><td>-</td><td>-</td><td>-</td><td>0.00s</td></tr><tr><td>❌</td><td>Semantica is parsing</td><td>🔍 parse</td><td>DocumentParser</td><td>-</td><td>-</td><td>-</td><td>0.00s</td></tr><tr><td>✅</td><td>Semantica is normalizing</td><td>🔧 normalize</td><td>TextNormalizer</td><td>100.0%</td><td>-</td><td>-</td><td>0.00s</td></tr><tr><td>✅</td><td>Semantica is extracting</td><td>🎯 semantic_extract</td><td>NERExtractor</td><td>100.0%</td><td>-</td><td>-</td><td>0.56s</td></tr><tr><td>✅</td><td>Semantica is extracting</td><td>🎯 semantic_extract</td><td>RelationExtractor</td><td>100.0%</td><td>-</td><td>-</td><td>0.46s</td></tr><tr><td>🔄</td><td>Semantica is deduplicating</td><td>🔄 deduplication</td><td>DuplicateDetector</td><td>-</td><td>-</td><td>-</td><td>634.12s</td></tr><tr><td>🔄</td><td>Semantica is deduplicating</td><td>🔄 deduplication</td><td>SimilarityCalculator</td><td>-</td><td>-</td><td>-</td><td>0.01s</td></tr></table></div>"
"<div style='font-family: monospace;'><h4>🧠 Semantica - 📊 Current Progress</h4><table style='width: 100%; border-collapse: collapse;'><tr><th>Status</th><th>Action</th><th>Module</th><th>Submodule</th><th>Progress</th><th>ETA</th><th>Rate</th><th>Time</th></tr><tr><td>❌</td><td>Semantica is parsing</td><td>🔍 parse</td><td>DocumentParser</td><td>-</td><td>-</td><td>-</td><td>0.01s</td></tr><tr><td>❌</td><td>Semantica is parsing</td><td>🔍 parse</td><td>DocumentParser</td><td>-</td><td>-</td><td>-</td><td>0.00s</td></tr><tr><td>❌</td><td>Semantica is parsing</td><td>🔍 parse</td><td>DocumentParser</td><td>-</td><td>-</td><td>-</td><td>0.00s</td></tr><tr><td>❌</td><td>Semantica is parsing</td><td>🔍 parse</td><td>DocumentParser</td><td>-</td><td>-</td><td>-</td><td>0.01s</td></tr><tr><td>❌</td><td>Semantica is parsing</td><td>🔍 parse</td><td>DocumentParser</td><td>-</td><td>-</td><td>-</td><td>0.00s</td></tr><tr><td>✅</td><td>Semantica is normalizing</td><td>🔧 normalize</td><td>TextNormalizer</td><td>100.0%</td><td>-</td><td>-</td><td>0.00s</td></tr><tr><td>✅</td><td>Semantica is extracting</td><td>🎯 semantic_extract</td><td>NERExtractor</td><td>100.0%</td><td>-</td><td>-</td><td>1.16s</td></tr><tr><td>✅</td><td>Semantica is extracting</td><td>🎯 semantic_extract</td><td>RelationExtractor</td><td>100.0%</td><td>-</td><td>-</td><td>0.67s</td></tr><tr><td>🔄</td><td>Semantica is deduplicating</td><td>🔄 deduplication</td><td>DuplicateDetector</td><td>-</td><td>-</td><td>-</td><td>723.21s</td></tr><tr><td>🔄</td><td>Semantica is deduplicating</td><td>🔄 deduplication</td><td>SimilarityCalculator</td><td>-</td><td>-</td><td>-</td><td>0.01s</td></tr></table></div>"
],
"text/plain": [
"<IPython.core.display.HTML object>"
+79 -9
View File
@@ -110,7 +110,10 @@ class ConflictAnalyzer:
self.logger = get_logger("conflict_analyzer")
self.config = config or {}
self.config.update(kwargs)
# Initialize progress tracker and ensure it's enabled
self.progress_tracker = get_progress_tracker()
if not self.progress_tracker.enabled:
self.progress_tracker.enabled = True
def analyze_conflicts(self, conflicts: List[Conflict]) -> Dict[str, Any]:
"""
@@ -131,18 +134,85 @@ class ConflictAnalyzer:
)
try:
self.progress_tracker.update_tracking(
tracking_id, message="Analyzing conflict patterns..."
total_steps = 6 # by_type, by_severity, by_source, by_entity, by_property, patterns, recommendations
current_step = 0
# Step 1: Analyze by type
current_step += 1
remaining_steps = total_steps - current_step
self.progress_tracker.update_progress(
tracking_id,
processed=current_step,
total=total_steps,
message=f"Analyzing by type... ({current_step}/{total_steps}, remaining: {remaining_steps} steps)"
)
by_type = self._analyze_by_type(conflicts)
# Step 2: Analyze by severity
current_step += 1
remaining_steps = total_steps - current_step
self.progress_tracker.update_progress(
tracking_id,
processed=current_step,
total=total_steps,
message=f"Analyzing by severity... ({current_step}/{total_steps}, remaining: {remaining_steps} steps)"
)
by_severity = self._analyze_by_severity(conflicts)
# Step 3: Analyze by source
current_step += 1
remaining_steps = total_steps - current_step
self.progress_tracker.update_progress(
tracking_id,
processed=current_step,
total=total_steps,
message=f"Analyzing by source... ({current_step}/{total_steps}, remaining: {remaining_steps} steps)"
)
by_source = self._analyze_by_source(conflicts)
# Step 4: Analyze by entity
current_step += 1
remaining_steps = total_steps - current_step
self.progress_tracker.update_progress(
tracking_id,
processed=current_step,
total=total_steps,
message=f"Analyzing by entity... ({current_step}/{total_steps}, remaining: {remaining_steps} steps)"
)
by_entity = self._analyze_by_entity(conflicts)
# Step 5: Analyze by property
current_step += 1
remaining_steps = total_steps - current_step
self.progress_tracker.update_progress(
tracking_id,
processed=current_step,
total=total_steps,
message=f"Analyzing by property... ({current_step}/{total_steps}, remaining: {remaining_steps} steps)"
)
by_property = self._analyze_by_property(conflicts)
# Step 6: Identify patterns and generate recommendations
current_step += 1
remaining_steps = total_steps - current_step
self.progress_tracker.update_progress(
tracking_id,
processed=current_step,
total=total_steps,
message=f"Identifying patterns and generating recommendations... ({current_step}/{total_steps}, remaining: {remaining_steps} steps)"
)
patterns = self._identify_patterns(conflicts)
recommendations = self._generate_recommendations(conflicts)
analysis = {
"total_conflicts": len(conflicts),
"by_type": self._analyze_by_type(conflicts),
"by_severity": self._analyze_by_severity(conflicts),
"by_source": self._analyze_by_source(conflicts),
"by_entity": self._analyze_by_entity(conflicts),
"by_property": self._analyze_by_property(conflicts),
"patterns": self._identify_patterns(conflicts),
"recommendations": self._generate_recommendations(conflicts),
"by_type": by_type,
"by_severity": by_severity,
"by_source": by_source,
"by_entity": by_entity,
"by_property": by_property,
"patterns": patterns,
"recommendations": recommendations,
}
self.progress_tracker.stop_tracking(
+285 -45
View File
@@ -217,7 +217,19 @@ class ConflictDetector:
)
total_entities = len(entities)
update_interval = max(1, total_entities // 20) # Update every 5%
if total_entities <= 10:
update_interval = 1 # Update every item for small datasets
else:
update_interval = max(1, min(10, total_entities // 100))
# Initial progress update - ALWAYS show this
remaining = total_entities
self.progress_tracker.update_progress(
tracking_id,
processed=0,
total=total_entities,
message=f"Analyzing entities... 0/{total_entities} (remaining: {remaining})"
)
for i, entity in enumerate(entities):
entity_id = entity.get("id") or entity.get("entity_id")
@@ -231,18 +243,37 @@ class ConflictDetector:
entity_groups[entity_id] = []
entity_groups[entity_id].append(entity)
# Update progress periodically
if (i + 1) % update_interval == 0 or (i + 1) == total_entities:
remaining = total_entities - (i + 1)
# Update progress: always update for small datasets, or at intervals for large ones
should_update = (
(i + 1) % update_interval == 0 or
(i + 1) == total_entities or
i == 0 or
total_entities <= 10 # Always update for small datasets
)
if should_update:
self.progress_tracker.update_progress(
tracking_id,
processed=i + 1,
total=total_entities,
message=f"Analyzing entities... {i + 1}/{total_entities}"
message=f"Analyzing entities... {i + 1}/{total_entities} (remaining: {remaining})"
)
# Check each entity group for conflicts
total_groups = len(entity_groups)
group_update_interval = max(1, total_groups // 20) # Update every 5%
if total_groups <= 10:
group_update_interval = 1 # Update every item for small datasets
else:
group_update_interval = max(1, min(10, total_groups // 100))
# Initial progress update for group checking
remaining_groups = total_groups
self.progress_tracker.update_progress(
tracking_id,
processed=0,
total=total_groups,
message=f"Checking entity groups for conflicts... 0/{total_groups} (remaining: {remaining_groups})"
)
for j, (entity_id, entity_list) in enumerate(entity_groups.items()):
if len(entity_list) < 2:
@@ -302,13 +333,20 @@ class ConflictDetector:
f"has conflicting values: {unique_values}"
)
# Update progress periodically for group checking
if (j + 1) % group_update_interval == 0 or (j + 1) == total_groups:
remaining_groups = total_groups - (j + 1)
# Update progress: always update for small datasets, or at intervals for large ones
should_update = (
(j + 1) % group_update_interval == 0 or
(j + 1) == total_groups or
j == 0 or
total_groups <= 10 # Always update for small datasets
)
if should_update:
self.progress_tracker.update_progress(
tracking_id,
processed=j + 1,
total=total_groups,
message=f"Checking entity groups for conflicts... {j + 1}/{total_groups}"
message=f"Checking entity groups for conflicts... {j + 1}/{total_groups} (remaining: {remaining_groups})"
)
self.progress_tracker.stop_tracking(
@@ -365,7 +403,19 @@ class ConflictDetector:
# Group relationships by ID
rel_groups: Dict[str, List[Dict[str, Any]]] = {}
total_rels = len(relationships)
update_interval = max(1, total_rels // 20) # Update every 5%
if total_rels <= 10:
update_interval = 1 # Update every item for small datasets
else:
update_interval = max(1, min(10, total_rels // 100))
# Initial progress update
remaining = total_rels
self.progress_tracker.update_progress(
tracking_id,
processed=0,
total=total_rels,
message=f"Grouping relationships... 0/{total_rels} (remaining: {remaining})"
)
for i, rel in enumerate(relationships):
rel_id = (
@@ -377,18 +427,37 @@ class ConflictDetector:
rel_groups[rel_id] = []
rel_groups[rel_id].append(rel)
# Update progress periodically
if (i + 1) % update_interval == 0 or (i + 1) == total_rels:
remaining = total_rels - (i + 1)
# Update progress: always update for small datasets, or at intervals for large ones
should_update = (
(i + 1) % update_interval == 0 or
(i + 1) == total_rels or
i == 0 or
total_rels <= 10 # Always update for small datasets
)
if should_update:
self.progress_tracker.update_progress(
tracking_id,
processed=i + 1,
total=total_rels,
message=f"Grouping relationships... {i + 1}/{total_rels}"
message=f"Grouping relationships... {i + 1}/{total_rels} (remaining: {remaining})"
)
# Check for conflicts
total_groups = len(rel_groups)
group_update_interval = max(1, total_groups // 20) # Update every 5%
if total_groups <= 10:
group_update_interval = 1 # Update every item for small datasets
else:
group_update_interval = max(1, min(10, total_groups // 100))
# Initial progress update for group checking
remaining_groups = total_groups
self.progress_tracker.update_progress(
tracking_id,
processed=0,
total=total_groups,
message=f"Checking relationship groups... 0/{total_groups} (remaining: {remaining_groups})"
)
for j, (rel_id, rel_list) in enumerate(rel_groups.items()):
if len(rel_list) < 2:
@@ -413,13 +482,20 @@ class ConflictDetector:
conflicts.append(conflict)
self.detected_conflicts[conflict.conflict_id] = conflict
# Update progress periodically
if (j + 1) % group_update_interval == 0 or (j + 1) == total_groups:
remaining_groups = total_groups - (j + 1)
# Update progress: always update for small datasets, or at intervals for large ones
should_update = (
(j + 1) % group_update_interval == 0 or
(j + 1) == total_groups or
j == 0 or
total_groups <= 10 # Always update for small datasets
)
if should_update:
self.progress_tracker.update_progress(
tracking_id,
processed=j + 1,
total=total_groups,
message=f"Checking relationship groups... {j + 1}/{total_groups}"
message=f"Checking relationship groups... {j + 1}/{total_groups} (remaining: {remaining_groups})"
)
self.progress_tracker.stop_tracking(
@@ -472,20 +548,39 @@ class ConflictDetector:
)
total_fields = len(fields_to_check)
update_interval = max(1, total_fields // 20) # Update every 5%
if total_fields <= 10:
update_interval = 1 # Update every item for small datasets
else:
update_interval = max(1, min(10, total_fields // 100))
# Initial progress update
remaining = total_fields
self.progress_tracker.update_progress(
tracking_id,
processed=0,
total=total_fields,
message=f"Checking fields for conflicts... 0/{total_fields} (remaining: {remaining})"
)
# Check each field
for i, field_name in enumerate(fields_to_check):
conflicts = self.detect_value_conflicts(entities, field_name, entity_type)
all_conflicts.extend(conflicts)
# Update progress periodically
if (i + 1) % update_interval == 0 or (i + 1) == total_fields:
remaining = total_fields - (i + 1)
# Update progress: always update for small datasets, or at intervals for large ones
should_update = (
(i + 1) % update_interval == 0 or
(i + 1) == total_fields or
i == 0 or
total_fields <= 10 # Always update for small datasets
)
if should_update:
self.progress_tracker.update_progress(
tracking_id,
processed=i + 1,
total=total_fields,
message=f"Checking fields for conflicts... {i + 1}/{total_fields}"
message=f"Checking fields for conflicts... {i + 1}/{total_fields} (remaining: {remaining})"
)
self.progress_tracker.stop_tracking(
@@ -626,7 +721,19 @@ class ConflictDetector:
# Group entities by ID
entity_groups: Dict[str, List[Dict[str, Any]]] = {}
total_entities = len(entities)
update_interval = max(1, total_entities // 20) # Update every 5%
if total_entities <= 10:
update_interval = 1 # Update every item for small datasets
else:
update_interval = max(1, min(10, total_entities // 100))
# Initial progress update
remaining = total_entities
self.progress_tracker.update_progress(
tracking_id,
processed=0,
total=total_entities,
message=f"Grouping entities... 0/{total_entities} (remaining: {remaining})"
)
for i, entity in enumerate(entities):
entity_id = entity.get("id") or entity.get("entity_id")
@@ -637,18 +744,37 @@ class ConflictDetector:
entity_groups[entity_id] = []
entity_groups[entity_id].append(entity)
# Update progress periodically
if (i + 1) % update_interval == 0 or (i + 1) == total_entities:
remaining = total_entities - (i + 1)
# Update progress: always update for small datasets, or at intervals for large ones
should_update = (
(i + 1) % update_interval == 0 or
(i + 1) == total_entities or
i == 0 or
total_entities <= 10 # Always update for small datasets
)
if should_update:
self.progress_tracker.update_progress(
tracking_id,
processed=i + 1,
total=total_entities,
message=f"Grouping entities... {i + 1}/{total_entities}"
message=f"Grouping entities... {i + 1}/{total_entities} (remaining: {remaining})"
)
# Check each entity group for type conflicts
total_groups = len(entity_groups)
group_update_interval = max(1, total_groups // 20) # Update every 5%
if total_groups <= 10:
group_update_interval = 1 # Update every item for small datasets
else:
group_update_interval = max(1, min(10, total_groups // 100))
# Initial progress update for group checking
remaining_groups = total_groups
self.progress_tracker.update_progress(
tracking_id,
processed=0,
total=total_groups,
message=f"Checking entity groups for type conflicts... 0/{total_groups} (remaining: {remaining_groups})"
)
for j, (entity_id, entity_list) in enumerate(entity_groups.items()):
if len(entity_list) < 2:
@@ -706,13 +832,20 @@ class ConflictDetector:
f"{unique_types}"
)
# Update progress periodically for group checking
if (j + 1) % group_update_interval == 0 or (j + 1) == total_groups:
remaining_groups = total_groups - (j + 1)
# Update progress: always update for small datasets, or at intervals for large ones
should_update = (
(j + 1) % group_update_interval == 0 or
(j + 1) == total_groups or
j == 0 or
total_groups <= 10 # Always update for small datasets
)
if should_update:
self.progress_tracker.update_progress(
tracking_id,
processed=j + 1,
total=total_groups,
message=f"Checking entity groups for type conflicts... {j + 1}/{total_groups}"
message=f"Checking entity groups for type conflicts... {j + 1}/{total_groups} (remaining: {remaining_groups})"
)
self.progress_tracker.stop_tracking(
@@ -765,7 +898,19 @@ class ConflictDetector:
# Group entities by ID
entity_groups: Dict[str, List[Dict[str, Any]]] = {}
total_entities = len(entities)
update_interval = max(1, total_entities // 20) # Update every 5%
if total_entities <= 10:
update_interval = 1 # Update every item for small datasets
else:
update_interval = max(1, min(10, total_entities // 100))
# Initial progress update
remaining = total_entities
self.progress_tracker.update_progress(
tracking_id,
processed=0,
total=total_entities,
message=f"Grouping entities... 0/{total_entities} (remaining: {remaining})"
)
for i, entity in enumerate(entities):
entity_id = entity.get("id") or entity.get("entity_id")
@@ -776,18 +921,37 @@ class ConflictDetector:
entity_groups[entity_id] = []
entity_groups[entity_id].append(entity)
# Update progress periodically
if (i + 1) % update_interval == 0 or (i + 1) == total_entities:
remaining = total_entities - (i + 1)
# Update progress: always update for small datasets, or at intervals for large ones
should_update = (
(i + 1) % update_interval == 0 or
(i + 1) == total_entities or
i == 0 or
total_entities <= 10 # Always update for small datasets
)
if should_update:
self.progress_tracker.update_progress(
tracking_id,
processed=i + 1,
total=total_entities,
message=f"Grouping entities... {i + 1}/{total_entities}"
message=f"Grouping entities... {i + 1}/{total_entities} (remaining: {remaining})"
)
# Check each entity group for temporal conflicts
total_groups = len(entity_groups)
group_update_interval = max(1, total_groups // 20) # Update every 5%
if total_groups <= 10:
group_update_interval = 1 # Update every item for small datasets
else:
group_update_interval = max(1, min(10, total_groups // 100))
# Initial progress update for group checking
remaining_groups = total_groups
self.progress_tracker.update_progress(
tracking_id,
processed=0,
total=total_groups,
message=f"Checking entity groups for temporal conflicts... 0/{total_groups} (remaining: {remaining_groups})"
)
for j, (entity_id, entity_list) in enumerate(entity_groups.items()):
if len(entity_list) < 2:
@@ -875,13 +1039,20 @@ class ConflictDetector:
f"has conflicting values: {unique_values}"
)
# Update progress periodically for group checking
if (j + 1) % group_update_interval == 0 or (j + 1) == total_groups:
remaining_groups = total_groups - (j + 1)
# Update progress: always update for small datasets, or at intervals for large ones
should_update = (
(j + 1) % group_update_interval == 0 or
(j + 1) == total_groups or
j == 0 or
total_groups <= 10 # Always update for small datasets
)
if should_update:
self.progress_tracker.update_progress(
tracking_id,
processed=j + 1,
total=total_groups,
message=f"Checking entity groups for temporal conflicts... {j + 1}/{total_groups}"
message=f"Checking entity groups for temporal conflicts... {j + 1}/{total_groups} (remaining: {remaining_groups})"
)
self.progress_tracker.stop_tracking(
@@ -930,7 +1101,19 @@ class ConflictDetector:
# Group entities by ID
entity_groups: Dict[str, List[Dict[str, Any]]] = {}
total_entities = len(entities)
update_interval = max(1, total_entities // 20) # Update every 5%
if total_entities <= 10:
update_interval = 1 # Update every item for small datasets
else:
update_interval = max(1, min(10, total_entities // 100))
# Initial progress update
remaining = total_entities
self.progress_tracker.update_progress(
tracking_id,
processed=0,
total=total_entities,
message=f"Grouping entities... 0/{total_entities} (remaining: {remaining})"
)
for i, entity in enumerate(entities):
entity_id = entity.get("id") or entity.get("entity_id")
@@ -941,18 +1124,37 @@ class ConflictDetector:
entity_groups[entity_id] = []
entity_groups[entity_id].append(entity)
# Update progress periodically
if (i + 1) % update_interval == 0 or (i + 1) == total_entities:
remaining = total_entities - (i + 1)
# Update progress: always update for small datasets, or at intervals for large ones
should_update = (
(i + 1) % update_interval == 0 or
(i + 1) == total_entities or
i == 0 or
total_entities <= 10 # Always update for small datasets
)
if should_update:
self.progress_tracker.update_progress(
tracking_id,
processed=i + 1,
total=total_entities,
message=f"Grouping entities... {i + 1}/{total_entities}"
message=f"Grouping entities... {i + 1}/{total_entities} (remaining: {remaining})"
)
# Check each entity group for logical conflicts
total_groups = len(entity_groups)
group_update_interval = max(1, total_groups // 20) # Update every 5%
if total_groups <= 10:
group_update_interval = 1 # Update every item for small datasets
else:
group_update_interval = max(1, min(10, total_groups // 100))
# Initial progress update for group checking
remaining_groups = total_groups
self.progress_tracker.update_progress(
tracking_id,
processed=0,
total=total_groups,
message=f"Checking entity groups for logical conflicts... 0/{total_groups} (remaining: {remaining_groups})"
)
for j, (entity_id, entity_list) in enumerate(entity_groups.items()):
if len(entity_list) < 2:
@@ -1017,13 +1219,20 @@ class ConflictDetector:
)
break
# Update progress periodically for group checking
if (j + 1) % group_update_interval == 0 or (j + 1) == total_groups:
remaining_groups = total_groups - (j + 1)
# Update progress: always update for small datasets, or at intervals for large ones
should_update = (
(j + 1) % group_update_interval == 0 or
(j + 1) == total_groups or
j == 0 or
total_groups <= 10 # Always update for small datasets
)
if should_update:
self.progress_tracker.update_progress(
tracking_id,
processed=j + 1,
total=total_groups,
message=f"Checking entity groups for logical conflicts... {j + 1}/{total_groups}"
message=f"Checking entity groups for logical conflicts... {j + 1}/{total_groups} (remaining: {remaining_groups})"
)
self.progress_tracker.stop_tracking(
@@ -1165,8 +1374,23 @@ class ConflictDetector:
resolved_count = 0
unresolved_count = 0
total_conflicts = len(conflicts)
if total_conflicts <= 10:
update_interval = 1 # Update every item for small datasets
else:
update_interval = max(1, min(10, total_conflicts // 100))
# Initial progress update
remaining = total_conflicts
self.progress_tracker.update_progress(
tracking_id,
processed=0,
total=total_conflicts,
message=f"Resolving conflicts... 0/{total_conflicts} (remaining: {remaining})"
)
for conflict in conflicts:
for i, conflict in enumerate(conflicts):
if self.auto_resolve:
# Simple resolution logic: pick value with highest confidence
# This is a placeholder for more complex logic
@@ -1177,6 +1401,22 @@ class ConflictDetector:
unresolved_count += 1
else:
unresolved_count += 1
remaining = total_conflicts - (i + 1)
# Update progress: always update for small datasets, or at intervals for large ones
should_update = (
(i + 1) % update_interval == 0 or
(i + 1) == total_conflicts or
i == 0 or
total_conflicts <= 10 # Always update for small datasets
)
if should_update:
self.progress_tracker.update_progress(
tracking_id,
processed=i + 1,
total=total_conflicts,
message=f"Resolving conflicts... {i + 1}/{total_conflicts} (remaining: {remaining})"
)
self.progress_tracker.stop_tracking(
tracking_id,
+23 -4
View File
@@ -304,19 +304,38 @@ class ConflictResolver:
try:
results = []
total_conflicts = len(conflicts)
update_interval = max(1, total_conflicts // 20) # Update every 5%
if total_conflicts <= 10:
update_interval = 1 # Update every item for small datasets
else:
update_interval = max(1, min(10, total_conflicts // 100))
# Initial progress update
remaining = total_conflicts
self.progress_tracker.update_progress(
tracking_id,
processed=0,
total=total_conflicts,
message=f"Resolving conflicts... 0/{total_conflicts} (remaining: {remaining})"
)
for i, conflict in enumerate(conflicts):
result = self.resolve_conflict(conflict, strategy)
results.append(result)
# Update progress periodically
if (i + 1) % update_interval == 0 or (i + 1) == total_conflicts:
remaining = total_conflicts - (i + 1)
# Update progress: always update for small datasets, or at intervals for large ones
should_update = (
(i + 1) % update_interval == 0 or
(i + 1) == total_conflicts or
i == 0 or
total_conflicts <= 10 # Always update for small datasets
)
if should_update:
self.progress_tracker.update_progress(
tracking_id,
processed=i + 1,
total=total_conflicts,
message=f"Resolving conflicts... {i + 1}/{total_conflicts}"
message=f"Resolving conflicts... {i + 1}/{total_conflicts} (remaining: {remaining})"
)
self.progress_tracker.stop_tracking(
+121 -26
View File
@@ -138,8 +138,10 @@ class ClusterBuilder:
self.max_cluster_size = max_cluster_size
self.use_hierarchical = use_hierarchical
# Initialize progress tracker
# Initialize progress tracker and ensure it's enabled
self.progress_tracker = get_progress_tracker()
if not self.progress_tracker.enabled:
self.progress_tracker.enabled = True
self.logger.debug(
f"Cluster builder initialized: threshold={similarity_threshold}, "
@@ -171,8 +173,17 @@ class ClusterBuilder:
try:
threshold = options.get("threshold", self.similarity_threshold)
self.progress_tracker.update_tracking(
tracking_id, message=f"Clustering {len(entities)} entities..."
total_steps = 4 # Clustering, filtering, finding unclustered, quality metrics
current_step = 0
# Step 1: Clustering
current_step += 1
remaining_steps = total_steps - current_step
self.progress_tracker.update_progress(
tracking_id,
processed=current_step,
total=total_steps,
message=f"Clustering {len(entities)} entities... ({current_step}/{total_steps}, remaining: {remaining_steps} steps)"
)
if self.use_hierarchical:
@@ -180,9 +191,14 @@ class ClusterBuilder:
else:
clusters = self._graph_based_clustering(entities, threshold, tracking_id)
# Filter clusters by size
self.progress_tracker.update_tracking(
tracking_id, message="Filtering clusters by size..."
# Step 2: Filter clusters by size
current_step += 1
remaining_steps = total_steps - current_step
self.progress_tracker.update_progress(
tracking_id,
processed=current_step,
total=total_steps,
message=f"Filtering clusters by size... ({current_step}/{total_steps}, {len(clusters)} clusters, remaining: {remaining_steps} steps)"
)
valid_clusters = [
c
@@ -190,9 +206,14 @@ class ClusterBuilder:
if self.min_cluster_size <= len(c.entities) <= self.max_cluster_size
]
# Find unclustered entities
self.progress_tracker.update_tracking(
tracking_id, message="Finding unclustered entities..."
# Step 3: Find unclustered entities
current_step += 1
remaining_steps = total_steps - current_step
self.progress_tracker.update_progress(
tracking_id,
processed=current_step,
total=total_steps,
message=f"Finding unclustered entities... ({current_step}/{total_steps}, remaining: {remaining_steps} steps)"
)
clustered_entity_ids = set()
for cluster in valid_clusters:
@@ -204,9 +225,14 @@ class ClusterBuilder:
e for e in entities if (e.get("id") or id(e)) not in clustered_entity_ids
]
# Calculate quality metrics
self.progress_tracker.update_tracking(
tracking_id, message="Calculating cluster quality metrics..."
# Step 4: Calculate quality metrics
current_step += 1
remaining_steps = total_steps - current_step
self.progress_tracker.update_progress(
tracking_id,
processed=current_step,
total=total_steps,
message=f"Calculating cluster quality metrics... ({current_step}/{total_steps}, {len(valid_clusters)} clusters, remaining: {remaining_steps} steps)"
)
quality_metrics = self._calculate_cluster_quality(valid_clusters)
@@ -246,6 +272,23 @@ class ClusterBuilder:
clusters_dict = {}
cluster_id_counter = 0
total_pairs = len(similarity_pairs)
processed_pairs = 0
if total_pairs <= 10:
update_interval = 1 # Update every item for small datasets
else:
update_interval = max(1, min(10, total_pairs // 100))
# Initial progress update
if tracking_id and total_pairs > 0:
remaining = total_pairs
self.progress_tracker.update_progress(
tracking_id,
processed=0,
total=total_pairs,
message=f"Building clusters from similarity pairs... 0/{total_pairs} (remaining: {remaining})"
)
for entity1, entity2, score in similarity_pairs:
entity1_id = entity1.get("id") or id(entity1)
entity2_id = entity2.get("id") or id(entity2)
@@ -293,6 +336,24 @@ class ClusterBuilder:
entity_to_cluster[entity_id] = cluster1
del clusters_dict[cluster2]
processed_pairs += 1
remaining = total_pairs - processed_pairs
# Update progress: always update for small datasets, or at intervals for large ones
if tracking_id:
should_update = (
processed_pairs % update_interval == 0 or
processed_pairs == total_pairs or
processed_pairs == 1 or
total_pairs <= 10 # Always update for small datasets
)
if should_update:
self.progress_tracker.update_progress(
tracking_id,
processed=processed_pairs,
total=total_pairs,
message=f"Building clusters from similarity pairs... {processed_pairs}/{total_pairs} (remaining: {remaining})"
)
return list(clusters_dict.values())
@@ -311,7 +372,10 @@ class ClusterBuilder:
merged = True
iteration = 0
total_iterations = len(entities) # Maximum iterations
update_interval = max(1, total_iterations // 20) # Update every 5%
if total_iterations <= 10:
update_interval = 1 # Update every iteration for small datasets
else:
update_interval = max(1, total_iterations // 20) # Update every 5%
while merged:
merged = False
@@ -320,7 +384,20 @@ class ClusterBuilder:
total_comparisons = len(clusters) * (len(clusters) - 1) // 2
processed_comparisons = 0
comparison_update_interval = max(1, total_comparisons // 20) if total_comparisons > 0 else 1
if total_comparisons <= 10:
comparison_update_interval = 1 # Update every comparison for small datasets
else:
comparison_update_interval = max(1, total_comparisons // 20) if total_comparisons > 0 else 1
# Initial progress update for comparisons
if tracking_id and total_comparisons > 0:
remaining_comparisons = total_comparisons
self.progress_tracker.update_progress(
tracking_id,
processed=0,
total=total_comparisons,
message=f"Comparing clusters... 0/{total_comparisons} (remaining: {remaining_comparisons})"
)
for i in range(len(clusters)):
for j in range(i + 1, len(clusters)):
@@ -332,13 +409,22 @@ class ClusterBuilder:
best_merge = (i, j)
processed_comparisons += 1
if tracking_id and (processed_comparisons % comparison_update_interval == 0 or processed_comparisons == total_comparisons):
self.progress_tracker.update_progress(
tracking_id,
processed=processed_comparisons,
total=total_comparisons,
message=f"Comparing clusters... {processed_comparisons}/{total_comparisons}"
remaining_comparisons = total_comparisons - processed_comparisons
# Update progress: always update for small datasets, or at intervals for large ones
if tracking_id:
should_update = (
processed_comparisons % comparison_update_interval == 0 or
processed_comparisons == total_comparisons or
processed_comparisons == 1 or
total_comparisons <= 10 # Always update for small datasets
)
if should_update:
self.progress_tracker.update_progress(
tracking_id,
processed=processed_comparisons,
total=total_comparisons,
message=f"Comparing clusters... {processed_comparisons}/{total_comparisons} (remaining: {remaining_comparisons})"
)
if best_merge:
i, j = best_merge
@@ -353,13 +439,22 @@ class ClusterBuilder:
merged = True
iteration += 1
if tracking_id and (iteration % update_interval == 0):
self.progress_tracker.update_progress(
tracking_id,
processed=iteration,
total=total_iterations,
message=f"Hierarchical clustering iteration {iteration}... {len(clusters)} clusters remaining"
remaining_iterations = total_iterations - iteration
# Update progress: always update for small datasets, or at intervals for large ones
if tracking_id:
should_update = (
iteration % update_interval == 0 or
iteration == total_iterations or
iteration == 1 or
total_iterations <= 10 # Always update for small datasets
)
if should_update:
self.progress_tracker.update_progress(
tracking_id,
processed=iteration,
total=total_iterations,
message=f"Hierarchical clustering iteration {iteration}/{total_iterations}... {len(clusters)} clusters remaining (remaining: {remaining_iterations} iterations)"
)
return clusters
+70 -22
View File
@@ -217,8 +217,20 @@ class DuplicateDetector:
# Create duplicate candidates from similar pairs
candidates = []
total_similarities = len(similarities)
# Update more frequently: every 1% or at least every 10 items
update_interval = max(1, min(10, total_similarities // 100))
# Update more frequently: every 1% or at least every 10 items, but always update for small datasets
if total_similarities <= 10:
update_interval = 1 # Update every item for small datasets
else:
update_interval = max(1, min(10, total_similarities // 100))
# Initial progress update - ALWAYS show this
remaining = total_similarities
self.progress_tracker.update_progress(
tracking_id,
processed=0,
total=total_similarities,
message=f"Creating duplicate candidates... 0/{total_similarities} (remaining: {remaining})"
)
for i, (entity1, entity2, score) in enumerate(similarities):
candidate = self._create_duplicate_candidate(entity1, entity2, score)
@@ -227,13 +239,20 @@ class DuplicateDetector:
if candidate.confidence >= self.confidence_threshold:
candidates.append(candidate)
# Update progress more frequently
if (i + 1) % update_interval == 0 or (i + 1) == total_similarities or i == 0:
remaining = total_similarities - (i + 1)
# Update progress: always update for small datasets, or at intervals for large ones
should_update = (
(i + 1) % update_interval == 0 or
(i + 1) == total_similarities or
i == 0 or
total_similarities <= 10 # Always update for small datasets
)
if should_update:
self.progress_tracker.update_progress(
tracking_id,
processed=i + 1,
total=total_similarities,
message=f"Creating duplicate candidates... {i + 1}/{total_similarities}"
message=f"Creating duplicate candidates... {i + 1}/{total_similarities} (remaining: {remaining})"
)
# Sort by confidence (highest first)
@@ -335,19 +354,38 @@ class DuplicateDetector:
# Calculate group metrics for each group
total_groups = len(groups)
# Update more frequently: every item if small, or every 1% if large
update_interval = max(1, min(5, total_groups // 100))
if total_groups <= 10:
update_interval = 1 # Update every item for small datasets
else:
update_interval = max(1, min(5, total_groups // 100))
# Initial progress update - ALWAYS show this
remaining = total_groups
self.progress_tracker.update_progress(
tracking_id,
processed=0,
total=total_groups,
message=f"Calculating group metrics... 0/{total_groups} (remaining: {remaining})"
)
for i, group in enumerate(groups):
group.confidence = self._calculate_group_confidence(group)
group.representative = self._select_representative(group)
# Update progress more frequently
if (i + 1) % update_interval == 0 or (i + 1) == total_groups or i == 0:
remaining = total_groups - (i + 1)
# Update progress: always update for small datasets, or at intervals for large ones
should_update = (
(i + 1) % update_interval == 0 or
(i + 1) == total_groups or
i == 0 or
total_groups <= 10 # Always update for small datasets
)
if should_update:
self.progress_tracker.update_progress(
tracking_id,
processed=i + 1,
total=total_groups,
message=f"Calculating group metrics... {i + 1}/{total_groups}"
message=f"Calculating group metrics... {i + 1}/{total_groups} (remaining: {remaining})"
)
self.logger.info(
@@ -445,17 +483,20 @@ class DuplicateDetector:
candidates = []
total_comparisons = len(new_entities) * len(existing_entities)
processed = 0
# Update more frequently: every 1% or at least every 50 items
update_interval = max(1, min(50, total_comparisons // 100))
# Update more frequently: every 1% or at least every 10 items, but always update for small datasets
if total_comparisons <= 10:
update_interval = 1 # Update every item for small datasets
else:
update_interval = max(1, min(10, total_comparisons // 100))
# Initial progress update
if total_comparisons > 0:
self.progress_tracker.update_progress(
tracking_id,
processed=0,
total=total_comparisons,
message=f"Starting incremental detection... 0/{total_comparisons}"
)
# Initial progress update - ALWAYS show this
remaining = total_comparisons
self.progress_tracker.update_progress(
tracking_id,
processed=0,
total=total_comparisons,
message=f"Starting incremental detection... 0/{total_comparisons} (remaining: {remaining})"
)
# Compare each new entity with all existing entities
for new_entity in new_entities:
@@ -476,13 +517,20 @@ class DuplicateDetector:
candidates.append(candidate)
processed += 1
# Update progress more frequently
if processed % update_interval == 0 or processed == total_comparisons or processed == 1:
remaining = total_comparisons - processed
# Update progress: always update for small datasets, or at intervals for large ones
should_update = (
processed % update_interval == 0 or
processed == total_comparisons or
processed == 1 or
total_comparisons <= 10 # Always update for small datasets
)
if should_update:
self.progress_tracker.update_progress(
tracking_id,
processed=processed,
total=total_comparisons,
message=f"Comparing entities... {processed}/{total_comparisons}"
message=f"Comparing entities... {processed}/{total_comparisons} (remaining: {remaining})"
)
# Sort by confidence (highest first)
+22 -12
View File
@@ -218,16 +218,19 @@ class EntityMerger:
mergeable_groups = [g for g in duplicate_groups if len(g.entities) >= 2]
total_groups = len(mergeable_groups)
# Update more frequently: every item if small, or every 1% if large
update_interval = max(1, min(5, total_groups // 100))
if total_groups <= 10:
update_interval = 1 # Update every item for small datasets
else:
update_interval = max(1, min(5, total_groups // 100))
# Initial progress update
if total_groups > 0:
self.progress_tracker.update_progress(
tracking_id,
processed=0,
total=total_groups,
message=f"Starting merge operations... 0/{total_groups}"
)
# Initial progress update - ALWAYS show this
remaining = total_groups
self.progress_tracker.update_progress(
tracking_id,
processed=0,
total=total_groups,
message=f"Starting merge operations... 0/{total_groups} (remaining: {remaining})"
)
# Merge each duplicate group
for i, group in enumerate(mergeable_groups):
@@ -262,13 +265,20 @@ class EntityMerger:
merge_operations.append(operation)
self.merge_history.append(operation)
# Update progress more frequently
if (i + 1) % update_interval == 0 or (i + 1) == total_groups or i == 0:
remaining = total_groups - (i + 1)
# Update progress: always update for small datasets, or at intervals for large ones
should_update = (
(i + 1) % update_interval == 0 or
(i + 1) == total_groups or
i == 0 or
total_groups <= 10 # Always update for small datasets
)
if should_update:
self.progress_tracker.update_progress(
tracking_id,
processed=i + 1,
total=total_groups,
message=f"Merging groups... {i + 1}/{total_groups}"
message=f"Merging groups... {i + 1}/{total_groups} (remaining: {remaining})"
)
self.logger.info(
+40 -12
View File
@@ -144,8 +144,10 @@ class MergeStrategyManager:
# Custom merge strategies (callable functions)
self.custom_strategies: Dict[str, Callable] = {}
# Initialize progress tracker
# Initialize progress tracker and ensure it's enabled
self.progress_tracker = get_progress_tracker()
if not self.progress_tracker.enabled:
self.progress_tracker.enabled = True
self.logger.debug(
f"Merge strategy manager initialized (default: {self.default_strategy.value})"
@@ -238,28 +240,54 @@ class MergeStrategyManager:
if not isinstance(strategy, MergeStrategy):
strategy = self.default_strategy
self.progress_tracker.update_tracking(
tracking_id, message=f"Selecting base entity using {strategy.value}..."
total_steps = 4 # Select base, merge properties, merge relationships, build entity
current_step = 0
# Step 1: Select base entity
current_step += 1
remaining_steps = total_steps - current_step
self.progress_tracker.update_progress(
tracking_id,
processed=current_step,
total=total_steps,
message=f"Selecting base entity using {strategy.value}... ({current_step}/{total_steps}, remaining: {remaining_steps} steps)"
)
# Select base entity
base_entity = self._select_base_entity(entities, strategy)
self.progress_tracker.update_tracking(
tracking_id, message="Merging properties..."
# Step 2: Merge properties
current_step += 1
remaining_steps = total_steps - current_step
total_properties = sum(len(e.get("properties", {})) for e in entities)
self.progress_tracker.update_progress(
tracking_id,
processed=current_step,
total=total_steps,
message=f"Merging properties... ({current_step}/{total_steps}, {total_properties} properties, remaining: {remaining_steps} steps)"
)
# Merge properties
merged_properties, property_conflicts = self._merge_properties(
entities, base_entity, strategy
)
self.progress_tracker.update_tracking(
tracking_id, message="Merging relationships..."
# Step 3: Merge relationships
current_step += 1
remaining_steps = total_steps - current_step
total_relationships = sum(len(e.get("relationships", [])) for e in entities)
self.progress_tracker.update_progress(
tracking_id,
processed=current_step,
total=total_steps,
message=f"Merging relationships... ({current_step}/{total_steps}, {total_relationships} relationships, remaining: {remaining_steps} steps)"
)
# Merge relationships
merged_relationships = self._merge_relationships(entities, base_entity)
self.progress_tracker.update_tracking(
tracking_id, message="Building merged entity..."
# Step 4: Build merged entity
current_step += 1
remaining_steps = total_steps - current_step
self.progress_tracker.update_progress(
tracking_id,
processed=current_step,
total=total_steps,
message=f"Building merged entity... ({current_step}/{total_steps}, remaining: {remaining_steps} steps)"
)
# Build merged entity
merged_entity = {
@@ -560,23 +560,27 @@ class SimilarityCalculator:
# Calculate total pairs: n*(n-1)/2
total_pairs = len(entities) * (len(entities) - 1) // 2
processed = 0
# Update more frequently: every 1% or at least every 50 items
update_interval = max(1, min(50, total_pairs // 100))
# Update more frequently: every 1% or at least every 10 items, but always update for small datasets
if total_pairs <= 10:
update_interval = 1 # Update every item for small datasets
else:
update_interval = max(1, min(10, total_pairs // 100))
# Initial progress update to show tracking started
# Initial progress update to show tracking started - ALWAYS show this
self.progress_tracker.update_tracking(
tracking_id,
status="running",
message=f"Calculating similarity for {len(entities)} entities ({total_pairs} pairs)..."
)
if total_pairs > 0:
self.progress_tracker.update_progress(
tracking_id,
processed=0,
total=total_pairs,
message=f"Starting similarity calculation... 0/{total_pairs}"
)
# Always show initial progress, even if total_pairs is 0
remaining = total_pairs - processed
self.progress_tracker.update_progress(
tracking_id,
processed=0,
total=total_pairs,
message=f"Starting similarity calculation... 0/{total_pairs} (remaining: {remaining})"
)
for i in range(len(entities)):
for j in range(i + 1, len(entities)):
@@ -586,13 +590,20 @@ class SimilarityCalculator:
results.append((entities[i], entities[j], similarity.score))
processed += 1
# Update progress more frequently
if processed % update_interval == 0 or processed == total_pairs or processed == 1:
remaining = total_pairs - processed
# Update progress: always update for small datasets, or at intervals for large ones
should_update = (
processed % update_interval == 0 or
processed == total_pairs or
processed == 1 or
total_pairs <= 10 # Always update for small datasets
)
if should_update:
self.progress_tracker.update_progress(
tracking_id,
processed=processed,
total=total_pairs,
message=f"Comparing entity pairs... {processed}/{total_pairs}"
message=f"Comparing entity pairs... {processed}/{total_pairs} (remaining: {remaining})"
)
self.progress_tracker.stop_tracking(
@@ -139,31 +139,54 @@ class CoreferenceResolver:
)
try:
# Extract mentions
self.progress_tracker.update_tracking(
tracking_id, message="Extracting mentions..."
total_steps = 4 # Extract mentions, resolve pronouns, detect coreferences, build chains
current_step = 0
# Step 1: Extract mentions
current_step += 1
remaining_steps = total_steps - current_step
self.progress_tracker.update_progress(
tracking_id,
processed=current_step,
total=total_steps,
message=f"Extracting mentions... ({current_step}/{total_steps}, remaining: {remaining_steps} steps)"
)
mentions = self._extract_mentions(text)
# Resolve pronouns
self.progress_tracker.update_tracking(
tracking_id, message="Resolving pronouns..."
# Step 2: Resolve pronouns
current_step += 1
remaining_steps = total_steps - current_step
self.progress_tracker.update_progress(
tracking_id,
processed=current_step,
total=total_steps,
message=f"Resolving pronouns... ({current_step}/{total_steps}, {len(mentions)} mentions, remaining: {remaining_steps} steps)"
)
pronoun_resolutions = self.pronoun_resolver.resolve_pronouns(
text, mentions, **options
)
# Detect entity coreferences
self.progress_tracker.update_tracking(
tracking_id, message="Detecting entity coreferences..."
# Step 3: Detect entity coreferences
current_step += 1
remaining_steps = total_steps - current_step
self.progress_tracker.update_progress(
tracking_id,
processed=current_step,
total=total_steps,
message=f"Detecting entity coreferences... ({current_step}/{total_steps}, remaining: {remaining_steps} steps)"
)
entity_corefs = self.entity_detector.detect_entity_coreferences(
text, mentions, **options
)
# Build chains
self.progress_tracker.update_tracking(
tracking_id, message="Building coreference chains..."
# Step 4: Build chains
current_step += 1
remaining_steps = total_steps - current_step
self.progress_tracker.update_progress(
tracking_id,
processed=current_step,
total=total_steps,
message=f"Building coreference chains... ({current_step}/{total_steps}, remaining: {remaining_steps} steps)"
)
chains = self.chain_builder.build_coreference_chains(mentions, **options)
@@ -207,8 +230,18 @@ class CoreferenceResolver:
"their": r"\btheir\b",
}
for pronoun, pattern in pronoun_patterns.items():
for match in re.finditer(pattern, text, re.IGNORECASE):
total_patterns = len(pronoun_patterns)
if total_patterns <= 10:
pattern_update_interval = 1 # Update every pattern for small datasets
else:
pattern_update_interval = max(1, min(5, total_patterns // 20))
for pattern_idx, (pronoun, pattern) in enumerate(pronoun_patterns.items(), 1):
# Count matches first
matches = list(re.finditer(pattern, text, re.IGNORECASE))
total_matches = len(matches)
for match in matches:
mentions.append(
Mention(
text=match.group(0),
+71 -3
View File
@@ -176,11 +176,46 @@ class EventDetector:
}
# Detect events using patterns
self.progress_tracker.update_tracking(
tracking_id, message="Scanning text for event patterns..."
total_event_types = len(event_patterns_to_use)
if total_event_types <= 10:
event_type_update_interval = 1 # Update every type for small datasets
else:
event_type_update_interval = max(1, min(5, total_event_types // 20))
# Initial progress update
remaining_types = total_event_types
self.progress_tracker.update_progress(
tracking_id,
processed=0,
total=total_event_types,
message=f"Scanning text for event patterns... 0/{total_event_types} event types (remaining: {remaining_types})"
)
event_type_idx = 0
for event_type, pattern in event_patterns_to_use.items():
for match in re.finditer(pattern, text, re.IGNORECASE):
event_type_idx += 1
remaining_types = total_event_types - event_type_idx
# Count matches first to show progress
matches = list(re.finditer(pattern, text, re.IGNORECASE))
total_matches = len(matches)
# Initialize match update interval
if total_matches <= 10:
match_update_interval = 1
else:
match_update_interval = max(1, min(10, total_matches // 100))
if tracking_id and total_matches > 0:
remaining_matches = total_matches
self.progress_tracker.update_progress(
tracking_id,
processed=0,
total=total_matches,
message=f"Processing {event_type} events... 0/{total_matches} matches (remaining: {remaining_matches})"
)
for match_idx, match in enumerate(matches, 1):
# Extract surrounding context
start = max(0, match.start() - 50)
end = min(len(text), match.end() + 50)
@@ -213,6 +248,39 @@ class EventDetector:
metadata={"context": context},
)
events.append(event)
# Update progress for matches
if tracking_id and total_matches > 0:
remaining_matches = total_matches - match_idx
should_update = (
match_idx % match_update_interval == 0 or
match_idx == total_matches or
match_idx == 1 or
total_matches <= 10 # Always update for small datasets
)
if should_update:
self.progress_tracker.update_progress(
tracking_id,
processed=match_idx,
total=total_matches,
message=f"Processing {event_type} events... {match_idx}/{total_matches} matches (remaining: {remaining_matches})"
)
# Update progress for event types
if tracking_id:
should_update = (
event_type_idx % event_type_update_interval == 0 or
event_type_idx == total_event_types or
event_type_idx == 1 or
total_event_types <= 10 # Always update for small datasets
)
if should_update:
self.progress_tracker.update_progress(
tracking_id,
processed=event_type_idx,
total=total_event_types,
message=f"Scanning text for event patterns... {event_type_idx}/{total_event_types} event types (remaining: {remaining_types})"
)
self.progress_tracker.stop_tracking(
tracking_id,
+18 -7
View File
@@ -168,15 +168,19 @@ class NERExtractor:
try:
results = []
total_items = len(text)
# Update more frequently: every 1% or at least every 10 items
update_interval = max(1, min(10, total_items // 100))
# Update more frequently: every 1% or at least every 10 items, but always update for small datasets
if total_items <= 10:
update_interval = 1 # Update every item for small datasets
else:
update_interval = max(1, min(10, total_items // 100))
# Initial progress update
# Initial progress update - ALWAYS show this
remaining = total_items
self.progress_tracker.update_progress(
tracking_id,
processed=0,
total=total_items,
message=f"Starting batch extraction... 0/{total_items}"
message=f"Starting batch extraction... 0/{total_items} (remaining: {remaining})"
)
for idx, item in enumerate(text, 1):
@@ -194,13 +198,20 @@ class NERExtractor:
except Exception:
results.append([])
# Update progress more frequently
if idx % update_interval == 0 or idx == total_items or idx == 1:
remaining = total_items - idx
# Update progress: always update for small datasets, or at intervals for large ones
should_update = (
idx % update_interval == 0 or
idx == total_items or
idx == 1 or
total_items <= 10 # Always update for small datasets
)
if should_update:
self.progress_tracker.update_progress(
tracking_id,
processed=idx,
total=total_items,
message=f"Processing documents... {idx}/{total_items}"
message=f"Processing documents... {idx}/{total_items} (remaining: {remaining})"
)
self.progress_tracker.stop_tracking(
@@ -194,15 +194,19 @@ class RelationExtractor:
results = []
# Ensure lists are same length
min_len = min(len(text), len(entities))
# Update more frequently: every 1% or at least every 10 items
update_interval = max(1, min(10, min_len // 100))
# Update more frequently: every 1% or at least every 10 items, but always update for small datasets
if min_len <= 10:
update_interval = 1 # Update every item for small datasets
else:
update_interval = max(1, min(10, min_len // 100))
# Initial progress update
# Initial progress update - ALWAYS show this
remaining = min_len
self.progress_tracker.update_progress(
tracking_id,
processed=0,
total=min_len,
message=f"Starting batch extraction... 0/{min_len}"
message=f"Starting batch extraction... 0/{min_len} (remaining: {remaining})"
)
for i in range(min_len):
@@ -223,13 +227,20 @@ class RelationExtractor:
results.append(self.extract_relations(doc_text, ent_item, **kwargs))
# Update progress more frequently
if (i + 1) % update_interval == 0 or (i + 1) == min_len or i == 0:
remaining = min_len - (i + 1)
# Update progress: always update for small datasets, or at intervals for large ones
should_update = (
(i + 1) % update_interval == 0 or
(i + 1) == min_len or
i == 0 or
min_len <= 10 # Always update for small datasets
)
if should_update:
self.progress_tracker.update_progress(
tracking_id,
processed=i + 1,
total=min_len,
message=f"Processing documents... {i + 1}/{min_len}"
message=f"Processing documents... {i + 1}/{min_len} (remaining: {remaining})"
)
self.progress_tracker.stop_tracking(
+81 -26
View File
@@ -367,6 +367,10 @@ class SemanticClusterer:
"""Initialize semantic clusterer."""
self.logger = get_logger("semantic_clusterer")
self.config = config
# Initialize progress tracker and ensure it's enabled
self.progress_tracker = get_progress_tracker()
if not self.progress_tracker.enabled:
self.progress_tracker.enabled = True
def cluster(self, texts: List[str], **options) -> List[SemanticCluster]:
"""
@@ -384,38 +388,89 @@ class SemanticClusterer:
if not texts:
return []
similarity_threshold = options.get("similarity_threshold", 0.5)
similarity_analyzer = SimilarityAnalyzer()
# Track clustering
tracking_id = self.progress_tracker.start_tracking(
module="semantic_extract",
submodule="SemanticClusterer",
message=f"Clustering {len(texts)} texts",
)
clusters = []
assigned = set()
try:
similarity_threshold = options.get("similarity_threshold", 0.5)
similarity_analyzer = SimilarityAnalyzer()
cluster_id = 0
for i, text1 in enumerate(texts):
if i in assigned:
continue
clusters = []
assigned = set()
cluster_texts = [text1]
assigned.add(i)
total_texts = len(texts)
if total_texts <= 10:
update_interval = 1 # Update every item for small datasets
else:
update_interval = max(1, min(10, total_texts // 100))
# Initial progress update
remaining = total_texts
self.progress_tracker.update_progress(
tracking_id,
processed=0,
total=total_texts,
message=f"Clustering texts... 0/{total_texts} (remaining: {remaining})"
)
# Find similar texts
for j, text2 in enumerate(texts[i + 1 :], start=i + 1):
if j in assigned:
cluster_id = 0
for i, text1 in enumerate(texts):
if i in assigned:
continue
similarity = similarity_analyzer.calculate_similarity(text1, text2)
if similarity >= similarity_threshold:
cluster_texts.append(text2)
assigned.add(j)
cluster_texts = [text1]
assigned.add(i)
# Create cluster
cluster = SemanticCluster(
texts=cluster_texts,
cluster_id=cluster_id,
centroid=cluster_texts[0], # Use first as centroid
similarity_score=similarity_threshold,
# Find similar texts
remaining_texts = len(texts) - (i + 1)
for j, text2 in enumerate(texts[i + 1 :], start=i + 1):
if j in assigned:
continue
similarity = similarity_analyzer.calculate_similarity(text1, text2)
if similarity >= similarity_threshold:
cluster_texts.append(text2)
assigned.add(j)
# Create cluster
cluster = SemanticCluster(
texts=cluster_texts,
cluster_id=cluster_id,
centroid=cluster_texts[0], # Use first as centroid
similarity_score=similarity_threshold,
)
clusters.append(cluster)
cluster_id += 1
remaining = total_texts - (i + 1)
# Update progress: always update for small datasets, or at intervals for large ones
should_update = (
(i + 1) % update_interval == 0 or
(i + 1) == total_texts or
i == 0 or
total_texts <= 10 # Always update for small datasets
)
if should_update:
self.progress_tracker.update_progress(
tracking_id,
processed=i + 1,
total=total_texts,
message=f"Clustering texts... {i + 1}/{total_texts} (remaining: {remaining})"
)
self.progress_tracker.stop_tracking(
tracking_id,
status="completed",
message=f"Created {len(clusters)} clusters",
)
clusters.append(cluster)
cluster_id += 1
return clusters
return clusters
except Exception as e:
self.progress_tracker.stop_tracking(
tracking_id, status="failed", message=str(e)
)
raise
@@ -217,10 +217,18 @@ class SemanticNetworkExtractor:
relations = rel_extractor.extract_relations(text, entities, **options)
# Build network
self.progress_tracker.update_tracking(
tracking_id, message="Building semantic network..."
total_steps = 2 # Create nodes, create edges
current_step = 0
current_step += 1
remaining_steps = total_steps - current_step
self.progress_tracker.update_progress(
tracking_id,
processed=current_step,
total=total_steps,
message=f"Building semantic network... Creating nodes from {len(entities)} entities ({current_step}/{total_steps}, remaining: {remaining_steps} steps)"
)
network = self._build_network(entities, relations)
network = self._build_network(entities, relations, tracking_id, total_steps, current_step)
self.progress_tracker.stop_tracking(
tracking_id,
@@ -236,7 +244,7 @@ class SemanticNetworkExtractor:
raise
def _build_network(
self, entities: List[Entity], relations: List[Relation]
self, entities: List[Entity], relations: List[Relation], tracking_id: str = None, total_steps: int = 2, current_step: int = 1
) -> SemanticNetwork:
"""Build semantic network from entities and relations."""
nodes = []
@@ -244,7 +252,23 @@ class SemanticNetworkExtractor:
node_map = {}
# Create nodes from entities
for entity in entities:
total_entities = len(entities)
if total_entities <= 10:
entity_update_interval = 1 # Update every item for small datasets
else:
entity_update_interval = max(1, min(10, total_entities // 100))
# Initial progress update for entities
if tracking_id and total_entities > 0:
remaining_entities = total_entities
self.progress_tracker.update_progress(
tracking_id,
processed=0,
total=total_entities,
message=f"Creating nodes from entities... 0/{total_entities} (remaining: {remaining_entities})"
)
for i, entity in enumerate(entities):
node_id = f"entity_{len(nodes)}"
node_map[entity.text] = node_id
@@ -260,9 +284,42 @@ class SemanticNetworkExtractor:
metadata=entity.metadata,
)
nodes.append(node)
remaining_entities = total_entities - (i + 1)
# Update progress: always update for small datasets, or at intervals for large ones
if tracking_id:
should_update = (
(i + 1) % entity_update_interval == 0 or
(i + 1) == total_entities or
i == 0 or
total_entities <= 10 # Always update for small datasets
)
if should_update:
self.progress_tracker.update_progress(
tracking_id,
processed=i + 1,
total=total_entities,
message=f"Creating nodes from entities... {i + 1}/{total_entities} (remaining: {remaining_entities})"
)
# Create edges from relations
for relation in relations:
total_relations = len(relations)
if total_relations <= 10:
relation_update_interval = 1 # Update every item for small datasets
else:
relation_update_interval = max(1, min(10, total_relations // 100))
if tracking_id and total_relations > 0:
# Initial progress update for relations
remaining_relations = total_relations
self.progress_tracker.update_progress(
tracking_id,
processed=0,
total=total_relations,
message=f"Creating edges from relations... 0/{total_relations} (remaining: {remaining_relations})"
)
for j, relation in enumerate(relations):
subject_id = node_map.get(relation.subject.text)
object_id = node_map.get(relation.object.text)
@@ -278,6 +335,23 @@ class SemanticNetworkExtractor:
metadata=relation.metadata,
)
edges.append(edge)
remaining_relations = len(relations) - (j + 1)
# Update progress: always update for small datasets, or at intervals for large ones
if tracking_id:
should_update = (
(j + 1) % relation_update_interval == 0 or
(j + 1) == len(relations) or
j == 0 or
len(relations) <= 10 # Always update for small datasets
)
if should_update:
self.progress_tracker.update_progress(
tracking_id,
processed=j + 1,
total=len(relations),
message=f"Creating edges from relations... {j + 1}/{len(relations)} (remaining: {remaining_relations})"
)
return SemanticNetwork(
nodes=nodes,
@@ -206,11 +206,29 @@ class TripletExtractor:
# Try each method in order (fallback chain)
all_triplets = []
for method_name in methods:
total_methods = len(methods)
if total_methods <= 10:
method_update_interval = 1 # Update every method for small datasets
else:
method_update_interval = max(1, min(5, total_methods // 20))
# Initial progress update for methods
remaining_methods = total_methods
self.progress_tracker.update_progress(
tracking_id,
processed=0,
total=total_methods,
message=f"Starting triplet extraction... 0/{total_methods} methods (remaining: {remaining_methods})"
)
for method_idx, method_name in enumerate(methods, 1):
try:
self.progress_tracker.update_tracking(
remaining_methods = total_methods - method_idx
self.progress_tracker.update_progress(
tracking_id,
message=f"Extracting triplets using {method_name}...",
processed=method_idx,
total=total_methods,
message=f"Extracting triplets using {method_name}... ({method_idx}/{total_methods}, remaining: {remaining_methods} methods)"
)
method_func = get_triplet_method(method_name)
+147 -15
View File
@@ -116,7 +116,8 @@ class ConsoleProgressDisplay(ProgressDisplay):
def _should_update(self) -> bool:
"""Check if enough time has passed for update."""
now = time.time()
if now - self.last_update >= self.update_interval:
# If last_update is 0.0 or very old, always update (forced update)
if self.last_update <= 0.0 or (now - self.last_update) >= self.update_interval:
self.last_update = now
return True
return False
@@ -546,7 +547,7 @@ class JupyterProgressDisplay(ProgressDisplay):
return "".join(html_parts)
def update(self, item: ProgressItem) -> None:
"""Update Jupyter progress display."""
"""Update Jupyter progress display (works in Jupyter and Google Colab)."""
# Add or update item
existing = None
for i, existing_item in enumerate(self.items):
@@ -563,13 +564,51 @@ class JupyterProgressDisplay(ProgressDisplay):
else:
self.items.append(item)
# Update display
# Update display - always update immediately in Jupyter/Colab
if IPYTHON_AVAILABLE:
html = self._build_html(self.items)
if self.display_handle is None:
try:
# Check if we're in Google Colab (Colab sometimes needs fresh displays)
is_colab = False
try:
import sys
import os
is_colab = ('google.colab' in sys.modules or
os.environ.get("COLAB_GPU") is not None)
except Exception:
pass
if self.display_handle is None:
# First time - create display
self.display_handle = display(HTML(html), display_id=True)
else:
# Try to update existing display
try:
# In Colab, sometimes update() doesn't work, so we recreate
if is_colab:
# Clear and recreate for Colab compatibility
try:
clear_output(wait=False)
except Exception:
pass
self.display_handle = display(HTML(html), display_id=True)
else:
# Regular Jupyter - try update first
self.display_handle.update(HTML(html))
except (AttributeError, TypeError, Exception):
# If update fails, create new display (works in both Jupyter and Colab)
try:
clear_output(wait=False)
except Exception:
pass
self.display_handle = display(HTML(html), display_id=True)
except Exception:
# Fallback: always create new display if update fails
try:
clear_output(wait=False)
except Exception:
pass
self.display_handle = display(HTML(html), display_id=True)
else:
self.display_handle.update(HTML(html))
def show_summary(self, items: List[ProgressItem]) -> None:
"""Show final summary in Jupyter."""
@@ -823,22 +862,33 @@ class ProgressTracker:
Initialize progress tracker.
Args:
enabled: Enable progress tracking
enabled: Enable progress tracking (default: True, always enabled)
use_emoji: Use emoji indicators
update_interval: Minimum time between updates (seconds)
"""
self.enabled = enabled
# Always enable progress tracking by default - cannot be disabled via constructor
# This ensures progress is always shown automatically
self.enabled = True # Force enabled, ignore parameter
self.use_emoji = use_emoji
self.update_interval = update_interval
# Detect environment
# Detect environment - will be checked dynamically
self.is_jupyter = self._detect_jupyter()
# Create displays
self.displays: List[ProgressDisplay] = []
if self.is_jupyter and IPYTHON_AVAILABLE:
self.displays.append(JupyterProgressDisplay(use_emoji=use_emoji))
# Always try Jupyter first if available, fallback to console
if IPYTHON_AVAILABLE:
# Try to detect Jupyter - if available, use it
if self.is_jupyter:
self.displays.append(JupyterProgressDisplay(use_emoji=use_emoji))
# Also add console as fallback for immediate feedback
self.displays.append(
ConsoleProgressDisplay(
use_emoji=use_emoji, update_interval=update_interval
)
)
else:
self.displays.append(
ConsoleProgressDisplay(
@@ -855,12 +905,48 @@ class ProgressTracker:
self.lock = threading.Lock()
def _detect_jupyter(self) -> bool:
"""Detect if running in Jupyter notebook."""
"""Detect if running in Jupyter notebook or Google Colab."""
if not IPYTHON_AVAILABLE:
return False
try:
ipython = get_ipython()
return ipython is not None and hasattr(ipython, "kernel")
if ipython is None:
return False
# Method 1: Check for Google Colab
# Colab has 'google.colab' in sys.modules or environment variables
try:
import sys
import os
if 'google.colab' in sys.modules:
return True
# Check environment variables (Colab sets these)
if os.environ.get("COLAB_GPU") is not None or os.environ.get("COLAB_JUPYTER_TRANSPORT") is not None:
return True
# Check IPython config for Colab
if hasattr(ipython, 'config') and hasattr(ipython.config, 'IPKernelApp'):
config_str = str(ipython.config.IPKernelApp)
if 'google.colab' in config_str or 'colab' in config_str.lower():
return True
except Exception:
pass
# Method 2: Check for kernel attribute (Jupyter/Colab)
if hasattr(ipython, "kernel"):
return True
# Method 3: Check for IPython shell class name
if hasattr(ipython, "__class__"):
class_name = ipython.__class__.__name__
# Check for Jupyter, Colab, or ZMQ shell types
if any(name in class_name for name in ["ZMQInteractiveShell", "Jupyter", "Colab", "InteractiveShell"]):
return True
# Method 4: Check for IPython display capability
if hasattr(ipython, "display_pub"):
return True
return False
except Exception:
return False
@@ -871,6 +957,11 @@ class ProgressTracker:
with cls._lock:
if cls._instance is None:
cls._instance = cls()
# Ensure it's always enabled
cls._instance.enabled = True
else:
# Always ensure enabled when getting instance
cls._instance.enabled = True
return cls._instance
def start_tracking(
@@ -895,6 +986,15 @@ class ProgressTracker:
if not self.enabled:
return ""
# Re-detect Jupyter environment in case it wasn't detected at init
# This helps if the tracker was created before Jupyter was fully initialized
if IPYTHON_AVAILABLE and not self.is_jupyter:
self.is_jupyter = self._detect_jupyter()
# If Jupyter is now detected and we don't have a Jupyter display, add it
if self.is_jupyter and not any(isinstance(d, JupyterProgressDisplay) for d in self.displays):
# Insert Jupyter display at the beginning for priority
self.displays.insert(0, JupyterProgressDisplay(use_emoji=self.use_emoji))
# Auto-detect if not provided
if not module or not submodule:
detected_module, detected_submodule = ModuleDetector.detect_from_call_stack(
@@ -986,6 +1086,14 @@ class ProgressTracker:
if not self.enabled or not tracking_id:
return
# Re-detect Jupyter environment in case it wasn't detected at init
if IPYTHON_AVAILABLE and not self.is_jupyter:
self.is_jupyter = self._detect_jupyter()
# If Jupyter is now detected and we don't have a Jupyter display, add it
if self.is_jupyter and not any(isinstance(d, JupyterProgressDisplay) for d in self.displays):
# Insert Jupyter display at the beginning for priority
self.displays.insert(0, JupyterProgressDisplay(use_emoji=self.use_emoji))
with self.lock:
if tracking_id in self.active_items:
item = self.active_items[tracking_id]
@@ -998,9 +1106,21 @@ class ProgressTracker:
if message:
item.message = message
# Update displays
# Update displays - force immediate update for progress
for display in self.displays:
display.update(item)
# For Jupyter, always update immediately
if isinstance(display, JupyterProgressDisplay):
display.update(item)
# For console, force update by temporarily bypassing interval check
elif isinstance(display, ConsoleProgressDisplay):
# Force update by setting last_update far in the past
original_last_update = display.last_update
display.last_update = 0.0 # This will make _should_update return True
display.update(item)
# Restore original value (update() will set it to current time anyway)
display.last_update = original_last_update
else:
display.update(item)
def _calculate_eta(self, item: ProgressItem) -> Optional[float]:
"""
@@ -1132,6 +1252,18 @@ def get_progress_tracker() -> ProgressTracker:
global _global_tracker
if _global_tracker is None:
_global_tracker = ProgressTracker.get_instance()
# Always ensure progress tracker is enabled automatically
_global_tracker.enabled = True
# Re-detect Jupyter environment dynamically (in case it wasn't ready at init)
if IPYTHON_AVAILABLE and not _global_tracker.is_jupyter:
_global_tracker.is_jupyter = _global_tracker._detect_jupyter()
# If Jupyter is now detected and we don't have a Jupyter display, add it
if _global_tracker.is_jupyter and not any(isinstance(d, JupyterProgressDisplay) for d in _global_tracker.displays):
# Insert Jupyter display at the beginning for priority
_global_tracker.displays.insert(0, JupyterProgressDisplay(use_emoji=_global_tracker.use_emoji))
return _global_tracker