From d01fdabd687689943c66c4125af38fc8778f04fb Mon Sep 17 00:00:00 2001 From: Codex Agent Date: Fri, 23 Oct 2026 00:00:41 +0300 Subject: [PATCH 1/9] feat(frontend): implement legal search interface --- DESIGN.md | 49 +- README.md | 9 +- backend/README.md | 4 +- backend/ingestion/minjust_current.py | 2 +- backend/search/api.py | 31 + backend/search/minjust_opensearch.py | 2 +- backend/search/review.html | 528 ++++++++++++++++-- backend/search/static/fonts/OFL-Noto-Sans.txt | 93 +++ .../search/static/fonts/OFL-Noto-Serif.txt | 93 +++ .../static/fonts/noto-sans-cyrillic-ext.woff2 | Bin 0 -> 70680 bytes .../static/fonts/noto-sans-cyrillic.woff2 | Bin 0 -> 20080 bytes .../search/static/fonts/noto-sans-latin.woff2 | Bin 0 -> 35820 bytes .../fonts/noto-serif-cyrillic-ext.woff2 | Bin 0 -> 89100 bytes .../static/fonts/noto-serif-cyrillic.woff2 | Bin 0 -> 24572 bytes .../static/fonts/noto-serif-latin.woff2 | Bin 0 -> 36756 bytes backend/test_search_api.py | 7 + docs/README.md | 2 +- docs/operations/CURRENT_STATE.md | 2 +- docs/product/frontend-pixel-perfect-plan.md | 332 +++++++++++ docs/product/visual-review/README.md | 40 ++ docs/product/visual-review/desktop-1024px.png | Bin 0 -> 168088 bytes .../visual-review/desktop-1280x800.png | Bin 0 -> 176879 bytes docs/product/visual-review/desktop-1440px.png | Bin 0 -> 192187 bytes docs/product/visual-review/mobile-390px.png | Bin 0 -> 119824 bytes docs/product/visual-review/overlay-50.png | Bin 0 -> 1143965 bytes .../visual-review/pixel-difference.png | Bin 0 -> 731760 bytes 26 files changed, 1142 insertions(+), 52 deletions(-) create mode 100644 backend/search/static/fonts/OFL-Noto-Sans.txt create mode 100644 backend/search/static/fonts/OFL-Noto-Serif.txt create mode 100644 backend/search/static/fonts/noto-sans-cyrillic-ext.woff2 create mode 100644 backend/search/static/fonts/noto-sans-cyrillic.woff2 create mode 100644 backend/search/static/fonts/noto-sans-latin.woff2 create mode 100644 backend/search/static/fonts/noto-serif-cyrillic-ext.woff2 create mode 100644 backend/search/static/fonts/noto-serif-cyrillic.woff2 create mode 100644 backend/search/static/fonts/noto-serif-latin.woff2 create mode 100644 docs/product/frontend-pixel-perfect-plan.md create mode 100644 docs/product/visual-review/README.md create mode 100644 docs/product/visual-review/desktop-1024px.png create mode 100644 docs/product/visual-review/desktop-1280x800.png create mode 100644 docs/product/visual-review/desktop-1440px.png create mode 100644 docs/product/visual-review/mobile-390px.png create mode 100644 docs/product/visual-review/overlay-50.png create mode 100644 docs/product/visual-review/pixel-difference.png diff --git a/DESIGN.md b/DESIGN.md index 8e727ab..54ac9e7 100644 --- a/DESIGN.md +++ b/DESIGN.md @@ -86,6 +86,15 @@ spacing: генеративного изображения. Название продукта — **Акылдаш**; «УКУК» из ранних концепций не используется. +Изображение определяет целевую композицию, пропорции панелей и расположение +элементов поискового экрана. Эта спецификация определяет семантические токены, +шрифты, доступность и правила поведения. Расхождения между ними фиксируются +до реализации; утверждённые токены не заменяются цветами, взятыми пипеткой +из растрового изображения. Изменения постоянных правил вносятся сюда явно. +Этапы работ, исходные измерения и критерии визуальной приёмки находятся в +[плане точного воспроизведения поискового интерфейса](docs/product/frontend-pixel-perfect-plan.md). +План не является второй дизайн-системой и не означает завершение реализации. + **Key Characteristics:** - Спокойное чтение и компактные, понятные элементы управления. @@ -162,6 +171,34 @@ spacing: Разворачивание документа и поиск внутри него реализуются только вместе с работающим поведением, а не как декоративные кнопки. +### Композиция поискового экрана + +- Первая строка desktop: знак и название, разделитель и подпись продукта + слева; подписанное поле поиска с очисткой и кнопкой «Найти» в центре; + переключатель языка интерфейса справа. Служебные действия авторизованного + пользователя не вытесняют поиск и не меняют эту иерархию. +- Вторая строка: навигация «Поиск» / «Об источниках» и удаляемые активные + фильтры. Сведения о демонстрационном режиме показываются только в нём. +- Ниже расположены три смежные панели: фильтры, результаты, документ. + Иерархия создаётся фонами и непрерывными тонкими разделителями, без + промежутков между самостоятельными карточками панелей. +- Фильтры имеют компактные группы, текстовое действие сброса, выбор органа + и пару полей даты в одной строке при достаточной ширине. Вид элемента + выбора соответствует одиночному или множественному контракту API. +- Заголовок результатов и счётчик находятся слева, сортировка справа. + Высота строк зависит от содержимого; едины отступы и типографика. + Выбранный результат имеет мягкий красный фон, тонкую красную рамку + и текстовый признак открытия. +- Панель документа начинается с контекстной навигации и разворачивания, + затем идут название, компактные метаданные, действия, поиск внутри + документа и единое поле чтения. Название документа имеет отдельную + типографическую роль, отличную от заголовков интерфейсных панелей. +- При коротком содержимом footer расположен у нижней границы окна; + длинное содержимое доступно прокруткой без перекрытия текста и управления. + Бренд находится слева, фактические версии frontend и backend справа. +- Поисковая страница не показывает инструменты оценивания лаборатории. + Общие стили и ресурсы не смешивают композицию этих двух сценариев. + Внутренняя лаборатория сохраняет свой двухпанельный сценарий: фактический порядок результатов и оценивание слева, текст выбранной редакции справа. Она не получает фильтры, аналитику или публичную навигацию исключительно @@ -176,8 +213,9 @@ spacing: горизонтальной прокрутки; широкие таблицы имеют отдельную обозначенную область прокрутки. -Проверочные размеры при реализации: **1440, 1024 и 390 px**. Это размеры -проверки, а не заранее навязанные CSS breakpoints. +Проверочные размеры при реализации: **1280×800, 1440, 1024 и 390 px**. +1280×800 — основной размер сравнения поискового экрана с референсом. +Это размеры проверки, а не заранее навязанные CSS breakpoints. ## Elevation & Depth @@ -204,6 +242,13 @@ spacing: помечается `aria-hidden`, не находится под текстом и не мешает нажатию. На телефоне орнамент сокращается либо убирается. +Поисковый desktop включает небольшой золотистый орнамент в свободной нижней +части панели фильтров. Он не перекрывает длинные списки и не резервирует +место ценой недоступности управления. Знак бренда и орнамент поставляются +как SVG-ресурсы. Favicon использует тот же знак, адаптированный для малых +размеров; проверяется читаемость 16×16 и 32×32. Иконки управления имеют +согласованные толщину линий, оптический размер и выравнивание. + ## Components Это требования к будущей реализации, а не перечень уже обновлённых компонентов. diff --git a/README.md b/README.md index 932be06..3a5f064 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,8 @@ Telegram-бот — только часть рабочего окружения ## Текущее состояние Сейчас реализованы Telegram-бот-секретарь версии `0.2.2`, backend версии -`0.12.2` и внутренний frontend лаборатории `0.1.2`. Авторизация хранится в +`0.13.0` и frontend `0.2.0` с поисковым экраном и внутренней лабораторией. +Авторизация хранится в SQLite; первый пользователь `admin` создаётся интерактивной CLI-командой. Поиск расширяет аббревиатуры организационно-правовых форм и поднимает действующий Налоговый кодекс для запросов о налоге на прибыль. @@ -18,8 +19,8 @@ SQLite; первый пользователь `admin` создаётся инт | Компонент | Версия | Состояние | |---|---:|---| | Telegram-бот | `0.2.2` | на Synology работает `0.2.1`; обновление после слияния | -| Backend | `0.12.2` | авторизация, поиск с фильтрами и ограниченный staging текущего API Минюста | -| Frontend | `0.1.2` | лаборатория поиска, личный кабинет и управление учетными записями | +| Backend | `0.13.0` | авторизация, поиск с фильтрами, ограниченный staging и статические ресурсы | +| Frontend | `0.2.0` | поиск по правовой базе, просмотр редакций и внутренняя лаборатория | | Сбор и обработка правовых данных | `0.8.0` | добавлены relevance set и оценка выдачи | | RAG и база знаний | — | ещё не созданы | @@ -62,4 +63,4 @@ python3 -m unittest discover -s tools/telegram-bot -v --- -Акылдаш · Telegram-бот v0.2.2 · Backend v0.12.2 · Frontend v0.1.2 +Акылдаш · Telegram-бот v0.2.2 · Backend v0.13.0 · Frontend v0.2.0 diff --git a/backend/README.md b/backend/README.md index 298e191..0842263 100644 --- a/backend/README.md +++ b/backend/README.md @@ -1,6 +1,6 @@ # Backend Акылдаш -Версия: `0.12.2` +Версия: `0.13.0` Первая backend-область проекта — загрузка правовых документов из официального Open Data API ЦБД Минюста Кыргызской Республики. Код расположен в @@ -296,4 +296,4 @@ PYTHONPATH=backend python3 -m search.query "ЖЧК ачуу тартиби" --la --- -Акылдаш · Backend v0.12.2 · Frontend v0.1.2 +Акылдаш · Backend v0.13.0 · Frontend v0.2.0 diff --git a/backend/ingestion/minjust_current.py b/backend/ingestion/minjust_current.py index 26d4bea..c9db9ad 100644 --- a/backend/ingestion/minjust_current.py +++ b/backend/ingestion/minjust_current.py @@ -23,7 +23,7 @@ from typing import Callable from ingestion.minjust_cbd import atomic_write, connect_manifest, json_bytes API_ROOT = "https://cbd.minjust.gov.kg/api/v1" -APP_VERSION = "0.12.2" +APP_VERSION = "0.13.0" REQUEST_CONTRACT = { "version": 2, "endpoint": "/GetEdition", diff --git a/backend/search/api.py b/backend/search/api.py index 37337ef..3362554 100644 --- a/backend/search/api.py +++ b/backend/search/api.py @@ -26,6 +26,14 @@ LANGUAGES = {"ru", "ky"} CODE = re.compile(r"^[0-9]+$") MAX_PAGE_SIZE = 100 MAX_RESULT_WINDOW = 10_000 +STATIC_ASSETS = { + "/static/fonts/noto-sans-cyrillic-ext.woff2": ("fonts/noto-sans-cyrillic-ext.woff2", "font/woff2"), + "/static/fonts/noto-sans-cyrillic.woff2": ("fonts/noto-sans-cyrillic.woff2", "font/woff2"), + "/static/fonts/noto-sans-latin.woff2": ("fonts/noto-sans-latin.woff2", "font/woff2"), + "/static/fonts/noto-serif-cyrillic-ext.woff2": ("fonts/noto-serif-cyrillic-ext.woff2", "font/woff2"), + "/static/fonts/noto-serif-cyrillic.woff2": ("fonts/noto-serif-cyrillic.woff2", "font/woff2"), + "/static/fonts/noto-serif-latin.woff2": ("fonts/noto-serif-latin.woff2", "font/woff2"), +} class ApiError(Exception): @@ -303,6 +311,17 @@ class Api: except (OSError, UnicodeError) as error: raise ApiError(500, "review interface is unavailable") from error + @staticmethod + def static_asset(route: str) -> tuple[str, bytes]: + asset = STATIC_ASSETS.get(route) + if asset is None: + raise ApiError(404, "static asset not found") + path = Path(__file__).with_name("static") / asset[0] + try: + return asset[1], path.read_bytes() + except OSError as error: + raise ApiError(500, "static asset is unavailable") from error + def handle(self, method: str, path: str, body: dict | None = None, user: dict | None = None) -> tuple[int, dict]: parsed = urllib.parse.urlsplit(path) query = urllib.parse.parse_qs(parsed.query, keep_blank_values=True) @@ -386,6 +405,18 @@ def handler(api: Api): self.end_headers() self.wfile.write(body) return + if method == "GET" and route in STATIC_ASSETS: + content_type, body = api.static_asset(route) + self.send_response(200) + self.send_header("Content-Type", content_type) + self.send_header("Cache-Control", "public, max-age=604800, immutable") + self.send_header("X-Content-Type-Options", "nosniff") + self.send_header("X-Frame-Options", "DENY") + self.send_header("Referrer-Policy", "no-referrer") + self.send_header("Content-Length", str(len(body))) + self.end_headers() + self.wfile.write(body) + return if not (method == "POST" and route in {"/auth/login", "/auth/logout"}): if not user: raise ApiError(401, "authentication required") diff --git a/backend/search/minjust_opensearch.py b/backend/search/minjust_opensearch.py index 0feda71..10b7049 100644 --- a/backend/search/minjust_opensearch.py +++ b/backend/search/minjust_opensearch.py @@ -17,7 +17,7 @@ from typing import Iterator from search.catalog import authority_codes, legal_force_code, source_code -APP_VERSION = "0.12.2" +APP_VERSION = "0.13.0" LANGUAGES = {"ru", "ky"} DEFAULT_MAPPING = Path(__file__).with_name("minjust-fragments-index.json") diff --git a/backend/search/review.html b/backend/search/review.html index d5a42f0..aa76c3e 100644 --- a/backend/search/review.html +++ b/backend/search/review.html @@ -3,8 +3,16 @@ + + Оценка поисковой выдачи · Акылдаш Перейти к результатам
-
Акылдаш
+
Акылдаш

Проверка качества поиска

Сопоставляйте запрос с текстом документов и сохраняйте оценку выдачи.

+
+
+ + + АкылдашПравовая база Кыргызстана + +
+
+
+ +

Поиск по правовой базе

-
-

-
-

Результаты

Введите запрос
  1. Начните с поискаВведите запрос, чтобы найти документы.
Страница 1
-

Документ

Выберите результат, чтобы прочитать доступную редакцию.

+

+
+

Результаты

Введите запрос
  1. Начните с поискаВведите запрос, чтобы найти документы.
Страница 1
+
ДокументВыберите результат

Документ

Выберите результат, чтобы прочитать доступную редакцию.

Содержание
+

Вход в Акылдаш

Введите учетные данные, выданные администратором.

Создайте новый пароль

Для продолжения замените временный пароль. Используйте не менее 12 символов.

Личный кабинет

Логин
Роль
Администратор
Дата создания

Изменить пароль

Новый пароль должен содержать не менее 12 символов.

Управление пользователями

Пароль — от 12 символов. Новый пользователь сменит его при первом входе. Передайте временный пароль отдельно.

    Документ

    +

    Источники документов

    Поиск выполняется по документам, подготовленным из правовых источников Кыргызской Республики. Для каждого результата доступная редакция и ссылка на официальный источник показываются в панели документа.

    Официальный источник: Централизованный банк данных правовой информации Министерства юстиции Кыргызской Республики.

    Короткое знакомство

    - +