Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
"""alterar unidade de anotação de comentário para dataset_entry (usuário)

Revision ID: 0015
Revises: 0014
Create Date: 2026-04-08 00:00:00.000000

Migração destrutiva: remove todas as anotações, conflitos e resoluções
existentes antes de alterar as FKs. Necessário porque a mudança de
granularidade (comentário → usuário) invalida os dados anteriores.
"""

from collections.abc import Sequence

import sqlalchemy as sa

from alembic import op

revision: str = "0015"
down_revision: str | None = "0014"
branch_labels: str | Sequence[str] | None = None
depends_on: str | Sequence[str] | None = None


def upgrade() -> None:
# 1) Limpar dados existentes (mudança de granularidade os invalida)
op.execute("DELETE FROM resolutions")
op.execute("DELETE FROM annotation_conflicts")
op.execute("DELETE FROM annotations")

# 2) annotations: comment_id → dataset_entry_id
op.drop_constraint("uq_comment_annotator", "annotations", type_="unique")
op.drop_constraint("annotations_comment_id_fkey", "annotations", type_="foreignkey")
op.drop_column("annotations", "comment_id")

op.add_column(
"annotations",
sa.Column(
"dataset_entry_id",
sa.Uuid(),
sa.ForeignKey("dataset_entries.id"),
nullable=False,
),
)
op.create_unique_constraint(
"uq_entry_annotator",
"annotations",
["dataset_entry_id", "annotator_id"],
)

# 3) annotation_conflicts: comment_id → dataset_entry_id
op.drop_constraint(
"annotation_conflicts_comment_id_key",
"annotation_conflicts",
type_="unique",
)
op.drop_constraint(
"annotation_conflicts_comment_id_fkey",
"annotation_conflicts",
type_="foreignkey",
)
op.drop_column("annotation_conflicts", "comment_id")

op.add_column(
"annotation_conflicts",
sa.Column(
"dataset_entry_id",
sa.Uuid(),
sa.ForeignKey("dataset_entries.id"),
nullable=False,
unique=True,
),
)


def downgrade() -> None:
# Reverter: dataset_entry_id → comment_id
op.execute("DELETE FROM resolutions")
op.execute("DELETE FROM annotation_conflicts")
op.execute("DELETE FROM annotations")

# annotation_conflicts
op.drop_constraint(
"annotation_conflicts_dataset_entry_id_key",
"annotation_conflicts",
type_="unique",
)
op.drop_column("annotation_conflicts", "dataset_entry_id")
op.add_column(
"annotation_conflicts",
sa.Column(
"comment_id",
sa.Uuid(),
sa.ForeignKey("comments.id"),
nullable=False,
unique=True,
),
)

# annotations
op.drop_constraint("uq_entry_annotator", "annotations", type_="unique")
op.drop_column("annotations", "dataset_entry_id")
op.add_column(
"annotations",
sa.Column(
"comment_id",
sa.Uuid(),
sa.ForeignKey("comments.id"),
nullable=False,
),
)
op.create_unique_constraint(
"uq_comment_annotator",
"annotations",
["comment_id", "annotator_id"],
)
12 changes: 7 additions & 5 deletions backend/models/annotation.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@ class Annotation(Base):
__tablename__ = "annotations"

id: Mapped[uuid.UUID] = mapped_column(primary_key=True, default=uuid.uuid4)
comment_id: Mapped[uuid.UUID] = mapped_column(ForeignKey("comments.id"))
dataset_entry_id: Mapped[uuid.UUID] = mapped_column(
ForeignKey("dataset_entries.id")
)
annotator_id: Mapped[uuid.UUID] = mapped_column(ForeignKey("users.id"))
label: Mapped[str] = mapped_column(String(8), nullable=False)
justificativa: Mapped[str | None] = mapped_column(Text, nullable=True)
Expand All @@ -20,11 +22,11 @@ class Annotation(Base):
default=datetime.utcnow, onupdate=datetime.utcnow
)

comment: Mapped["Comment"] = relationship() # noqa: F821
dataset_entry: Mapped["DatasetEntry"] = relationship() # noqa: F821
annotator: Mapped["User"] = relationship() # noqa: F821

__table_args__ = (
UniqueConstraint("comment_id", "annotator_id", name="uq_comment_annotator"),
UniqueConstraint("dataset_entry_id", "annotator_id", name="uq_entry_annotator"),
CheckConstraint("label IN ('bot', 'humano')", name="ck_valid_label"),
)

Expand All @@ -33,8 +35,8 @@ class AnnotationConflict(Base):
__tablename__ = "annotation_conflicts"

id: Mapped[uuid.UUID] = mapped_column(primary_key=True, default=uuid.uuid4)
comment_id: Mapped[uuid.UUID] = mapped_column(
ForeignKey("comments.id"), unique=True
dataset_entry_id: Mapped[uuid.UUID] = mapped_column(
ForeignKey("dataset_entries.id"), unique=True
)
annotation_a_id: Mapped[uuid.UUID] = mapped_column(ForeignKey("annotations.id"))
annotation_b_id: Mapped[uuid.UUID] = mapped_column(ForeignKey("annotations.id"))
Expand Down
4 changes: 2 additions & 2 deletions backend/routers/annotate.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ def get_comments_endpoint(
)


# ─── Anotar comentário ──────────────────────────────────────────────────────
# ─── Anotar usuário (entry) ───────────────────────────────────────────────


@router.post("", response_model=AnnotationResult)
Expand All @@ -91,7 +91,7 @@ def annotate_endpoint(

result = upsert_annotation(
db,
payload.comment_db_id,
payload.entry_id,
current_user.id,
payload.label,
payload.justificativa,
Expand Down
8 changes: 4 additions & 4 deletions backend/routers/dashboard.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,15 @@
from database import get_db
from models.user import User
from schemas.dashboard import (
BotCommentsResponse,
BotUsersResponse,
CriteriaEffectivenessItem,
GlobalDashboardResponse,
UserDashboardResponse,
VideoDashboardResponse,
)
from services.auth import get_current_user
from services.dashboard import (
get_bot_comments,
get_bot_users,
get_criteria_effectiveness,
get_global_dashboard,
get_user_dashboard,
Expand Down Expand Up @@ -69,7 +69,7 @@ def user_endpoint(
return get_user_dashboard(db, user_id=current_user.id)


@router.get("/bots", response_model=BotCommentsResponse)
@router.get("/bots", response_model=BotUsersResponse)
def bots_endpoint(
dataset_id: str | None = Query(default=None),
video_id: str | None = Query(default=None),
Expand All @@ -84,7 +84,7 @@ def bots_endpoint(
criteria_list = (
[c.strip() for c in criteria.split(",") if c.strip()] if criteria else None
)
return get_bot_comments(
return get_bot_users(
db,
dataset_id=dataset_id,
video_id=video_id,
Expand Down
4 changes: 2 additions & 2 deletions backend/routers/review.py
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ def import_endpoint(
db: Session = Depends(get_db),
admin: User = Depends(require_admin),
):
return import_review(db, admin.id, payload.video_id, payload.comments)
return import_review(db, admin.id, payload.video_id, payload.users)


@router.post("/import-chunk", response_model=ImportChunkResponse)
Expand All @@ -166,5 +166,5 @@ def import_chunk_endpoint(
db: Session = Depends(get_db),
admin: User = Depends(require_admin),
):
result = import_review_chunk(db, admin.id, payload.comments, payload.done)
result = import_review_chunk(db, admin.id, payload.users, payload.done)
return ImportChunkResponse(**result)
27 changes: 14 additions & 13 deletions backend/schemas/annotate.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@


class AnnotationCreate(BaseModel):
comment_db_id: uuid.UUID
entry_id: uuid.UUID
label: Literal["bot", "humano"]
justificativa: str | None = None

Expand All @@ -20,7 +20,7 @@ def justificativa_required_for_bot(self):


class AnnotationImportItem(BaseModel):
comment_db_id: uuid.UUID
entry_id: uuid.UUID
label: Literal["bot", "humano"]
justificativa: str | None = None

Expand Down Expand Up @@ -54,7 +54,7 @@ class ImportChunkResponse(BaseModel):

class AnnotationResult(BaseModel):
annotation_id: uuid.UUID
comment_db_id: uuid.UUID
entry_id: uuid.UUID
label: str
conflict_created: bool

Expand All @@ -74,38 +74,39 @@ class AnnotatorAnnotation(BaseModel):
annotated_at: datetime


class CommentWithAnnotation(BaseModel):
class CommentItem(BaseModel):
"""Comentário exibido como evidência (sem anotação individual)."""

comment_db_id: uuid.UUID
text_original: str
like_count: int
reply_count: int
published_at: datetime
my_annotation: MyAnnotation | None
all_annotations: list[AnnotatorAnnotation] | None = None


class UserCommentsResponse(BaseModel):
entry_id: uuid.UUID
author_display_name: str
author_channel_id: str
comments: list[CommentWithAnnotation]
comments: list[CommentItem]
my_annotation: MyAnnotation | None
all_annotations: list[AnnotatorAnnotation] | None = None


class UserItem(BaseModel):
entry_id: uuid.UUID
author_channel_id: str
author_display_name: str
comment_count: int
my_annotated_count: int
my_pending_count: int
is_annotated_by_me: bool
my_label: str | None = None


class DatasetUsersResponse(BaseModel):
dataset_id: uuid.UUID
dataset_name: str
total_users: int
total_comments: int
annotated_comments_by_me: int
annotated_users_by_me: int
page: int
page_size: int
total_pages: int
Expand All @@ -115,7 +116,7 @@ class DatasetUsersResponse(BaseModel):
class DatasetProgress(BaseModel):
dataset_id: uuid.UUID
dataset_name: str
total_comments: int
total_users: int
annotated: int
bots: int
humans: int
Expand All @@ -134,7 +135,7 @@ class AnnotatorProgress(BaseModel):
annotator_name: str
dataset_id: uuid.UUID
dataset_name: str
total_comments: int
total_users: int
annotated: int
bots: int
humans: int
Expand Down
19 changes: 10 additions & 9 deletions backend/schemas/dashboard.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@

class GlobalSummary(BaseModel):
total_datasets: int
total_comments_annotated: int
total_comments_in_datasets: int
total_users_annotated: int
total_users_in_datasets: int
annotation_progress: float
total_bots: int
total_humans: int
Expand All @@ -37,7 +37,7 @@ class CriteriaEffectivenessItem(BaseModel):
criteria: str
group: str
total_datasets: int
total_comments_selected: int
total_users_selected: int
total_bots: int
bot_rate: float

Expand All @@ -47,7 +47,7 @@ class CriteriaEffectivenessItem(BaseModel):

class VideoSummary(BaseModel):
total_comments_collected: int
total_comments_in_datasets: int
total_users_in_datasets: int
total_annotated: int
total_bots: int
total_humans: int
Expand Down Expand Up @@ -91,7 +91,7 @@ class UserDatasetProgress(BaseModel):
dataset_id: uuid.UUID
dataset_name: str
video_id: str
total_comments: int
total_users: int
annotated_by_me: int
pending: int
percent_complete: float
Expand All @@ -111,16 +111,17 @@ class UserDashboardResponse(BaseModel):
# ── Tabela de Bots ─────────────────────────────────────────────────


class BotCommentItem(BaseModel):
class BotUserItem(BaseModel):
dataset_name: str
author_display_name: str
text_original: str
author_channel_id: str
comment_count: int
concordance_pct: int
conflict_status: str | None = None
annotators_count: int = 0
criteria: list[str] = []


class BotCommentsResponse(BaseModel):
class BotUsersResponse(BaseModel):
total: int
items: list[BotCommentItem]
items: list[BotUserItem]
9 changes: 4 additions & 5 deletions backend/schemas/data.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,11 +47,10 @@ class DataDataset(BaseModel):
class DataAnnotationProgress(BaseModel):
dataset_id: uuid.UUID
dataset_name: str
total: int
annotated: int
pending: int
total_users: int
annotated_users: int
pending_users: int
conflicts: int
conflicts_resolved: int
annotators_count: int
bots_users: int
bots_comments: int
bots: int
Loading
Loading