diff --git a/backend/routers/admin.py b/backend/routers/admin.py
index 299e992..558b6ed 100644
--- a/backend/routers/admin.py
+++ b/backend/routers/admin.py
@@ -1,107 +1,19 @@
from __future__ import annotations
-from io import BytesIO
-import re
-import xml.etree.ElementTree as ET
-import zipfile
-
from datetime import datetime, timezone
import logging
-from fastapi import APIRouter, Depends, File, HTTPException, Query, UploadFile
+from fastapi import APIRouter, Depends, HTTPException, Query
from backend.database import db
from backend.routers.auth import require_roles
-from backend.schemas import CaseListResponse, CaseResponse, CaseUpdate, DashboardResponse, ParseDocResponse
+from backend.schemas import CaseListResponse, CaseResponse, CaseUpdate, DashboardResponse
log = logging.getLogger('vector.admin')
router = APIRouter(prefix='/api/v1/admin', tags=['admin'], dependencies=[Depends(require_roles(['admin']))])
-def _parse_bool(value: str | None) -> bool | None:
- if value is None:
- return None
- normalized = value.strip().lower()
- if normalized in {'1', 'true', 'yes', 'y', 'да'}:
- return True
- if normalized in {'0', 'false', 'no', 'n', 'нет'}:
- return False
- return None
-
-
-def _extract_docx_text(content: bytes) -> str:
- try:
- with zipfile.ZipFile(BytesIO(content)) as archive:
- xml = archive.read('word/document.xml')
- root = ET.fromstring(xml)
- except (zipfile.BadZipFile, KeyError, ET.ParseError) as exc:
- raise ValueError('Не удалось прочитать .docx: файл повреждён или имеет неверный формат') from exc
- ns = {'w': 'http://schemas.openxmlformats.org/wordprocessingml/2006/main'}
- paragraphs: list[str] = []
- for paragraph in root.findall('.//w:body/w:p', ns):
- texts = [node.text for node in paragraph.findall('.//w:t', ns) if node.text]
- if texts:
- paragraphs.append(''.join(texts).strip())
- return '\n'.join(paragraphs).strip()
-
-
-def _extract_kv_pairs(raw_text: str) -> dict[str, str]:
- parsed: dict[str, str] = {}
- for line in raw_text.splitlines():
- if ':' not in line:
- continue
- key, value = line.split(':', 1)
- parsed[key.strip().lower()] = value.strip()
- return parsed
-
-
-def _coerce_preview(raw_text: str) -> dict:
- pairs = _extract_kv_pairs(raw_text)
- case: dict[str, object] = {}
-
- if 'age' in pairs:
- try:
- case['age'] = int(pairs['age'])
- except ValueError:
- pass
- if 'gender' in pairs:
- case['gender'] = pairs['gender']
- if 'status' in pairs:
- case['status'] = pairs['status']
- if 'found alive' in pairs:
- case['found_alive'] = _parse_bool(pairs['found alive'])
- if 'found distance km' in pairs:
- try:
- case['found_distance_km'] = float(pairs['found distance km'])
- except ValueError:
- pass
- if 'found lat' in pairs:
- try:
- case['found_lat'] = float(pairs['found lat'])
- except ValueError:
- pass
- if 'found lon' in pairs:
- try:
- case['found_lon'] = float(pairs['found lon'])
- except ValueError:
- pass
- if 'who found' in pairs:
- case['who_found'] = pairs['who found']
- if 'notes' in pairs:
- case['notes'] = pairs['notes']
- elif 'note' in pairs:
- case['notes'] = pairs['note']
-
- case_id = None
- if 'case id' in pairs:
- match = re.search(r'\d+', pairs['case id'])
- if match:
- case_id = match.group(0)
-
- return {'raw_text': raw_text, 'case_id': case_id, 'case': case}
-
-
@router.get('/cases', response_model=CaseListResponse)
def admin_cases(
page: int = Query(default=1, ge=1),
@@ -157,14 +69,3 @@ def admin_update_case(case_id: str, payload: CaseUpdate) -> dict:
@router.get('/dashboard', response_model=DashboardResponse)
def admin_dashboard() -> dict:
return db.stats()
-
-
-@router.post('/parse-doc', response_model=ParseDocResponse)
-async def admin_parse_doc(file: UploadFile = File(...)) -> dict:
- content = await file.read()
- try:
- raw_text = _extract_docx_text(content)
- except ValueError as exc:
- raise HTTPException(status_code=400, detail=str(exc)) from exc
- preview = _coerce_preview(raw_text)
- return {'filename': file.filename, 'parsed': True, 'preview': preview, 'raw_text': raw_text}
diff --git a/backend/schemas.py b/backend/schemas.py
index f2ae606..4fc59a5 100644
--- a/backend/schemas.py
+++ b/backend/schemas.py
@@ -135,13 +135,6 @@ class AnalyzeResponse(BaseModel):
case_id: str
-class ParseDocResponse(BaseModel):
- filename: str
- parsed: bool
- preview: dict[str, Any]
- raw_text: str | None = None
-
-
# ================= ЗАВЕРШЁННЫЕ ПОИСКИ (ручной ввод, без ИИ) =================
# Оператор руками вносит данные ЗАВЕРШЁННОГО поиска: параметры субъекта/среды
# (вход матмодели) + реальный исход. Цель — датасет для калибровки модели
diff --git a/frontend/src/pages/AdminDashboard.jsx b/frontend/src/pages/AdminDashboard.jsx
index 006584f..2c42e44 100644
--- a/frontend/src/pages/AdminDashboard.jsx
+++ b/frontend/src/pages/AdminDashboard.jsx
@@ -148,14 +148,6 @@ function AdminDashboard() {
const [saving, setSaving] = useState(false);
const [saveError, setSaveError] = useState('');
const [saveMessage, setSaveMessage] = useState('');
- const [uploadFile, setUploadFile] = useState(null);
- const [uploadLoading, setUploadLoading] = useState(false);
- const [uploadError, setUploadError] = useState('');
- const [uploadMessage, setUploadMessage] = useState('');
- const [parsePreview, setParsePreview] = useState(null);
- const [parseRawText, setParseRawText] = useState('');
- const [parseCaseId, setParseCaseId] = useState('');
- const [parseCaseFields, setParseCaseFields] = useState({});
const selectedCase = useMemo(
() => cases.find((item) => item.id === selectedCaseId) || null,
@@ -240,116 +232,6 @@ function AdminDashboard() {
const totalPages = Math.max(1, Math.ceil(total / PAGE_SIZE));
- const parsePreviewFields = useMemo(() => {
- if (!parsePreview) return [];
- return Object.entries(parsePreview).filter(([key]) => key !== 'case' && key !== 'raw_text');
- }, [parsePreview]);
-
- function resetParseState() {
- setUploadFile(null);
- setUploadError('');
- setUploadMessage('');
- setParsePreview(null);
- setParseRawText('');
- setParseCaseId('');
- setParseCaseFields({});
- }
-
- function handleUploadFile(event) {
- setUploadFile(event.target.files?.[0] || null);
- setUploadError('');
- setUploadMessage('');
- }
-
- async function handleUploadPreview(event) {
- event.preventDefault();
- if (!uploadFile) {
- setUploadError('Выбери .docx файл для предпросмотра');
- return;
- }
-
- setUploadLoading(true);
- setUploadError('');
- setUploadMessage('');
-
- try {
- const formData = new FormData();
- formData.append('file', uploadFile);
-
- const response = await apiFetch('/api/v1/admin/parse-doc', {
- method: 'POST',
- body: formData,
- });
-
- if (!response.ok) {
- const body = await response.json().catch(() => ({}));
- throw new Error(body.detail || `Не удалось распарсить документ (${response.status})`);
- }
-
- const payload = await response.json();
- setParsePreview(payload.preview || {});
- setParseRawText(payload.raw_text || '');
- setParseCaseId(payload.preview?.case_id || '');
- setParseCaseFields(payload.preview?.case || {});
- setUploadMessage('Документ распознан, можно проверить preview ниже');
- } catch (err) {
- setUploadError(err.message || 'Ошибка загрузки файла');
- } finally {
- setUploadLoading(false);
- }
- }
-
- function fieldFromPreview(name, fallback = '') {
- const value = parseCaseFields?.[name];
- return value === undefined || value === null || value === '' ? fallback : String(value);
- }
-
- async function handleImportPreview(event) {
- event.preventDefault();
- if (!parseCaseFields || Object.keys(parseCaseFields).length === 0) {
- setUploadError('Сначала загрузи и распарсь .docx файл');
- return;
- }
-
- setUploadLoading(true);
- setUploadError('');
- setUploadMessage('');
-
- try {
- const payload = {
- age: parseCaseFields.age ?? null,
- gender: parseCaseFields.gender ?? null,
- status: parseCaseFields.status ?? 'new',
- found_alive: parseCaseFields.found_alive ?? null,
- found_distance_km: parseCaseFields.found_distance_km ?? null,
- found_lat: parseCaseFields.found_lat ?? null,
- found_lon: parseCaseFields.found_lon ?? null,
- who_found: parseCaseFields.who_found ?? null,
- note: parseCaseFields.notes ?? null,
- };
-
- const response = await apiFetch('/api/v1/cases', {
- method: 'POST',
- body: JSON.stringify(payload),
- });
-
- if (!response.ok) {
- const body = await response.json().catch(() => ({}));
- throw new Error(body.detail || `Не удалось создать кейс (${response.status})`);
- }
-
- const created = await response.json();
- setUploadMessage(`Кейс создан: ${created.id}`);
- resetParseState();
- setSaveMessage('Импорт из .docx завершён');
- setPage(1);
- } catch (err) {
- setUploadError(err.message || 'Ошибка импорта');
- } finally {
- setUploadLoading(false);
- }
- }
-
function handleFilterChange(event) {
const { name, value } = event.target;
setPage(1);
@@ -560,71 +442,6 @@ function AdminDashboard() {
- Загрузите документ, проверьте preview и импортируйте извлечённые поля в новый кейс.Импорт отчёта .docx
- Распознанные поля
-
- {parsePreviewFields.map(([key, value]) => (
-
- Кейс preview
-
-
- Показать raw text
- {parseRawText}
-