fix: correct content open rate calculation to use % of active members
Some checks are pending
NordaBiz Tests / Unit & Integration Tests (push) Waiting to run
NordaBiz Tests / E2E Tests (Playwright) (push) Blocked by required conditions
NordaBiz Tests / Smoke Tests (Production) (push) Blocked by required conditions
NordaBiz Tests / Send Failure Notification (push) Blocked by required conditions
Some checks are pending
NordaBiz Tests / Unit & Integration Tests (push) Waiting to run
NordaBiz Tests / E2E Tests (Playwright) (push) Blocked by required conditions
NordaBiz Tests / Smoke Tests (Production) (push) Blocked by required conditions
NordaBiz Tests / Send Failure Notification (push) Blocked by required conditions
Open rate now shows percentage of active members who read at least one piece of content, capped at 100%. Previously showed inflated numbers because it counted reads of older content against only recent publications. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
b0d9758d4a
commit
ad4acc7c62
@ -969,60 +969,58 @@ def _tab_pages(db, start_date, days):
|
||||
'top_clicked': [{'name': r.name, 'clicks': r.clicks} for r in top_clicked_companies],
|
||||
}
|
||||
|
||||
# Content open rates (30 days)
|
||||
# Content engagement (30 days)
|
||||
start_30d = datetime.combine(date.today() - timedelta(days=30), datetime.min.time())
|
||||
|
||||
# Announcements: total vs read
|
||||
# Total active members for open rate denominator
|
||||
total_active = db.query(func.count(User.id)).filter(
|
||||
User.is_active == True, User.last_login.isnot(None)
|
||||
).scalar() or 1
|
||||
|
||||
# Announcements: published in 30d + unique readers of those announcements
|
||||
total_announcements = db.query(func.count(Announcement.id)).filter(
|
||||
Announcement.created_at >= start_30d
|
||||
).scalar() or 0
|
||||
announcement_reads = db.query(func.count(func.distinct(AnnouncementRead.announcement_id))).filter(
|
||||
AnnouncementRead.read_at >= start_30d
|
||||
).scalar() or 0
|
||||
# How many unique users read ANY announcement (regardless of when published)
|
||||
total_announcement_readers = db.query(func.count(func.distinct(AnnouncementRead.user_id))).filter(
|
||||
AnnouncementRead.read_at >= start_30d
|
||||
).scalar() or 0
|
||||
# Open rate = % of active members who read at least one announcement
|
||||
ann_open_rate = min(100, round(total_announcement_readers / total_active * 100)) if total_active > 0 else 0
|
||||
|
||||
# Forum: topics published vs read
|
||||
# Forum: topics published in 30d + unique readers
|
||||
total_forum_topics = db.query(func.count(ForumTopic.id)).filter(
|
||||
ForumTopic.created_at >= start_30d
|
||||
).scalar() or 0
|
||||
forum_reads_count = db.query(func.count(func.distinct(ForumTopicRead.topic_id))).filter(
|
||||
ForumTopicRead.read_at >= start_30d
|
||||
).scalar() or 0
|
||||
total_forum_readers = db.query(func.count(func.distinct(ForumTopicRead.user_id))).filter(
|
||||
ForumTopicRead.read_at >= start_30d
|
||||
).scalar() or 0
|
||||
forum_open_rate = min(100, round(total_forum_readers / total_active * 100)) if total_active > 0 else 0
|
||||
|
||||
# Classifieds: published vs read
|
||||
# Classifieds: published in 30d + unique readers
|
||||
total_classifieds = db.query(func.count(Classified.id)).filter(
|
||||
Classified.created_at >= start_30d
|
||||
).scalar() or 0
|
||||
classified_reads_count = db.query(func.count(func.distinct(ClassifiedRead.classified_id))).filter(
|
||||
ClassifiedRead.read_at >= start_30d
|
||||
).scalar() or 0
|
||||
total_classified_readers = db.query(func.count(func.distinct(ClassifiedRead.user_id))).filter(
|
||||
ClassifiedRead.read_at >= start_30d
|
||||
).scalar() or 0
|
||||
classified_open_rate = min(100, round(total_classified_readers / total_active * 100)) if total_active > 0 else 0
|
||||
|
||||
content_engagement = {
|
||||
'announcements': {
|
||||
'published': total_announcements,
|
||||
'read_by': total_announcement_readers,
|
||||
'unique_read': announcement_reads,
|
||||
'open_rate': round(announcement_reads / total_announcements * 100) if total_announcements > 0 else 0,
|
||||
'open_rate': ann_open_rate,
|
||||
},
|
||||
'forum': {
|
||||
'published': total_forum_topics,
|
||||
'read_by': total_forum_readers,
|
||||
'unique_read': forum_reads_count,
|
||||
'open_rate': round(forum_reads_count / total_forum_topics * 100) if total_forum_topics > 0 else 0,
|
||||
'open_rate': forum_open_rate,
|
||||
},
|
||||
'classifieds': {
|
||||
'published': total_classifieds,
|
||||
'read_by': total_classified_readers,
|
||||
'unique_read': classified_reads_count,
|
||||
'open_rate': round(classified_reads_count / total_classifieds * 100) if total_classifieds > 0 else 0,
|
||||
'open_rate': classified_open_rate,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@ -709,7 +709,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div style="margin-bottom: 4px; display: flex; justify-content: space-between;">
|
||||
<span style="font-size: var(--font-size-xs); color: var(--text-secondary);">Open rate</span>
|
||||
<span style="font-size: var(--font-size-xs); color: var(--text-secondary);" title="% aktywnych członków, którzy czytali">Zasięg</span>
|
||||
<span style="font-size: var(--font-size-sm); font-weight: 600; color: {{ '#16a34a' if stats.open_rate >= 50 else ('#f59e0b' if stats.open_rate >= 25 else '#ef4444') }};">{{ stats.open_rate }}%</span>
|
||||
</div>
|
||||
<div style="height: 8px; background: #e2e8f0; border-radius: 4px; overflow: hidden;">
|
||||
|
||||
Loading…
Reference in New Issue
Block a user