"use client"; import { useState } from "react"; import { useMutation } from "@tanstack/react-query"; import { apiFetch } from "@/lib/api"; import { ResultPanel, ScraperPage, type ScraperPageConfig, } from "@/app/scrapers/_components/ScraperPage"; // ── Avito-specific trigger types + hooks ─────────────────────────────────── interface ScrapeAroundResp { total_fetched: number; total_inserted: number; total_updated: number; by_source: Array<{ source: string; fetched: number; inserted: number; updated: number; }>; } interface HouseTriggerResp { ok: boolean; house_url: string; counters: Record; } interface DetailTriggerResp { ok: boolean; item_id: string; updated: boolean; } interface ImvResp { ok: boolean; cache_key: string; recommended_price: number; range: [number, number]; market_count: number | null; evaluation_id: number; history_saved: number; } function useScrapeAround() { return useMutation< ScrapeAroundResp, Error, { lat: number; lon: number; radius_m: number } >({ mutationFn: (input) => apiFetch("/api/v1/admin/scrape", { method: "POST", body: JSON.stringify({ ...input, sources: ["avito"] }), }), }); } function useScrapeHouse() { return useMutation({ mutationFn: ({ house_url }) => apiFetch( `/api/v1/admin/scrape/avito-house?house_url=${encodeURIComponent(house_url)}`, { method: "POST" }, ), }); } function useScrapeDetail() { return useMutation({ mutationFn: ({ item_url }) => apiFetch( `/api/v1/admin/scrape/avito-detail?item_url=${encodeURIComponent(item_url)}`, { method: "POST" }, ), }); } function useScrapeImv() { return useMutation< ImvResp, Error, { address: string; rooms: number; area_m2: number; floor: number; floor_at_home: number; house_type: string; renovation_type: string; has_balcony: boolean; has_loggia: boolean; } >({ mutationFn: (input) => { const qs = new URLSearchParams({ address: input.address, rooms: String(input.rooms), area_m2: String(input.area_m2), floor: String(input.floor), floor_at_home: String(input.floor_at_home), house_type: input.house_type, renovation_type: input.renovation_type, has_balcony: String(input.has_balcony), has_loggia: String(input.has_loggia), }); return apiFetch( `/api/v1/admin/scrape/avito-imv?${qs.toString()}`, { method: "POST" }, ); }, }); } // ── Extra triggers ───────────────────────────────────────────────────────── function AvitoExtraTriggers() { // 1. Around-point const [lat, setLat] = useState("56.8400"); const [lon, setLon] = useState("60.6050"); const [radius, setRadius] = useState("1500"); const aroundMut = useScrapeAround(); // 2. Houses Catalog const [houseUrl, setHouseUrl] = useState( "/catalog/houses/ekaterinburg/ul_akademika_postovskogo_17a/3171365", ); const houseMut = useScrapeHouse(); // 3. Detail const [itemUrl, setItemUrl] = useState( "/ekaterinburg/kvartiry/3-k._kvartira_75_m_2026_et._7986882804", ); const detailMut = useScrapeDetail(); // 4. IMV const [imvAddress, setImvAddress] = useState( "Свердловская обл., Екатеринбург, ул. Чайковского, 66А", ); const [imvRooms, setImvRooms] = useState("2"); const [imvArea, setImvArea] = useState("42"); const [imvFloor, setImvFloor] = useState("4"); const [imvTotalFloors, setImvTotalFloors] = useState("5"); const [imvHouseType, setImvHouseType] = useState("panel"); const [imvRenovation, setImvRenovation] = useState("cosmetic"); const [imvBalcony, setImvBalcony] = useState(true); const [imvLoggia, setImvLoggia] = useState(false); const imvMut = useScrapeImv(); return ( <> {/* 1. Search around point */}

1. Search around (общий /admin/scrape)

Запускает классический cron-pipeline для одного якоря: search → save_listings. Использовать как baseline.

{ e.preventDefault(); aroundMut.mutate({ lat: parseFloat(lat), lon: parseFloat(lon), radius_m: parseInt(radius, 10), }); }} >
{/* 2. Houses Catalog */}

2. Houses Catalog enrichment

Парсит /catalog/houses/<slug>/<id> — характеристики дома, отзывы, history.

{ e.preventDefault(); houseMut.mutate({ house_url: houseUrl }); }} >
{/* 3. Detail */}

3. Detail page enrichment

Парсит /ekaterinburg/kvartiry/<...> — 30+ полей (kitchen_area, owners_count, etc). UPDATE listings WHERE source_id (listing должен уже существовать).

{ e.preventDefault(); detailMut.mutate({ item_url: itemUrl }); }} >
{/* 4. IMV evaluation */}

4. IMV evaluation (debug, без cache)

Прямой вызов Avito IMV API (2 HTTP requests). Production вызов из estimator on-demand с 24h cache; здесь — для debug.

{ e.preventDefault(); imvMut.mutate({ address: imvAddress, rooms: parseInt(imvRooms, 10), area_m2: parseFloat(imvArea), floor: parseInt(imvFloor, 10), floor_at_home: parseInt(imvTotalFloors, 10), house_type: imvHouseType, renovation_type: imvRenovation, has_balcony: imvBalcony, has_loggia: imvLoggia, }); }} >
); } // ── Page config ──────────────────────────────────────────────────────────── const AVITO_CONFIG: ScraperPageConfig = { source: "avito", activeTab: "avito", displayName: "Avito скрапер", subtitle: "Ручной запуск sub-pipeline'ов AvitoScraper v2. Для бесплатной разработки и теста после изменений.", scheduleSource: "avito_city_sweep", paramConfig: { hasDetailParams: true, hasEnrichAddress: false, defaults: { pages_per_anchor: 3, detail_top_n: 20, request_delay_sec: 7, radius_m: 1500, enrich_houses: true, }, }, delayMin: 3, delayMax: 30, }; export default function AvitoScraperPage() { return ( , }} /> ); }