{"record":{"id":"65419b52d2c6b8e3","repo":"jackwener/OpenCLI","slug":"powerchina-search","errorCode":null,"errorMessage":"powerchina search","messagePattern":"powerchina search","errorType":"exception","errorClass":"EmptyResultError","httpStatus":null,"severity":"error","filePath":"clis/powerchina/search.js","lineNumber":218,"sourceCode":"        const message = cleanText(error instanceof Error ? error.message : String(error || ''));\n        if (RETRYABLE_SEARCH_ERROR_HINT.test(message)) {\n          throw new Error(`[taxonomy=relay_unavailable] site=powerchina command=search detached browser context: ${message}`);\n        }\n        throw error;\n      }\n    }\n\n    const rows = filterNavigationRows(\n      dedupeCandidates(extractedRows).map((item) => ({\n        title: cleanText(item.title),\n        url: cleanText(item.url),\n        date: normalizeDate(cleanText(item.date)),\n        contextText: cleanText(item.contextText),\n      })),\n    );\n\n    if (rows.length === 0 && extractedRows.length > 0) {\n      throw new EmptyResultError('powerchina search', 'extracted only navigation/portal rows, no bid entries matched');\n    }\n\n    if (rows.length === 0) {\n      const pageText = cleanText(await page.evaluate('document.body ? document.body.innerText : \"\"'));\n      if (/(请先登录|未登录|登录后|验证码|人机验证)/.test(pageText)) {\n        throw new AuthRequiredError(\n          'bid.powerchina.cn',\n          '[taxonomy=selector_drift] site=powerchina command=search login required or human verification',\n        );\n      }\n      if (apiFailure) {\n        throw new EmptyResultError('powerchina search', `api/dom yielded no result: ${apiFailure}`);\n      }\n    }\n\n    return toProcurementSearchRecords(rows, {\n      site: 'powerchina',\n      query,","sourceCodeStart":200,"sourceCodeEnd":236,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/powerchina/search.js#L200-L236","documentation":"An EmptyResultError thrown by the powerchina search command when the DOM extraction produced rows, but every extracted item was classified as a navigation/portal row (site chrome like 'Home', 'English', search links) rather than a real bid entry. The library throws this to signal 'we reached the page and parsed something, but zero actual bid records matched', distinguishing it from a true empty result set or an auth wall.","triggerScenarios":"Running powerchina search where extractedRows.length > 0 but filterNavigationRows removes all of them because none match PROCUREMENT_TITLE_HINT (公告/招标/采购/中标/成交/项目/notice/tender/bidding) or all match NAVIGATION_TITLE_HINT / navigation URL patterns.","commonSituations":"Site layout change on bid.powerchina.cn moving bid entries into elements the extractor no longer sees; search returning only the portal shell (no results page content); query yielding zero real hits so only header/footer links were scraped; locale/English version of the site rendering titles that don't match the title hint regex.","solutions":["Retry with a more specific Chinese keyword so the search page returns real bid entries instead of only portal navigation.","Check whether bid.powerchina.cn changed its search-results markup and update the extractor/filter hints.","Inspect the raw page (or apiFailure info) to confirm whether the query genuinely has no results.","Fall back to the API endpoint (BidAnnouncementSummary/list) path if DOM extraction keeps returning only navigation rows."],"exampleFix":"// before\nawait cli search powerchina \"home\"\n// after\nawait cli search powerchina \"输变电项目 招标公告\"","handlingStrategy":"validation","validationCode":"// validate query contains bid-relevant keywords before calling\nconst BID_HINT = /(公告|招标|采购|中标|成交|项目|notice|tender|bidding)/i;\nif (!BID_HINT.test(query)) {\n  throw new Error(`query \"${query}\" unlikely to match bid entries; add tender/procurement keywords`);\n}","typeGuard":"function hasBidResults(result) {\n  return Array.isArray(result?.rows) && result.rows.length > 0;\n}","tryCatchPattern":"try {\n  rows = await searchPowerchina(query);\n} catch (err) {\n  if (err instanceof EmptyResultError && /navigation\\/portal rows/.test(err.message)) {\n    console.warn('Only portal chrome extracted — refine query or check site markup');\n    rows = [];\n  } else throw err;\n}","preventionTips":["Use specific Chinese procurement keywords (招标公告, 中标, 采购) in queries.","Sanity-check the site in a real browser when a query that used to work returns only navigation rows.","Pin/track markup-extraction filters (PROCUREMENT_TITLE_HINT) against site changes.","Prefer the API-based extraction path when the DOM keeps yielding portal-only rows."],"tags":["empty-result","scraping","selector-drift","dom-extraction"],"backgroundTag":"empty-result-set","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}