- src/pipeline/upload.py: parse_csv/parse_html → normalize→categorize→validate→upsert - POST /api/upload (multipart) + form on /sources - low-confidence records → review queue, not analytics - +3 tests (16 passed, 1 skipped)
58 lines
3.9 KiB
HTML
58 lines
3.9 KiB
HTML
<section class="kt-ai-hero" data-align="left" style="padding-top:24px;padding-bottom:16px">
|
||
<h1 style="font-size:32px;margin:0">Источники</h1>
|
||
<p class="kt-ai-hero-sub">Стекляшка из sources.yaml + живой статус (последний успех, количество записей, ошибка).</p>
|
||
</section>
|
||
|
||
<div class="panel">
|
||
<table class="data">
|
||
<thead><tr>
|
||
<th>Ид</th><th>Название</th><th>Тир</th>
|
||
<th>Последний успех</th><th>Записей</th><th>Статус</th>
|
||
<th>Последняя ошибка</th><th>Дата</th>
|
||
</tr></thead>
|
||
<tbody>{{ SOURCES_HTML }}</tbody>
|
||
</table>
|
||
</div>
|
||
|
||
<div class="panel">
|
||
<h3>Ручная загрузка файла (Arbuz.kz, Sharyn и аналоги без API)</h3>
|
||
<p class="muted" style="font-size:13px">
|
||
CSV со столбцами <code>product, price, unit, currency, region, as_of</code> (русские/английские названия
|
||
столбцов узнаются) или HTML-таблица (имя в 1-й колонке, цена во 2-й). Запись проходит через
|
||
тот же пайплайн: нормализация → категоризация → валидация. Низкая уверенность — в очередь на проверку,
|
||
не в аналитику.
|
||
</p>
|
||
<form id="upload-form" style="display:grid;grid-template-columns:repeat(2,1fr);gap:10px;margin-top:8px">
|
||
<label><span class="muted" style="font-size:12px">Источник (id, например arbuz)</span>
|
||
<input name="source_id" required style="width:100%;height:34px;padding:0 10px;border-radius:8px;border:1px solid var(--kt-ai-border);background:var(--kt-ai-surface);font-size:14px"></label>
|
||
<label><span class="muted" style="font-size:12px">Название источника</span>
|
||
<input name="source_name" style="width:100%;height:34px;padding:0 10px;border-radius:8px;border:1px solid var(--kt-ai-border);background:var(--kt-ai-surface);font-size:14px"></label>
|
||
<label><span class="muted" style="font-size:12px">Ссылка на страницу (для прозрачности)</span>
|
||
<input name="source_url" style="width:100%;height:34px;padding:0 10px;border-radius:8px;border:1px solid var(--kt-ai-border);background:var(--kt-ai-surface);font-size:14px"></label>
|
||
<label><span class="muted" style="font-size:12px">Дата цен (YYYY-MM-DD, если в файле нет)</span>
|
||
<input name="as_of" style="width:100%;height:34px;padding:0 10px;border-radius:8px;border:1px solid var(--kt-ai-border);background:var(--kt-ai-surface);font-size:14px"></label>
|
||
<label style="grid-column:1/-1"><span class="muted" style="font-size:12px">Файл (.csv / .html)</span>
|
||
<input type="file" name="file" accept=".csv,.html,.htm,.txt" required style="width:100%;padding:6px;font-size:14px"></label>
|
||
<div style="grid-column:1/-1">
|
||
<button type="submit" class="kt-ai-btn" data-variant="primary">Загрузить и проанализировать</button>
|
||
<span id="upload-result" class="muted" style="font-size:13px;margin-left:10px"></span>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
|
||
<script>
|
||
document.getElementById('upload-form').addEventListener('submit', function(e) {
|
||
e.preventDefault();
|
||
const f = new FormData(e.target);
|
||
const out = document.getElementById('upload-result');
|
||
out.textContent = 'Загружаю…';
|
||
fetch('api/upload', { method: 'POST', body: f }).then(r => r.json()).then(res => {
|
||
if (!res.ok) { out.textContent = 'Ошибка: ' + res.error; return; }
|
||
out.innerHTML = 'Готово: записано <b>' + res.loaded + '</b>, в карантине <b>' +
|
||
res.quarantined + '</b>, пропущено ' + res.skipped +
|
||
(res.total ? ' / всего ' + res.total : '') +
|
||
' строк. Источник: ' + res.source_id;
|
||
}).catch(err => { out.textContent = 'Ошибка сети: ' + err; });
|
||
});
|
||
</script>
|