welearn-database 1.4.5.dev1__tar.gz → 1.5.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. {welearn_database-1.4.5.dev1 → welearn_database-1.5.2}/PKG-INFO +1 -1
  2. {welearn_database-1.4.5.dev1 → welearn_database-1.5.2}/pyproject.toml +1 -1
  3. welearn_database-1.5.2/welearn_database/alembic/versions/5aad97149044_add_histo_table_for_materialized_views.py +71 -0
  4. welearn_database-1.5.2/welearn_database/alembic/versions/6d4346fad6f4_view_used_feature_per_session.py +88 -0
  5. welearn_database-1.5.2/welearn_database/alembic/versions/95ffe7afa64c_add_index_idx_welearn_document_keyword_.py +35 -0
  6. welearn_database-1.5.2/welearn_database/alembic/versions/b84462ca3800_update_grafana_process_state_view.py +45 -0
  7. welearn_database-1.5.2/welearn_database/alembic/versions/f5e91a5c950d_add_analytic_form.py +67 -0
  8. welearn_database-1.5.2/welearn_database/data/details_dict.py +30 -0
  9. {welearn_database-1.4.5.dev1 → welearn_database-1.5.2}/welearn_database/data/enumeration.py +6 -0
  10. {welearn_database-1.4.5.dev1 → welearn_database-1.5.2}/welearn_database/data/models/document_related.py +62 -2
  11. {welearn_database-1.4.5.dev1 → welearn_database-1.5.2}/welearn_database/data/models/grafana.py +15 -0
  12. {welearn_database-1.4.5.dev1 → welearn_database-1.5.2}/welearn_database/data/models/user_related.py +33 -1
  13. {welearn_database-1.4.5.dev1 → welearn_database-1.5.2}/LICENSE +0 -0
  14. {welearn_database-1.4.5.dev1 → welearn_database-1.5.2}/README.md +0 -0
  15. {welearn_database-1.4.5.dev1 → welearn_database-1.5.2}/welearn_database/__init__.py +0 -0
  16. {welearn_database-1.4.5.dev1 → welearn_database-1.5.2}/welearn_database/alembic/README +0 -0
  17. {welearn_database-1.4.5.dev1 → welearn_database-1.5.2}/welearn_database/alembic/env.py +0 -0
  18. {welearn_database-1.4.5.dev1 → welearn_database-1.5.2}/welearn_database/alembic/script.py.mako +0 -0
  19. {welearn_database-1.4.5.dev1 → welearn_database-1.5.2}/welearn_database/alembic/versions/068312e7800c_add_referrer_origin_column_to_user_and_.py +0 -0
  20. {welearn_database-1.4.5.dev1 → welearn_database-1.5.2}/welearn_database/alembic/versions/0e0bc0fca384_doc_qty_per_source.py +0 -0
  21. {welearn_database-1.4.5.dev1 → welearn_database-1.5.2}/welearn_database/alembic/versions/16ff997426d3_remove_error_retrieval_unique_constraint.py +0 -0
  22. {welearn_database-1.4.5.dev1 → welearn_database-1.5.2}/welearn_database/alembic/versions/2ad4895b2674_data_collection.py +0 -0
  23. {welearn_database-1.4.5.dev1 → welearn_database-1.5.2}/welearn_database/alembic/versions/4c7161819e5a_grafana_views.py +0 -0
  24. {welearn_database-1.4.5.dev1 → welearn_database-1.5.2}/welearn_database/alembic/versions/4f5a188dd614_add_main_url_column.py +0 -0
  25. {welearn_database-1.4.5.dev1 → welearn_database-1.5.2}/welearn_database/alembic/versions/4fcbfb7f3145_added_api_key_management_table.py +0 -0
  26. {welearn_database-1.4.5.dev1 → welearn_database-1.5.2}/welearn_database/alembic/versions/5d82613c9aca_context_document.py +0 -0
  27. {welearn_database-1.4.5.dev1 → welearn_database-1.5.2}/welearn_database/alembic/versions/821173cf9c5d_initial_migration.py +0 -0
  28. {welearn_database-1.4.5.dev1 → welearn_database-1.5.2}/welearn_database/alembic/versions/84c42739c17b_create_table_errordataquality.py +0 -0
  29. {welearn_database-1.4.5.dev1 → welearn_database-1.5.2}/welearn_database/alembic/versions/89920abb7ff8_add_category.py +0 -0
  30. {welearn_database-1.4.5.dev1 → welearn_database-1.5.2}/welearn_database/alembic/versions/8b780aea403a_add_university_data.py +0 -0
  31. {welearn_database-1.4.5.dev1 → welearn_database-1.5.2}/welearn_database/alembic/versions/96bba9e4842a_merge_external_id_error_quality.py +0 -0
  32. {welearn_database-1.4.5.dev1 → welearn_database-1.5.2}/welearn_database/alembic/versions/9b4f1da0c1f2_data_collection_for_focus_group.py +0 -0
  33. {welearn_database-1.4.5.dev1 → welearn_database-1.5.2}/welearn_database/alembic/versions/a50a1db3ca2a_add_used_since_column_for_embeddings.py +0 -0
  34. {welearn_database-1.4.5.dev1 → welearn_database-1.5.2}/welearn_database/alembic/versions/b031206324b7_agent_related.py +0 -0
  35. {welearn_database-1.4.5.dev1 → welearn_database-1.5.2}/welearn_database/alembic/versions/b049924f7067_modify_corpus_name_embedding_model_lang_.py +0 -0
  36. {welearn_database-1.4.5.dev1 → welearn_database-1.5.2}/welearn_database/alembic/versions/ccdbd708c997_create_column_external_id.py +0 -0
  37. {welearn_database-1.4.5.dev1 → welearn_database-1.5.2}/welearn_database/alembic/versions/e354666f951d_inferred_user.py +0 -0
  38. {welearn_database-1.4.5.dev1 → welearn_database-1.5.2}/welearn_database/alembic/versions/f1ce0ad2845b_add_doi_column_to_document.py +0 -0
  39. {welearn_database-1.4.5.dev1 → welearn_database-1.5.2}/welearn_database/alembic/versions/f8602200fa99_update_session_view.py +0 -0
  40. {welearn_database-1.4.5.dev1 → welearn_database-1.5.2}/welearn_database/data/__init__.py +0 -0
  41. {welearn_database-1.4.5.dev1 → welearn_database-1.5.2}/welearn_database/data/models/__init__.py +0 -0
  42. {welearn_database-1.4.5.dev1 → welearn_database-1.5.2}/welearn_database/data/models/agent_related.py +0 -0
  43. {welearn_database-1.4.5.dev1 → welearn_database-1.5.2}/welearn_database/data/models/corpus_related.py +0 -0
  44. {welearn_database-1.4.5.dev1 → welearn_database-1.5.2}/welearn_database/database_utils.py +0 -0
  45. {welearn_database-1.4.5.dev1 → welearn_database-1.5.2}/welearn_database/exceptions.py +0 -0
  46. {welearn_database-1.4.5.dev1 → welearn_database-1.5.2}/welearn_database/modules/__init__.py +0 -0
  47. {welearn_database-1.4.5.dev1 → welearn_database-1.5.2}/welearn_database/modules/text_cleaning.py +0 -0
  48. {welearn_database-1.4.5.dev1 → welearn_database-1.5.2}/welearn_database/regular_expression.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: welearn-database
3
- Version: 1.4.5.dev1
3
+ Version: 1.5.2
4
4
  Summary: All stuff related to relationnal database from the WeLearn project
5
5
  License: cc-by-sa-nc
6
6
  Author: Théo
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "welearn-database"
3
- version = "1.4.5.dev1"
3
+ version = "1.5.2"
4
4
  description = "All stuff related to relationnal database from the WeLearn project"
5
5
  authors = [
6
6
  {name = "Théo",email = "theo.nardin@cri-paris.org"}
@@ -0,0 +1,71 @@
1
+ """add_histo_table_for_materialized_views
2
+
3
+ Revision ID: 5aad97149044
4
+ Revises: b84462ca3800
5
+ Create Date: 2026-06-17 11:31:55.292978
6
+
7
+ """
8
+
9
+ from typing import Sequence, Union
10
+
11
+ import sqlalchemy as sa
12
+ from alembic import op
13
+ from sqlalchemy import text
14
+ from sqlalchemy.dialects import postgresql
15
+
16
+ # revision identifiers, used by Alembic.
17
+ revision: str = "5aad97149044"
18
+ down_revision: Union[str, None] = "b84462ca3800"
19
+ branch_labels: Union[str, Sequence[str], None] = None
20
+ depends_on: Union[str, Sequence[str], None] = None
21
+
22
+
23
+ def upgrade() -> None:
24
+ op.create_table(
25
+ "historical_qty_document_per_corpus",
26
+ sa.Column(
27
+ "id", sa.Uuid(), server_default=text("gen_random_uuid()"), nullable=False
28
+ ),
29
+ sa.Column("source_name", sa.String(), nullable=False),
30
+ sa.Column("count", sa.Integer(), nullable=False),
31
+ sa.Column(
32
+ "created_at", postgresql.TIMESTAMP(), server_default="NOW()", nullable=False
33
+ ),
34
+ sa.PrimaryKeyConstraint("id"),
35
+ schema="document_related",
36
+ )
37
+
38
+ op.create_table(
39
+ "historical_qty_document_in_qdrant_per_corpus",
40
+ sa.Column(
41
+ "id", sa.Uuid(), server_default=text("gen_random_uuid()"), nullable=False
42
+ ),
43
+ sa.Column("source_name", sa.String(), nullable=False),
44
+ sa.Column("count", sa.Integer(), nullable=False),
45
+ sa.Column(
46
+ "created_at", postgresql.TIMESTAMP(), server_default="NOW()", nullable=False
47
+ ),
48
+ sa.PrimaryKeyConstraint("id"),
49
+ schema="document_related",
50
+ )
51
+
52
+ op.create_table(
53
+ "historical_qty_document_in_qdrant",
54
+ sa.Column(
55
+ "id", sa.Uuid(), server_default=text("gen_random_uuid()"), nullable=False
56
+ ),
57
+ sa.Column("count", sa.Integer(), nullable=False),
58
+ sa.Column(
59
+ "created_at", postgresql.TIMESTAMP(), server_default="NOW()", nullable=False
60
+ ),
61
+ sa.PrimaryKeyConstraint("id"),
62
+ schema="document_related",
63
+ )
64
+
65
+
66
+ def downgrade() -> None:
67
+ op.drop_table("historical_qty_document_per_corpus", schema="document_related")
68
+ op.drop_table(
69
+ "historical_qty_document_in_qdrant_per_corpus", schema="document_related"
70
+ )
71
+ op.drop_table("historical_qty_document_in_qdrant", schema="document_related")
@@ -0,0 +1,88 @@
1
+ """view_used_feature_per_session
2
+
3
+ Revision ID: 6d4346fad6f4
4
+ Revises: 5aad97149044
5
+ Create Date: 2026-06-17 11:54:52.441998
6
+
7
+ """
8
+
9
+ from typing import Sequence, Union
10
+
11
+ from alembic import op
12
+
13
+ # revision identifiers, used by Alembic.
14
+ revision: str = "6d4346fad6f4"
15
+ down_revision: Union[str, None] = "5aad97149044"
16
+ branch_labels: Union[str, Sequence[str], None] = None
17
+ depends_on: Union[str, Sequence[str], None] = None
18
+
19
+
20
+ def upgrade() -> None:
21
+ op.execute("""
22
+ CREATE OR REPLACE VIEW grafana.used_feature_per_session
23
+ AS WITH matching_features_endpoint AS (
24
+ SELECT
25
+ endpoint_name,
26
+ feature_name
27
+ FROM
28
+ (
29
+ VALUES
30
+ ('/api/v1/search/by_document', 'search'),
31
+ ('/api/v1/qna/chat/answer', 'chat'),
32
+ ('/api/v1/qna/chat/agent', 'chat'),
33
+ ('/api/v1/tutor/syllabus', 'syllabus'),
34
+ ('/api/v1/user/bookmarks/' || ':' || 'document_id', 'bookmark'),
35
+ ('/api/v1/user/' || ':' || 'user_id' || '/bookmarks/' || ':' || 'document_id', 'bookmark')
36
+ ) AS t(endpoint_name, feature_name)
37
+ ),
38
+ session_feature_pair AS (
39
+ SELECT
40
+ DISTINCT
41
+ er.session_id,
42
+ mfe.feature_name
43
+ FROM
44
+ user_related.endpoint_request er
45
+ CROSS JOIN matching_features_endpoint mfe
46
+ ORDER BY
47
+ er.session_id
48
+ ),
49
+ actual_count AS (
50
+ SELECT
51
+ er.session_id,
52
+ mfe.feature_name,
53
+ COUNT(1) AS cnt
54
+ FROM
55
+ user_related.endpoint_request er
56
+ INNER JOIN
57
+ user_related."session" s ON
58
+ s.id = er.session_id
59
+ INNER JOIN
60
+ matching_features_endpoint mfe ON
61
+ mfe.endpoint_name = er.endpoint_name
62
+ GROUP BY
63
+ er.session_id,
64
+ mfe.feature_name
65
+ )
66
+ SELECT
67
+ s.inferred_user_id,
68
+ sfp.session_id,
69
+ sfp.feature_name,
70
+ COALESCE(ac.cnt, 0) AS cnt,
71
+ COALESCE(ac.cnt, 0) > 0 AS is_feature_used,
72
+ s.created_at AS session_created_at
73
+ FROM
74
+ session_feature_pair sfp
75
+ LEFT JOIN actual_count ac ON
76
+ ac.feature_name = sfp.feature_name
77
+ AND ac.session_id = sfp.session_id
78
+ INNER JOIN user_related."session" s ON
79
+ s.id = sfp.session_id
80
+ ORDER BY
81
+ session_created_at
82
+ """)
83
+
84
+
85
+ def downgrade() -> None:
86
+ op.execute("""
87
+ DROP VIEW IF EXISTS grafana.used_feature_per_session;
88
+ """)
@@ -0,0 +1,35 @@
1
+ """add_index_idx_welearn_document_keyword_document_id
2
+
3
+ Revision ID: 95ffe7afa64c
4
+ Revises: f5e91a5c950d
5
+ Create Date: 2026-09-16 15:44:06.977345
6
+
7
+ """
8
+
9
+ from typing import Sequence, Union
10
+
11
+ import sqlalchemy as sa
12
+ from alembic import op
13
+
14
+ # revision identifiers, used by Alembic.
15
+ revision: str = "95ffe7afa64c"
16
+ down_revision: Union[str, None] = "f5e91a5c950d"
17
+ branch_labels: Union[str, Sequence[str], None] = None
18
+ depends_on: Union[str, Sequence[str], None] = None
19
+
20
+
21
+ def upgrade() -> None:
22
+ op.create_index(
23
+ "idx_welearn_document_keyword_document_id",
24
+ "welearn_document_keyword",
25
+ ["welearn_document_id"],
26
+ schema="document_related",
27
+ )
28
+
29
+
30
+ def downgrade() -> None:
31
+ op.drop_index(
32
+ "idx_welearn_document_keyword_document_id",
33
+ table_name="document_keyword",
34
+ schema="document_related",
35
+ )
@@ -0,0 +1,45 @@
1
+ """update grafana process state view
2
+
3
+ Revision ID: b84462ca3800
4
+ Revises: 8b780aea403a
5
+ Create Date: 2026-06-04 16:56:18.693751
6
+
7
+ """
8
+
9
+ from typing import Sequence, Union
10
+
11
+ import sqlalchemy as sa
12
+ from alembic import op
13
+
14
+ # revision identifiers, used by Alembic.
15
+ revision: str = "b84462ca3800"
16
+ down_revision: Union[str, None] = "8b780aea403a"
17
+ branch_labels: Union[str, Sequence[str], None] = None
18
+ depends_on: Union[str, Sequence[str], None] = None
19
+
20
+
21
+ def upgrade() -> None:
22
+ op.execute("""
23
+ CREATE OR REPLACE VIEW grafana.process_state
24
+ AS SELECT process_state.id,
25
+ process_state.document_id,
26
+ process_state.title,
27
+ process_state.created_at,
28
+ process_state.operation_order,
29
+ corpus.source_name
30
+ FROM document_related.process_state
31
+ JOIN document_related.welearn_document ON welearn_document.id = process_state.document_id
32
+ JOIN corpus_related.corpus ON welearn_document.corpus_id = corpus.id;
33
+ """)
34
+
35
+
36
+ def downgrade() -> None:
37
+ op.execute("""
38
+ CREATE OR REPLACE VIEW grafana.process_state
39
+ AS SELECT process_state.id,
40
+ process_state.document_id,
41
+ process_state.title,
42
+ process_state.created_at,
43
+ process_state.operation_order,
44
+ FROM document_related.process_state;
45
+ """)
@@ -0,0 +1,67 @@
1
+ """add analytic form
2
+
3
+ Revision ID: f5e91a5c950d
4
+ Revises: 6d4346fad6f4
5
+ Create Date: 2026-09-02 16:32:55.188400
6
+
7
+ """
8
+
9
+ from typing import Sequence, Union
10
+
11
+ import sqlalchemy as sa
12
+ from alembic import op
13
+ from sqlalchemy.dialects import postgresql
14
+
15
+ # revision identifiers, used by Alembic.
16
+ revision: str = "f5e91a5c950d"
17
+ down_revision: Union[str, None] = "6d4346fad6f4"
18
+ branch_labels: Union[str, Sequence[str], None] = None
19
+ depends_on: Union[str, Sequence[str], None] = None
20
+
21
+
22
+ def upgrade() -> None:
23
+ op.create_table(
24
+ "analytic_form",
25
+ sa.Column(
26
+ "id", sa.Uuid(), server_default=sa.func.gen_random_uuid(), nullable=False
27
+ ),
28
+ sa.Column("form_name", sa.String(), nullable=False),
29
+ sa.Column("question", sa.String(), nullable=False),
30
+ sa.Column("answer", sa.String(), nullable=False),
31
+ sa.Column(
32
+ "answer_type",
33
+ postgresql.ENUM(
34
+ "text",
35
+ "checkbox",
36
+ "predefined_text",
37
+ name="answer_type",
38
+ schema="user_related",
39
+ ),
40
+ nullable=False,
41
+ ),
42
+ sa.Column("session_id", sa.Uuid(), nullable=False),
43
+ sa.Column(
44
+ "created_at",
45
+ sa.TIMESTAMP(timezone=False),
46
+ nullable=False,
47
+ server_default="NOW()",
48
+ ),
49
+ sa.ForeignKeyConstraint(
50
+ ["session_id"],
51
+ ["user_related.session.id"],
52
+ name="analytic_form_session_id_fkey",
53
+ ),
54
+ sa.PrimaryKeyConstraint("id"),
55
+ schema="user_related",
56
+ )
57
+
58
+
59
+ def downgrade() -> None:
60
+ op.drop_constraint(
61
+ "analytic_form_session_id_fkey",
62
+ "analytic_form",
63
+ schema="user_related",
64
+ type_="foreignkey",
65
+ )
66
+ op.drop_table("analytic_form", schema="user_related")
67
+ op.execute("DROP TYPE IF EXISTS user_related.answer_type")
@@ -0,0 +1,30 @@
1
+ from dataclasses import asdict, is_dataclass
2
+
3
+ from sqlalchemy import types
4
+
5
+
6
+ class DetailsDict(types.TypeDecorator):
7
+ """
8
+ Convert dataclass instances into dictionaries for JSON storage.
9
+ """
10
+
11
+ impl = types.JSON
12
+ cache_ok = True
13
+
14
+ @staticmethod
15
+ def _is_dataclass_instance(obj):
16
+ return is_dataclass(obj) and not isinstance(obj, type)
17
+
18
+ def _inner_serialize_dataclass(self, value):
19
+ match value:
20
+ case list():
21
+ return [self._inner_serialize_dataclass(item) for item in value]
22
+ case dict():
23
+ return {k: self._inner_serialize_dataclass(v) for k, v in value.items()}
24
+ if self._is_dataclass_instance(value):
25
+ return asdict(value)
26
+ return value
27
+
28
+ def process_bind_param(self, value, dialect):
29
+ # Serialize recursively without mutating the original object stored on the ORM instance.
30
+ return self._inner_serialize_dataclass(value)
@@ -43,3 +43,9 @@ class ExternalIdType(StrEnum):
43
43
  class FilterType(StrEnum):
44
44
  SDG = auto()
45
45
  SOURCE = auto()
46
+
47
+
48
+ class QuestionType(StrEnum):
49
+ TEXT = auto()
50
+ CHECKBOX = auto()
51
+ PREDEFINED_TEXT = auto()
@@ -16,6 +16,7 @@ from sqlalchemy import (
16
16
  from sqlalchemy.dialects.postgresql import ARRAY, ENUM, TIMESTAMP
17
17
  from sqlalchemy.orm import Mapped, mapped_column, relationship, validates
18
18
 
19
+ from welearn_database.data.details_dict import DetailsDict
19
20
  from welearn_database.data.enumeration import (
20
21
  ContextType,
21
22
  Counter,
@@ -44,7 +45,7 @@ class WeLearnDocument(Base):
44
45
  """
45
46
  This class represents a document in the WeLearn system.
46
47
  :cvar id: The unique identifier of the document.
47
- :cvar doi: The unique DOI identifier if it exist for this document
48
+ :cvar doi: The unique DOI identifier if it exists for this document
48
49
  :cvar external_id: ID use by the document provider for identify it (ex: PubMed ID, ArXiv ID, OAI PMH identifier etc.)
49
50
  :cvar external_id_type: The type of the external ID, represented as an enumeration
50
51
  :cvar url: The URL of the document.
@@ -83,7 +84,7 @@ class WeLearnDocument(Base):
83
84
  lang: Mapped[str | None]
84
85
  description: Mapped[str | None]
85
86
  full_content: Mapped[str | None]
86
- details: Mapped[dict[str, Any] | None]
87
+ details: Mapped[dict[str, Any] | None] = mapped_column(DetailsDict)
87
88
  trace: Mapped[int | None] = mapped_column(types.BIGINT)
88
89
  corpus_id: Mapped[UUID] = mapped_column(
89
90
  types.Uuid,
@@ -478,6 +479,65 @@ class ContextDocument(Base):
478
479
  )
479
480
 
480
481
 
482
+ class HistoricalQtyDocumentPerCorpus(Base):
483
+ __tablename__ = "historical_qty_document_per_corpus"
484
+ __table_args__ = {"schema": schema_name}
485
+
486
+ id = mapped_column(
487
+ types.Uuid,
488
+ primary_key=True,
489
+ server_default=GEN_RANDOM_UUID,
490
+ nullable=False,
491
+ )
492
+ source_name: Mapped[str] = mapped_column()
493
+ count: Mapped[int]
494
+ created_at: Mapped[datetime] = mapped_column(
495
+ TIMESTAMP(timezone=False),
496
+ nullable=False,
497
+ default=func.localtimestamp(),
498
+ server_default=NOW,
499
+ )
500
+
501
+
502
+ class HistoricalQtyDocumentInQdrantPerCorpus(Base):
503
+ __tablename__ = "historical_qty_document_in_qdrant_per_corpus"
504
+ __table_args__ = {"schema": schema_name}
505
+
506
+ id = mapped_column(
507
+ types.Uuid,
508
+ primary_key=True,
509
+ server_default=GEN_RANDOM_UUID,
510
+ nullable=False,
511
+ )
512
+ source_name: Mapped[str] = mapped_column()
513
+ count: Mapped[int]
514
+ created_at: Mapped[datetime] = mapped_column(
515
+ TIMESTAMP(timezone=False),
516
+ nullable=False,
517
+ default=func.localtimestamp(),
518
+ server_default=NOW,
519
+ )
520
+
521
+
522
+ class HistoricalQtyDocumentInQdrant(Base):
523
+ __tablename__ = "historical_qty_document_in_qdrant"
524
+ __table_args__ = {"schema": schema_name}
525
+
526
+ id = mapped_column(
527
+ types.Uuid,
528
+ primary_key=True,
529
+ server_default=GEN_RANDOM_UUID,
530
+ nullable=False,
531
+ )
532
+ count: Mapped[int]
533
+ created_at: Mapped[datetime] = mapped_column(
534
+ TIMESTAMP(timezone=False),
535
+ nullable=False,
536
+ default=func.localtimestamp(),
537
+ server_default=NOW,
538
+ )
539
+
540
+
481
541
  # Views
482
542
  class QtyDocumentInQdrant(Base):
483
543
  __tablename__ = "qty_document_in_qdrant"
@@ -136,3 +136,18 @@ class Session(Base):
136
136
  created_at: Mapped[datetime] = mapped_column()
137
137
  end_at: Mapped[datetime | None] = mapped_column(nullable=True)
138
138
  host: Mapped[str | None] = mapped_column(nullable=True)
139
+
140
+
141
+ class UsedFeaturePerSession(Base):
142
+ __tablename__ = "used_feature_per_session"
143
+ __table_args__ = {"schema": schema_name}
144
+ __read_only__ = True
145
+
146
+ inferred_user_id: Mapped[uuid.UUID] = mapped_column(
147
+ UUID(as_uuid=True), primary_key=True
148
+ )
149
+ session_id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True)
150
+ feature_name: Mapped[str] = mapped_column(primary_key=True)
151
+ cnt: Mapped[int] = mapped_column()
152
+ is_feature_used: Mapped[bool] = mapped_column()
153
+ session_created_at: Mapped[datetime] = mapped_column()
@@ -5,7 +5,7 @@ from sqlalchemy import ForeignKey, func, types
5
5
  from sqlalchemy.dialects.postgresql import ENUM, TIMESTAMP
6
6
  from sqlalchemy.orm import Mapped, mapped_column, relationship
7
7
 
8
- from welearn_database.data.enumeration import DbSchemaEnum, FilterType
8
+ from welearn_database.data.enumeration import DbSchemaEnum, FilterType, QuestionType
9
9
  from welearn_database.data.models.document_related import WeLearnDocument
10
10
 
11
11
  from . import Base
@@ -271,3 +271,35 @@ class FilterUsedInQuery(Base):
271
271
  filter_value: Mapped[str]
272
272
 
273
273
  chat_message: Mapped["ChatMessage"] = relationship()
274
+
275
+
276
+ class AnalyticForm(Base):
277
+ __tablename__ = "analytic_form"
278
+ __table_args__ = {"schema": DbSchemaEnum.USER_RELATED.value}
279
+
280
+ id: Mapped[UUID] = mapped_column(
281
+ types.Uuid, primary_key=True, nullable=False, server_default="gen_random_uuid()"
282
+ )
283
+ form_name: Mapped[str] = mapped_column(nullable=False)
284
+ question: Mapped[str] = mapped_column(nullable=False)
285
+ answer: Mapped[str] = mapped_column(nullable=False)
286
+ answer_type: Mapped[str] = mapped_column(
287
+ ENUM(
288
+ *(e.value.lower() for e in QuestionType),
289
+ name="answer_type",
290
+ schema=DbSchemaEnum.USER_RELATED.value,
291
+ ),
292
+ nullable=False,
293
+ )
294
+ session_id: Mapped[UUID] = mapped_column(
295
+ types.Uuid,
296
+ ForeignKey(f"{DbSchemaEnum.USER_RELATED.value}.session.id"),
297
+ nullable=False,
298
+ )
299
+ created_at: Mapped[datetime] = mapped_column(
300
+ TIMESTAMP(timezone=False),
301
+ nullable=False,
302
+ default=func.localtimestamp(),
303
+ server_default="NOW()",
304
+ )
305
+ session = relationship("Session", foreign_keys=[session_id])