Added stats

This commit is contained in:
marc
2025-03-23 00:19:33 +01:00
parent 2f7c7c2429
commit c097811e40
10 changed files with 144 additions and 44 deletions

View File

@@ -1,11 +1,7 @@
{% if session == None %}
L'últim Dimecres de cada mes
{% else %}
{% set dn = date_names(session.date) %}
{{ dn.day_name }} {{ dn.day }} {{ dn.month_name }}
{% set dn = date_names(session.date) %}
{{ dn.day_name }} {{ dn.day }} {{ dn.month_name }}
de {{ session.start_time.strftime("%H:%M") }}
a {{ session.end_time.strftime("%H:%M") }}
{% if session.venue.name %}
{% if session.venue.name %}
a {{ session.venue.name }}
{% endif %}
{% endif %}

View File

@@ -13,5 +13,6 @@
{% include "fragments/tema/lyrics.html" %}
{% include "fragments/tema/links.html" %}
{% include "fragments/tema/properties.html" %}
{% include "fragments/tema/stats.html" %}
</div>
</div>

View File

@@ -0,0 +1,42 @@
{% if tema.stats %}
<h4 class="pt-4 text-xl mt-3 text-beige">Estadístiques</h4>
<hr class="h-px mt-1 mb-3 bg-beige border-0">
<div>
<p>
Aquest tema ha sigut tocat en
{% if tema.stats.times_played == 1%}
una sessió.
{% else %}
{{ tema.stats.times_played }} sessions.
{% endif %}
</p>
<p class="py-2">
S'ha tocat a les sessions següents:
<ol class="flex flex-col items-center justify-center">
{% for session in tema.stats.sessions_played %}
<li class="border rounded border-beige
flex flex-row grow
p-2 m-2 w-full max-w-xl
relative">
<a href="/session/{{session.id}}">
<div class="flex flex-row grow items-center">
<div class="flex-1">
<a href="/sessio/{{session.id}}/">
{% set dn = date_names(session.date) %}
{{ dn.day_name }} {{ dn.day }} {{ dn.month_name }}
</a>
</div>
<div class="ml-auto">
<a title="Més informació"
class="text-beige mx-1"
href="/sessio/{{session.id}}/">
<i class="fa fa-chevron-right" aria-hidden="true"></i>
</a>
</div>
</div>
</li>
{% endfor %}
</ol>
</p>
</div>
{% endif %}

View File

@@ -4,6 +4,7 @@
<tr class="border-b border-beige">
<td class="font-bold py-2 px-4">Nom</td>
<td class="font-bold py-2 px-4">Enllaços</td>
<td class="font-bold py-2 px-4">Cops tocat</td>
</tr>
{% for tema in temes %}
<tr class="border-b border-beige">
@@ -15,6 +16,13 @@
<td class="py-2 px-4">
{% include "fragments/temes/result_links.html" %}
</td>
<td class="py-2 px-4">
{% if tema.stats is none %}
-
{% else %}
{{ tema.stats.times_played }}
{% endif %}
</td>
</tr>
{% endfor %}
</table>

View File

@@ -1,10 +1,11 @@
from collections.abc import Iterator
from collections.abc import Iterable, Iterator
from typing import TypedDict
from folkugat_web.dal.sql import Connection, get_connection
from folkugat_web.dal.sql.sessions import conversion as sessions_conversion
from folkugat_web.model import playlists as model
from folkugat_web.model.sessions import Session
from folkugat_web.utils import groupby
from . import conversion
@@ -55,14 +56,22 @@ def get_playlist_entries(
return map(conversion.row_to_playlist_entry, cur.fetchall())
def get_tune_sessions(tema_ids: list[int], con: Connection | None = None) -> Iterator[Session]:
query = """
GetTuneSessionsRow = tuple[int, int, str, str, str, str | None, str | None, bool]
def get_tune_sessions(tema_ids: list[int], con: Connection | None = None) -> dict[int, list[Session]]:
placeholders = ", ".join(["?" for _ in tema_ids])
query = f"""
SELECT p.tema_id, s.id, s.date, s.start_time, s.end_time, s.venue_name, s.venue_url, s.is_live
FROM playlists p JOIN sessions s ON p.session_id = s.id
WHERE p.tema_id IN :tema_ids
WHERE p.tema_id IN ({placeholders})
"""
data = dict(tema_ids=tuple(tema_ids))
with get_connection(con) as con:
cur = con.cursor()
_ = cur.execute(query, data)
return map(sessions_conversion.row_to_session, cur.fetchall())
_ = cur.execute(query, tema_ids)
result_rows: Iterable[GetTuneSessionsRow] = cur.fetchall()
return dict(groupby(
result_rows,
key_fn=lambda row: row[0],
group_fn=lambda rows: list(sessions_conversion.row_to_session(row[1:]) for row in rows)
))

View File

@@ -1,6 +1,4 @@
from typing import Optional
from fastapi import Request
from fastapi import HTTPException, Request
from folkugat_web.model import temes as model
from folkugat_web.services.temes import query as temes_q
from folkugat_web.services.temes.links import guess_link_type
@@ -12,6 +10,8 @@ def title(request: Request, logged_in: bool, tema: model.Tema | None = None, tem
if tema_id is None:
raise ValueError("Either 'tema' or 'tema_id' must be given!")
tema = temes_q.get_tema_by_id(tema_id)
if not tema:
raise HTTPException(status_code=404, detail="Could not find tune")
return templates.TemplateResponse(
"fragments/tema/title.html",
{
@@ -24,6 +24,8 @@ def title(request: Request, logged_in: bool, tema: model.Tema | None = None, tem
def title_editor(request: Request, logged_in: bool, tema_id: int):
tema = temes_q.get_tema_by_id(tema_id)
if not tema:
raise HTTPException(status_code=404, detail="Could not find tune")
return templates.TemplateResponse(
"fragments/tema/editor/title.html",
{
@@ -36,8 +38,8 @@ def title_editor(request: Request, logged_in: bool, tema_id: int):
def lyric(request: Request, logged_in: bool, tema_id: int, lyric_id: int):
tema = temes_q.get_tema_by_id(tema_id)
if tema is None:
raise ValueError(f"No tune exists for tema_id: {tema_id}")
if not tema:
raise HTTPException(status_code=404, detail="Could not find tune")
lyric = tema.lyrics.get(lyric_id)
return templates.TemplateResponse(
@@ -54,8 +56,8 @@ def lyric(request: Request, logged_in: bool, tema_id: int, lyric_id: int):
def lyric_editor(request: Request, logged_in: bool, tema_id: int, lyric_id: int):
tema = temes_q.get_tema_by_id(tema_id)
if tema is None:
raise ValueError(f"No tune exists for tema_id: {tema_id}")
if not tema:
raise HTTPException(status_code=404, detail="Could not find tune")
lyric = tema.lyrics.get(lyric_id)
return templates.TemplateResponse(
@@ -72,8 +74,8 @@ def lyric_editor(request: Request, logged_in: bool, tema_id: int, lyric_id: int)
def link(request: Request, logged_in: bool, tema_id: int, link_id: int):
tema = temes_q.get_tema_by_id(tema_id)
if tema is None:
raise ValueError(f"No tune exists for tema_id: {tema_id}")
if not tema:
raise HTTPException(status_code=404, detail="Could not find tune")
link = tema.links.get(link_id)
return templates.TemplateResponse(
@@ -95,8 +97,8 @@ def link(request: Request, logged_in: bool, tema_id: int, link_id: int):
def link_editor(request: Request, logged_in: bool, tema_id: int, link_id: int):
tema = temes_q.get_tema_by_id(tema_id)
if tema is None:
raise ValueError(f"No tune exists for tema_id: {tema_id}")
if not tema:
raise HTTPException(status_code=404, detail="Could not find tune")
link = tema.links.get(link_id)
return templates.TemplateResponse(
@@ -115,8 +117,8 @@ def link_editor(request: Request, logged_in: bool, tema_id: int, link_id: int):
def link_editor_url(request: Request, logged_in: bool, tema_id: int, link_id: int):
tema = temes_q.get_tema_by_id(tema_id)
if tema is None:
raise ValueError(f"No tune exists for tema_id: {tema_id}")
if not tema:
raise HTTPException(status_code=404, detail="Could not find tune")
link = tema.links.get(link_id)
return templates.TemplateResponse(
"fragments/tema/editor/link_url.html",
@@ -134,8 +136,8 @@ def link_editor_url(request: Request, logged_in: bool, tema_id: int, link_id: in
def link_editor_file(request: Request, logged_in: bool, tema_id: int, link_id: int):
tema = temes_q.get_tema_by_id(tema_id)
if tema is None:
raise ValueError(f"No tune exists for tema_id: {tema_id}")
if not tema:
raise HTTPException(status_code=404, detail="Could not find tune")
link = tema.links.get(link_id)
return templates.TemplateResponse(
"fragments/tema/editor/link_file.html",
@@ -175,8 +177,8 @@ def link_icon(request: Request, logged_in: bool, tema_id: int, link_id: int, url
def score(request: Request, logged_in: bool, tema_id: int):
tema = temes_q.get_tema_by_id(tema_id)
if tema is None:
raise ValueError(f"No tune exists for tema_id: {tema_id}")
if not tema:
raise HTTPException(status_code=404, detail="Could not find tune")
return templates.TemplateResponse(
"fragments/tema/score.html",
{
@@ -190,8 +192,8 @@ def score(request: Request, logged_in: bool, tema_id: int):
def property_(request: Request, logged_in: bool, tema_id: int, property_id: int):
tema = temes_q.get_tema_by_id(tema_id)
if tema is None:
raise ValueError(f"No tune exists for tema_id: {tema_id}")
if not tema:
raise HTTPException(status_code=404, detail="Could not find tune")
prop = tema.properties.get(property_id)
return templates.TemplateResponse(
@@ -209,8 +211,8 @@ def property_(request: Request, logged_in: bool, tema_id: int, property_id: int)
def property_editor(request: Request, logged_in: bool, tema_id: int, property_id: int):
tema = temes_q.get_tema_by_id(tema_id)
if tema is None:
raise ValueError(f"No tune exists for tema_id: {tema_id}")
if not tema:
raise HTTPException(status_code=404, detail="Could not find tune")
prop = tema.properties.get(property_id)
return templates.TemplateResponse(

View File

@@ -1,6 +1,7 @@
from fastapi import Request
from fastapi import HTTPException, Request
from folkugat_web.model import temes as model
from folkugat_web.model.pagines import Pages
from folkugat_web.services import sessions as sessions_service
from folkugat_web.services.temes import query as temes_q
from folkugat_web.services.temes import search as temes_s
from folkugat_web.templates import templates
@@ -34,6 +35,8 @@ def temes_busca(request: Request, logged_in: bool, query: str, offset: int = 0,
if offset > 0:
prev_offset = max(offset - limit, 0)
temes = temes_q.temes_compute_stats(temes)
return templates.TemplateResponse(
"fragments/temes/results.html",
{
@@ -51,6 +54,9 @@ def temes_busca(request: Request, logged_in: bool, query: str, offset: int = 0,
def tema(request: Request, tema_id: int, logged_in: bool):
tema = temes_q.get_tema_by_id(tema_id)
if not tema:
raise HTTPException(status_code=404, detail="Could not find tune")
tema = temes_q.tema_compute_stats(tema)
return templates.TemplateResponse(
"fragments/tema/pagina.html",
{
@@ -60,5 +66,6 @@ def tema(request: Request, tema_id: int, logged_in: bool):
"LinkType": model.LinkType,
"ContentType": model.ContentType,
"tema": tema,
"date_names": sessions_service.get_date_names,
}
)

View File

@@ -101,6 +101,12 @@ class Lyrics(WithId):
)
@dataclasses.dataclass
class Stats:
times_played: int
sessions_played: list[Session]
@dataclasses.dataclass
class Tema:
id: int | None = None
@@ -117,7 +123,7 @@ class Tema:
modification_date: datetime.datetime = dataclasses.field(default_factory=datetime.datetime.now)
creation_date: datetime.datetime = dataclasses.field(default_factory=datetime.datetime.now)
# Stats
played: list[Session] = dataclasses.field(default_factory=list)
stats: Stats | None = None
def compute_ngrams(self):
self.ngrams = ngrams.get_text_ngrams(self.title, *self.alternatives)
@@ -126,5 +132,13 @@ class Tema:
self.compute_ngrams()
return self
@staticmethod
def _is_score(link: Link) -> bool:
if link.content_type is not ContentType.PARTITURA:
return False
if link.link_type is LinkType.PDF:
return link.url.startswith("/")
return link.link_type is LinkType.IMAGE
def score(self) -> Link | None:
return next(filter(lambda l: l.content_type is ContentType.PARTITURA and l.url.startswith('/'), self.links), None)
return next(filter(self._is_score, self.links), None)

View File

@@ -1,8 +1,29 @@
from typing import Optional
from folkugat_web.dal.sql.playlists import query as playlists_q
from folkugat_web.dal.sql.temes import query as temes_q
from folkugat_web.model import sessions as sessions_model
from folkugat_web.model import temes as model
def get_tema_by_id(tema_id: int) -> model.Tema | None:
return temes_q.get_tema_by_id(tema_id)
def tema_compute_stats(
tema: model.Tema,
tune_sessions_dict: dict[int, list[sessions_model.Session]] | None = None,
) -> model.Tema:
if tema.id:
if tune_sessions_dict is None:
tune_sessions_dict = playlists_q.get_tune_sessions(tema_ids=[tema.id])
if tema.id and (tune_sessions := tune_sessions_dict.get(tema.id)):
tema.stats = model.Stats(
times_played=len(tune_sessions),
sessions_played=list(reversed(sorted(tune_sessions, key=lambda s: s.date))),
)
return tema
def temes_compute_stats(temes: list[model.Tema]) -> list[model.Tema]:
tema_ids = [tema.id for tema in temes if tema.id is not None]
tune_sessions_dict = playlists_q.get_tune_sessions(tema_ids=tema_ids)
return [tema_compute_stats(tema=tema, tune_sessions_dict=tune_sessions_dict) for tema in temes]

View File

@@ -4,7 +4,7 @@ from typing import Protocol, Self, TypeVar
class SupportsLessThan(Protocol):
def __lt__(self, other: Self) -> bool:
def __lt__(self, other: Self, /) -> bool:
raise NotImplementedError()