Compare commits

...

2 Commits

Author SHA1 Message Date
artem 90331a7036 добил до конца получения результата по ссылке
Gitea Actions Demo / build_and_push (push) Has been cancelled Details
2026-02-07 21:14:32 +03:00
artem 3baab00f80 добавил страницу шаринга
Gitea Actions Demo / build_and_push (push) Has been cancelled Details
2026-02-07 14:11:56 +03:00
12 changed files with 159 additions and 18 deletions

View File

@ -1 +1 @@
cpython-3.13.5-linux-x86_64-gnu
cpython-3.13.12-linux-x86_64-gnu

View File

@ -14,7 +14,7 @@ format:
uv run ruff format
lint:
uv run mypy ./ --explicit-package-bases;
uv run mypy ./server --explicit-package-bases;
ruff check --fix
pipinstall:

View File

@ -35,4 +35,5 @@ class ResultsView(ModelView):
"result",
"path",
"beerd",
"probability"
]

View File

@ -3,11 +3,13 @@ from dataclasses import dataclass
from typing import Annotated
import inject
from litestar import Controller, Request, Response, post
from litestar import Controller, Request, Response, get, post
from litestar.datastructures import UploadFile
from litestar.enums import RequestEncodingType
from litestar.params import Body
from litestar.response import Template
from server.modules.attachments import AtachmentService
from server.modules.recognizer import RecognizerService
@ -49,3 +51,17 @@ class BreedsController(Controller):
body = await data.read()
result = await recognizer_service.predict_cat_image(body)
return result.to_serializable()
@get("/dogs/share/{result_id:str}")
async def beerds_share(self, result_id: str) -> Template:
recognizer_service: RecognizerService = inject.instance(RecognizerService)
result = await recognizer_service.get_results(result_id)
attach_service: AtachmentService = inject.instance(AtachmentService)
attachments = await attach_service.get_info_byid(session=None, attach_id=[result.attachment_id])
return Template(
template_name="share.html",
context={
"result": result,
"attachment": attachments[0],
},
)

View File

@ -0,0 +1,29 @@
"""3baab00
Revision ID: 081eb0827a55
Revises: bebaddef3e8d
Create Date: 2026-02-07 20:46:53.971562
"""
from collections.abc import Sequence
import sqlalchemy as sa
from alembic import op
# revision identifiers, used by Alembic.
revision: str = "081eb0827a55"
down_revision: str | None = "bebaddef3e8d"
branch_labels: str | Sequence[str] | None = None
depends_on: str | Sequence[str] | None = None
def upgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.add_column("recognizer_results_beerds", sa.Column("probability", sa.Integer(), nullable=True))
# ### end Alembic commands ###
def downgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.drop_column("recognizer_results_beerds", "probability")
# ### end Alembic commands ###

View File

@ -1,7 +1,8 @@
from server.modules.descriptions.repository.repository import (
ACharactersRepository,
Breed,
CharactersRepository,
PGCharactersRepository,
)
__all__ = ("CharactersRepository", "ACharactersRepository", "PGCharactersRepository")
__all__ = ("CharactersRepository", "ACharactersRepository", "PGCharactersRepository", "Breed")

View File

@ -13,9 +13,9 @@ from sqlalchemy.orm import relationship
from server.config import get_app_config
from server.infra.db.db_mapper import mapper_registry
from server.modules.rate import domain
from server.modules.attachments.repository.attachments import Attachment
from server.modules.descriptions.repository.models import Beerds
from server.modules.rate import domain
@mapper_registry.mapped

View File

@ -1,6 +1,3 @@
from server.modules.recognizer.repository.repository import (
ARecognizerRepository,
RecognizerRepository,
)
from server.modules.recognizer.repository.repository import ARecognizerRepository, RecognizerRepository, ResultBeerds
__all__ = ("RecognizerRepository", "ARecognizerRepository")
__all__ = ("RecognizerRepository", "ARecognizerRepository", "ResultBeerds")

View File

@ -6,6 +6,7 @@ from sqlalchemy import (
Column,
DateTime,
ForeignKeyConstraint,
Integer,
String,
)
from sqlalchemy.orm import relationship
@ -62,3 +63,4 @@ class ResultBeerds(UJsonMixin):
id: str = field(metadata={"sa": Column(String(), primary_key=True, nullable=False)})
recognizer_results_id: str = field(metadata={"sa": Column(String(), nullable=False)})
beerd_id: str = field(metadata={"sa": Column(String(), nullable=False)})
probability: int = field(metadata={"sa": Column(Integer(), nullable=True)})

View File

@ -17,6 +17,12 @@ class ResultWithBeerds:
beerds: list[rm.ResultBeerds]
@dataclass
class ResultBeerds:
beerd_id: str
probability: float
class ARecognizerRepository(metaclass=ABCMeta):
@abstractmethod
async def images_dogs(self) -> dict:
@ -39,7 +45,7 @@ class ARecognizerRepository(metaclass=ABCMeta):
"""Получить **все** результаты (кэшируется)."""
@abstractmethod
async def create_result_with_beerds(self, result: rm.Results, beerd_ids: list[str]) -> None:
async def create_result_with_beerds(self, result: rm.Results, beerd_ids: list[ResultBeerds]) -> None:
"""
Создать новый результат и сразу же вставить связанные `ResultBeerds`.
@ -74,7 +80,7 @@ class RecognizerRepository(ARecognizerRepository):
data_labels = f.read()
return ujson.loads(data_labels)
async def create_result_with_beerds(self, result: rm.Results, beerd_ids: list[str]) -> None:
async def create_result_with_beerds(self, result: rm.Results, beerd_ids: list[ResultBeerds]) -> None:
"""
Создаёт запись в ``recognizer_results`` и сразу же добавляет
одну запись в ``recognizer_results_beerds`` (если передан список
@ -101,7 +107,8 @@ class RecognizerRepository(ARecognizerRepository):
{
"id": str(uuid4()),
"recognizer_results_id": result.id,
"beerd_id": beerd_id,
"beerd_id": beerd_id.beerd_id,
"probability": beerd_id.probability,
}
for beerd_id in beerd_ids
]

View File

@ -14,8 +14,8 @@ import torch
from torchvision import transforms # type: ignore
from server.modules.attachments.domains.attachments import Attachment
from server.modules.descriptions.repository import ACharactersRepository
from server.modules.recognizer.repository import ARecognizerRepository, models
from server.modules.descriptions.repository import ACharactersRepository, Breed
from server.modules.recognizer.repository import ARecognizerRepository, ResultBeerds, models
TorchModel = NewType("TorchModel", torch.nn.Module)
@ -49,6 +49,26 @@ class RecognizerResult(UJsonMixin):
uploaded_attach_id: str | None
@dataclass
class SharingBeerds(UJsonMixin):
alias: str
name: str
images: list[str]
probability: float
@dataclass
class SharingResult(UJsonMixin):
beerds: list[SharingBeerds]
attachment_id: str
@dataclass
class ResultNameBeerds:
name: str
probability: float
class RecognizerService:
__slots__ = ("_repository", "_attachment_service", "_repository_characters")
@ -68,7 +88,10 @@ class RecognizerService:
async def images_dogs(self) -> dict:
return await self._repository.images_dogs()
async def create_result(self, attachment: Attachment, user_id: str, device_id: str, beerd_names: list[str]):
async def create_result(
self, attachment: Attachment, user_id: str, device_id: str, beerd_results: list[ResultNameBeerds]
):
beerd_names = {b.name: b for b in beerd_results}
characters = await self._repository_characters.get_characters()
await self._repository.create_result_with_beerds(
models.Results(
@ -78,9 +101,33 @@ class RecognizerService:
device_id=device_id,
created_at=datetime.now(UTC),
),
[ch.id for ch in characters if ch.name in beerd_names],
[
ResultBeerds(beerd_id=ch.id, probability=beerd_names[ch.name].probability)
for ch in characters
if ch.name in beerd_names
],
)
async def get_results(self, result_id: str) -> SharingResult:
results = await self._repository.get_results()
beerds_store: dict[str, Breed] = {b.id: b for b in await self._repository_characters.get_characters()}
images_dogs = await self._repository.images_dogs()
for r in results:
if r.result.id != result_id:
continue
beers: list[SharingBeerds] = []
for beerd in r.beerds:
name = beerds_store[beerd.beerd_id].name.replace(" ", "_")
beers.append(
SharingBeerds(
alias=f"/dogs-characteristics/{beerds_store[beerd.beerd_id].alias}",
name=beerds_store[beerd.beerd_id].name,
probability=beerd.probability,
images=[f"/static/assets/dog/{name}/{i}" for i in images_dogs[name]],
)
)
return SharingResult(beerds=beers, attachment_id=r.result.attachment_id)
async def predict_dog_image(self, image: bytes, user_id: str, device_id: str | None) -> RecognizerResult:
if device_id is None:
device_id = "mobile"
@ -101,7 +148,14 @@ class RecognizerService:
)
description.setdefault(name, []).append(f"/dogs-characteristics/{name.replace(' ', '_')}")
results[probabilities] = name
asyncio.create_task(self.create_result(attachment, user_id, device_id, [results[key] for key in results]))
asyncio.create_task(
self.create_result(
attachment,
user_id,
device_id,
[ResultNameBeerds(name=results[key], probability=key*100) for key in results],
)
)
return RecognizerResult(
results=results, images=images, description=description, uploaded_attach_id=attachment.id
)

View File

@ -0,0 +1,34 @@
{% extends "base.html" %}
{% block meta %}
<meta name="description" content="Порода по фото - поделиться результатом" />
{% endblock %}
{% block title %}Результат определение породы по фото{% endblock %}
{% block content %}
<h1>Мой результат определения породы по фото</h1>
{% endblock %}
{% block form %}
<div>
<div id="upload-image">
<div id="upload-image-text">Ваше изображение:</div>
<img id="image" style="max-width: 200px;" src="/attachments{{ attachment.path }}.original.jpg">
</div>
<div id="result">
<h3 class="image-results">Результаты</h3>
{% for result in result.beerds %}
<div class="image-block"><div class="image-text">{{ result.name }} (вероятность: {{ result.probability }}%) <br><a href="{{ result.alias }}" target="_blank">Описание и фото</a></div>
<div class="gallery-container">
<div class="main-image-container">
<img src="{{ result.images[0] }}" class="main-image"></div>
<div class="thumbnails" style="display:none;">
{% for image in result.images %}
<img src="{{ image }}" class="thumbnail">
{% endfor %}
</div>
</div>
</div>
{% endfor %}
</div>
</div>
{% endblock %}