Импорт .docx удалён: не использовался (0 кейсов), парсер не совместим с русскими спецдонесениями, LLM-распознавание вырезано

- admin.py: /parse-doc + _extract_docx_text/_extract_kv_pairs/_coerce_preview/
  _parse_bool удалены; импорты почищены.
- schemas.py: ParseDocResponse удалена.
- AdminDashboard: секция «Импорт отчёта .docx» + все upload/parse handlers
  и state удалены (компонент стал на ~160 строк короче).
This commit is contained in:
2026-09-09 23:00:13 +03:00
parent 0b5c70da73
commit a7f8879034
3 changed files with 2 additions and 291 deletions
-183
View File
@@ -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() {
</div>
</section>
<section className="admin-panel admin-upload-panel">
<div className="admin-panel-header">
<div>
<h2>Импорт отчёта .docx</h2>
<p>Загрузите документ, проверьте preview и импортируйте извлечённые поля в новый кейс.</p>
</div>
<div className="admin-upload-hint">
{parseCaseId ? `Preview case_id: ${parseCaseId}` : 'Ждём файл'}
</div>
</div>
<form className="admin-upload-form" onSubmit={handleUploadPreview}>
<div className="admin-upload-row">
<label className="admin-file-picker">
<span>Выбери .docx</span>
<input type="file" accept=".docx" onChange={handleUploadFile} />
</label>
<button type="submit" className="admin-secondary-button" disabled={uploadLoading}>
{uploadLoading ? 'Распознаю…' : 'Распознать'}
</button>
<button type="button" className="admin-primary-button" onClick={handleImportPreview} disabled={uploadLoading}>
Импортировать в кейс
</button>
</div>
</form>
{uploadError ? <div className="admin-alert admin-alert-error">{uploadError}</div> : null}
{uploadMessage ? <div className="admin-alert admin-alert-success">{uploadMessage}</div> : null}
{parsePreview ? (
<div className="admin-upload-preview">
<div className="admin-upload-grid">
<div>
<h3>Распознанные поля</h3>
<ul className="admin-preview-list">
{parsePreviewFields.map(([key, value]) => (
<li key={key}>
<strong>{key}</strong>
<span>{typeof value === 'object' ? JSON.stringify(value) : String(value)}</span>
</li>
))}
</ul>
</div>
<div>
<h3>Кейс preview</h3>
<ul className="admin-preview-list">
<li><strong>age</strong><span>{fieldFromPreview('age', '—')}</span></li>
<li><strong>gender</strong><span>{fieldFromPreview('gender', '—')}</span></li>
<li><strong>status</strong><span>{fieldFromPreview('status', 'new')}</span></li>
<li><strong>found_alive</strong><span>{fieldFromPreview('found_alive', '—')}</span></li>
<li><strong>found_distance_km</strong><span>{fieldFromPreview('found_distance_km', '—')}</span></li>
<li><strong>who_found</strong><span>{fieldFromPreview('who_found', '—')}</span></li>
<li><strong>notes</strong><span>{fieldFromPreview('notes', '—')}</span></li>
</ul>
</div>
</div>
{parseRawText ? (
<details className="admin-raw-details">
<summary>Показать raw text</summary>
<pre className="admin-raw-text">{parseRawText}</pre>
</details>
) : null}
</div>
) : null}
</section>
{error ? <div className="admin-alert admin-alert-error">{error}</div> : null}