fix(cluster_detail): always persist per-row cluster labels to EntityProfile for data row display
This commit is contained in:
@@ -359,6 +359,36 @@ def _run_clustering_pipeline(run, store, ds_id, feature_columns, algorithm,
|
||||
except Exception as svd_err:
|
||||
logger.warning(f'Cluster SVD extraction skipped: {svd_err}')
|
||||
|
||||
# ── Step 2c: Save per-row cluster labels to EntityProfile ──
|
||||
# Always persist row→cluster mapping so cluster_detail can show data rows
|
||||
# even when UMAP is skipped or fails.
|
||||
try:
|
||||
cluster_entry_save = store.get_cluster_result(cluster_id)
|
||||
labels_list_save = cluster_entry_save.get('labels', []) if cluster_entry_save else []
|
||||
if labels_list_save:
|
||||
from analysis.models import EntityProfile as EP_save
|
||||
from analysis.models import ClusterResult as CR_save
|
||||
# Delete stale EP records for this run first
|
||||
EP_save.objects.filter(run=run).delete()
|
||||
cr_cache_save = {}
|
||||
row_profiles = []
|
||||
for r_idx in range(len(labels_list_save)):
|
||||
lbl = labels_list_save[r_idx]
|
||||
ck = f'{run.id}_{lbl}'
|
||||
if ck not in cr_cache_save:
|
||||
cr_cache_save[ck] = CR_save.objects.filter(run=run, cluster_label=lbl).first()
|
||||
row_profiles.append(EP_save(
|
||||
entity_value=f'row_{r_idx}', run=run, cluster_label=lbl,
|
||||
cluster=cr_cache_save[ck],
|
||||
embedding_x=None, embedding_y=None, embedding_z=None,
|
||||
))
|
||||
if row_profiles:
|
||||
EP_save.objects.bulk_create(row_profiles, ignore_conflicts=True, batch_size=1000)
|
||||
run.entity_count = len(row_profiles)
|
||||
run.save(update_fields=['entity_count'])
|
||||
except Exception as save_err:
|
||||
logger.warning(f'Row labels save skipped: {save_err}')
|
||||
|
||||
# ── Step 3: UMAP-3D embedding (2D fallback) ───────────────────────
|
||||
if run_umap:
|
||||
try:
|
||||
@@ -438,41 +468,23 @@ def _run_clustering_pipeline(run, store, ds_id, feature_columns, algorithm,
|
||||
labels_list = cluster_entry.get('labels', []) if cluster_entry else []
|
||||
|
||||
from analysis.models import EntityProfile as EP
|
||||
from analysis.models import ClusterResult as CR
|
||||
|
||||
profiles = []
|
||||
cr_cache = {}
|
||||
# Update existing EP records with UMAP coordinates
|
||||
for i in range(len(df_umap)):
|
||||
# Use row index as identifier — no entity column
|
||||
ev = f'row_{i}'
|
||||
lbl = labels_list[i] if i < len(labels_list) else -1
|
||||
cache_key = f'{run.id}_{lbl}'
|
||||
if cache_key not in cr_cache:
|
||||
cr_cache[cache_key] = CR.objects.filter(
|
||||
run=run, cluster_label=lbl).first()
|
||||
has_z = umap_components >= 3 and coords.shape[1] >= 3
|
||||
profiles.append(EP(
|
||||
entity_value=ev, run=run, cluster_label=lbl,
|
||||
cluster=cr_cache[cache_key],
|
||||
embedding_x=float(coords[i, 0]) if i < len(coords) else None,
|
||||
embedding_y=float(coords[i, 1]) if i < len(coords) else None,
|
||||
embedding_z=float(coords[i, 2]) if has_z
|
||||
and i < len(coords) else None,
|
||||
))
|
||||
if profiles:
|
||||
EP.objects.bulk_create(profiles, ignore_conflicts=True, batch_size=1000)
|
||||
run.entity_count = len(profiles)
|
||||
run.save(update_fields=['entity_count'])
|
||||
update_fields = {
|
||||
'embedding_x': float(coords[i, 0]) if i < len(coords) else None,
|
||||
'embedding_y': float(coords[i, 1]) if i < len(coords) else None,
|
||||
}
|
||||
if has_z:
|
||||
update_fields['embedding_z'] = float(coords[i, 2]) if i < len(coords) else None
|
||||
EP.objects.filter(run=run, entity_value=ev).update(**update_fields)
|
||||
run.entity_count = len(df_umap)
|
||||
run.save(update_fields=['entity_count'])
|
||||
except Exception as umap_err:
|
||||
logger.warning(f'UMAP embedding skipped: {umap_err}')
|
||||
|
||||
# Fallback: if entity_count not set (UMAP skipped), derive from cluster sizes
|
||||
if run.entity_count is None:
|
||||
from django.db.models import Sum
|
||||
total = run.clusters.aggregate(total=Sum('size'))['total'] or 0
|
||||
run.entity_count = total
|
||||
run.save(update_fields=['entity_count'])
|
||||
|
||||
# ── Done ──
|
||||
run.status = 'completed'
|
||||
run.progress_pct = 100
|
||||
|
||||
Reference in New Issue
Block a user