v1.1.3: row-level clustering + dataset selector with status filter
- Add cluster_mode toggle (raw row-level vs entity-aggregated) in manual.html - manual_run_analysis supports cluster_mode='raw' skipping entity aggregation - Replace simple dataset dropdown with selector showing status badges and filter tabs - Filter tabs: 全部/待分析/已分析/错误, default 待分析 - E2E verified: raw mode entity_count=total_flows, entity mode regression OK
This commit is contained in:
+61
-1
@@ -593,6 +593,46 @@ def manual_page(request):
|
||||
store = SessionStore()
|
||||
datasets = store.list_datasets() if hasattr(store, 'list_datasets') else []
|
||||
|
||||
# Get upload AnalysisRun statuses for dataset badges
|
||||
from analysis.models import AnalysisRun
|
||||
upload_runs = AnalysisRun.objects.filter(run_type='upload').order_by('-created_at')[:50]
|
||||
run_status_map = {
|
||||
f'upload_{r.display_id}': {
|
||||
'status': r.status,
|
||||
'total_flows': r.total_flows,
|
||||
'display_id': r.display_id,
|
||||
}
|
||||
for r in upload_runs
|
||||
}
|
||||
|
||||
# Build datasets_with_status: each dataset annotated with run status
|
||||
datasets_with_status = []
|
||||
for ds in datasets:
|
||||
run_info = run_status_map.get(ds['dataset_id'])
|
||||
if run_info:
|
||||
datasets_with_status.append({
|
||||
**ds,
|
||||
'run_status': run_info['status'],
|
||||
'total_flows': run_info['total_flows'],
|
||||
'display_id': run_info['display_id'],
|
||||
})
|
||||
else:
|
||||
datasets_with_status.append({**ds, 'run_status': 'ready', 'total_flows': None, 'display_id': None})
|
||||
|
||||
# Map statuses to user-facing labels for the filter tabs
|
||||
# ready → 待分析, completed → 已分析, failed → 错误
|
||||
# Everything else (pending/loading/profiling/aggregating/clustering/extracting) → analyzing → 分析中
|
||||
for ds_info in datasets_with_status:
|
||||
s = ds_info['run_status']
|
||||
if s in ('completed',):
|
||||
ds_info['status_label'] = '已分析'
|
||||
elif s in ('failed',):
|
||||
ds_info['status_label'] = '错误'
|
||||
elif s in ('pending', 'loading', 'profiling', 'aggregating', 'clustering', 'extracting'):
|
||||
ds_info['status_label'] = '分析中'
|
||||
else:
|
||||
ds_info['status_label'] = '待分析'
|
||||
|
||||
return render(request, 'tianxuan/manual.html', {
|
||||
'core_tools': core_tools,
|
||||
'diag_tools': diag_tools,
|
||||
@@ -600,6 +640,7 @@ def manual_page(request):
|
||||
'other_tools': other_tools,
|
||||
'tools_meta': _json.dumps(meta_list, ensure_ascii=False),
|
||||
'datasets': datasets,
|
||||
'datasets_with_status_json': _json.dumps(datasets_with_status, ensure_ascii=False),
|
||||
})
|
||||
|
||||
|
||||
@@ -858,6 +899,7 @@ def manual_run_analysis(request):
|
||||
algorithm = body.get('algorithm', 'hdbscan')
|
||||
min_cluster_size = int(body.get('min_cluster_size', 5))
|
||||
head = body.get('head')
|
||||
cluster_mode = body.get('cluster_mode', 'entity')
|
||||
|
||||
def _analysis_fn(run, ctx):
|
||||
from analysis.session_store import SessionStore
|
||||
@@ -867,6 +909,7 @@ def manual_run_analysis(request):
|
||||
min_cluster_size = ctx['min_cluster_size']
|
||||
head = ctx.get('head')
|
||||
pk = ctx['pk']
|
||||
cluster_mode = ctx.get('cluster_mode', 'entity')
|
||||
|
||||
store = SessionStore()
|
||||
try:
|
||||
@@ -888,6 +931,23 @@ def manual_run_analysis(request):
|
||||
|
||||
ds_id = upload_ds_id if store.get_dataset(upload_ds_id) else f'upload_{pk}'
|
||||
|
||||
# ── Cluster mode: entity aggregation vs raw ──
|
||||
if cluster_mode == 'entity':
|
||||
# Build entity profiles first, then cluster on entity dataset
|
||||
from analysis.tool_registry import _handle_build_entity_profiles
|
||||
import asyncio
|
||||
entity_result = asyncio.new_event_loop().run_until_complete(
|
||||
_handle_build_entity_profiles(dataset_id=ds_id, auto_detect=True)
|
||||
)
|
||||
if 'error' in entity_result:
|
||||
run.error_message = f'实体聚合失败: {entity_result["error"]}'
|
||||
run.status = 'failed'
|
||||
run.save(update_fields=['status', 'error_message'])
|
||||
return
|
||||
ds_id = entity_result['dataset_id']
|
||||
run.entity_count = entity_result.get('n_entities')
|
||||
run.save(update_fields=['entity_count'])
|
||||
|
||||
# Use the unified clustering pipeline (clustering → extraction → PCA)
|
||||
from analysis.views import _run_clustering_pipeline
|
||||
_run_clustering_pipeline(
|
||||
@@ -910,7 +970,7 @@ def manual_run_analysis(request):
|
||||
args=(pk, _analysis_fn),
|
||||
kwargs=dict(feature_columns=feature_columns, algorithm=algorithm,
|
||||
min_cluster_size=min_cluster_size, head=head, pk=pk,
|
||||
display_id=run.display_id),
|
||||
display_id=run.display_id, cluster_mode=cluster_mode),
|
||||
daemon=True,
|
||||
)
|
||||
t.start()
|
||||
|
||||
Reference in New Issue
Block a user