Implement workspace history reconciliation

This commit is contained in:
2026-03-03 17:35:45 +00:00
parent 2898d9e832
commit 18351abb00
14 changed files with 556 additions and 57 deletions

View File

@@ -2753,7 +2753,11 @@ def _panel_context(
),
"compose_answer_suggestion_send_url": reverse("compose_answer_suggestion_send"),
"compose_ws_url": ws_url,
"tasks_hub_url": reverse("tasks_hub"),
"tasks_hub_url": (
f"{reverse('tasks_hub')}?{urlencode({'person': str(base['person'].id), 'service': base['service'], 'identifier': base['identifier'] or ''})}"
if base["person"]
else reverse("tasks_hub")
),
"tasks_group_url": reverse(
"tasks_group",
kwargs={

View File

@@ -39,6 +39,7 @@ from core.models import (
WorkspaceConversation,
WorkspaceMetricSnapshot,
)
from core.workspace import DENSITY_POINT_CAPS, downsample_points
SEND_ENABLED_MODES = {"active", "instant"}
OPERATION_LABELS = {
@@ -960,21 +961,28 @@ def _metric_psychological_read(metric_slug, conversation):
return ""
def _history_points(conversation, field_name):
def _history_points(conversation, field_name, density="medium"):
rows = (
conversation.metric_snapshots.exclude(**{f"{field_name}__isnull": True})
.order_by("computed_at")
.values("computed_at", field_name)
.values("computed_at", "source_event_ts", field_name)
)
points = []
raw_points = []
for row in rows:
points.append(
source_ts = int(row.get("source_event_ts") or 0)
if source_ts > 0:
x_value = datetime.fromtimestamp(source_ts / 1000, tz=timezone.utc).isoformat()
else:
x_value = row["computed_at"].isoformat()
raw_points.append(
{
"x": row["computed_at"].isoformat(),
"x": x_value,
"y": row[field_name],
"ts_ms": source_ts,
"computed_at": row.get("computed_at"),
}
)
return points
return downsample_points(raw_points, density=density)
def _metric_supports_history(metric_slug, metric_spec):
@@ -983,10 +991,15 @@ def _metric_supports_history(metric_slug, metric_spec):
return any(graph["slug"] == metric_slug for graph in INSIGHT_GRAPH_SPECS)
def _all_graph_payload(conversation):
def _all_graph_payload(conversation, density="medium"):
graphs = []
for spec in INSIGHT_GRAPH_SPECS:
points = _history_points(conversation, spec["field"])
raw_count = (
conversation.metric_snapshots.exclude(
**{f"{spec['field']}__isnull": True}
).count()
)
points = _history_points(conversation, spec["field"], density=density)
graphs.append(
{
"slug": spec["slug"],
@@ -995,6 +1008,7 @@ def _all_graph_payload(conversation):
"group_title": INSIGHT_GROUPS[spec["group"]]["title"],
"points": points,
"count": len(points),
"raw_count": raw_count,
"y_min": spec["y_min"],
"y_max": spec["y_max"],
}
@@ -1002,6 +1016,13 @@ def _all_graph_payload(conversation):
return graphs
def _sanitize_graph_density(value: str) -> str:
density = str(value or "").strip().lower()
if density in DENSITY_POINT_CAPS:
return density
return "medium"
def _information_overview_rows(conversation):
latest_snapshot = conversation.metric_snapshots.first()
rows = []
@@ -3668,9 +3689,12 @@ class AIWorkspaceInsightDetail(LoginRequiredMixin, View):
value = _format_metric_value(conversation, metric, latest_snapshot)
group = INSIGHT_GROUPS[spec["group"]]
graph_applicable = _metric_supports_history(metric, spec)
graph_density = _sanitize_graph_density(request.GET.get("density"))
points = []
if graph_applicable:
points = _history_points(conversation, spec["history_field"])
points = _history_points(
conversation, spec["history_field"], density=graph_density
)
context = {
"person": person,
@@ -3682,6 +3706,8 @@ class AIWorkspaceInsightDetail(LoginRequiredMixin, View):
"metric_group": group,
"graph_points": points,
"graph_applicable": graph_applicable,
"graph_density": graph_density,
"graph_density_caps": DENSITY_POINT_CAPS,
**_workspace_nav_urls(person),
}
return render(request, "pages/ai-workspace-insight-detail.html", context)
@@ -3696,11 +3722,14 @@ class AIWorkspaceInsightGraphs(LoginRequiredMixin, View):
person = get_object_or_404(Person, pk=person_id, user=request.user)
conversation = _conversation_for_person(request.user, person)
graph_cards = _all_graph_payload(conversation)
graph_density = _sanitize_graph_density(request.GET.get("density"))
graph_cards = _all_graph_payload(conversation, density=graph_density)
context = {
"person": person,
"workspace_conversation": conversation,
"graph_cards": graph_cards,
"graph_density": graph_density,
"graph_density_caps": DENSITY_POINT_CAPS,
**_workspace_nav_urls(person),
}
return render(request, "pages/ai-workspace-insight-graphs.html", context)
@@ -3717,9 +3746,10 @@ class AIWorkspaceInformation(LoginRequiredMixin, View):
conversation = _conversation_for_person(request.user, person)
latest_snapshot = conversation.metric_snapshots.first()
directionality = _commitment_directionality_payload(conversation)
graph_density = _sanitize_graph_density(request.GET.get("density"))
commitment_graph_cards = [
card
for card in _all_graph_payload(conversation)
for card in _all_graph_payload(conversation, density=graph_density)
if card["group"] == "commitment"
]
@@ -3743,6 +3773,7 @@ class AIWorkspaceInformation(LoginRequiredMixin, View):
"directionality": directionality,
"overview_rows": _information_overview_rows(conversation),
"commitment_graph_cards": commitment_graph_cards,
"graph_density": graph_density,
**_workspace_nav_urls(person),
}
return render(request, "pages/ai-workspace-information.html", context)