Pull NAPC solicitation details

Site napc.proTask pull-solicitation-detailsVersion v3Updated Aug 5, 2026Category procurement

Search archived NAPC solicitation reports with URL filters, paginate results, resolve opaque report IDs, and extract complete solicitation records and detail-page fields. This skill was captured from a live agent session on napc.pro and publishes here verbatim, exactly as an agent receives it.

NoteSelectors and URL schemes drift as sites change. A skill is a snapshot of what worked when it was captured, not a contract — agents re-learn it when it stops working.

Purpose

Find NAPC solicitation reports matching a caller's work type, location, institution, project name, keyword, and optional posting-date range, then return complete details from each solicitation, including report number, title, agency, dates, scope, contacts, documents, and priority information.

When to Use

Use for archived or current NAPC solicitation searches when the caller supplies a keyword, locality, institution, state, work type, date range, or priority item. The site requires an authenticated session before report pages can be used.

Workflow

  1. Establish an authenticated NAPC session at https://www.napc.pro/login/ using caller-provided credentials. Submit input#username, input#userpass, and input[name='loginbtn']; never store credentials in the skill or URLs.
  2. Navigate directly to the filtered report URL. Base form: https://www.napc.pro/nsi_reports.php?formstatus=all&archive=1&formscope={keyword}&formstateprovid%5B%5D={state-code}&formworktype%5B%5D={work-type}&formage={start-date}&formbidto={end-date}&o=2&d=1&p=0 Omit unrequested filters. URL-encode {keyword} and date values. archive=1 includes archived reports, formstatus=all includes all statuses, o=2&d=1 applies the observed date ordering, and p=0 selects the first result page. For a named project, university, campus, or locality, put the search phrase in formscope rather than guessing a report identifier. Run additional searches with alternative distinctive terms when needed, then deduplicate results.
  3. For demolition/abatement searches, use formworktype%5B%5D=DEM. The observed Texas state option is formstateprovid%5B%5D=44. Additional observed work-type values are ASB, HZW, LPA, MLD, and WDM; use them only when requested or when the caller wants the broader related-work set. Multiple work types may be supplied by repeating the parameter, for example formworktype%5B%5D=DEM&formworktype%5B%5D=ASB.
  4. On each result page, run the list extractor below. Collect every reports3.php?formprjid=... URL and visible row metadata. Follow pagination by incrementing p (p=1, p=2, etc.) until a page has no report links or no new records. Deduplicate by list URL or report number. Keep requested date ranges as a client-side check because date formatting and matching may vary.
  5. Navigate directly to each collected opaque-ID report URL and run the detail extractor below. Merge list metadata with detail fields, preserve the canonical source URL, and return all matching records. Do not stop after finding one priority or keyword match.

List-page extractor (evaluate on each nsi_reports.php results page):

(() => {
  const clean = (v) => (v || "").replace(/\\s+/g, " ").trim();
  const abs = (a) => new URL(a.getAttribute("href"), location.href).href;
  const seen = new Set();
  return [...document.querySelectorAll('a[href*="reports3.php?formprjid="]')]
    .map((a) => {
      const url = abs(a);
      if (seen.has(url)) return null;
      seen.add(url);
      const row = a.closest("tr");
      const cells = row
        ? [...row.querySelectorAll("th,td")].map((x) => clean(x.innerText))
        : [];
      return {
        title: clean(a.innerText),
        url,
        bidDate: cells[0] || "",
        posted: cells[2] || "",
        workType: cells[4] || "",
        city: cells[5] || "",
        state: cells[6] || "",
        status: cells[7] || "",
        reportNum: cells[8] || "",
        rowText: clean(row?.innerText || a.innerText),
      };
    })
    .filter(Boolean);
})();

Detail-page extractor (evaluate on each reports3.php report page):

(() => {
  const clean = (v) => (v || "").replace(/\\s+/g, " ").trim();
  const result = {
    url: location.href,
    title: clean(
      document.querySelector('h1,h2,.title,[class*="title"]')?.textContent,
    ),
    fields: {},
    sections: [],
    documents: [],
  };
  for (const row of document.querySelectorAll("tr")) {
    const cells = [...row.querySelectorAll("th,td")]
      .map((x) => clean(x.textContent))
      .filter(Boolean);
    if (cells.length >= 2) result.fields[cells[0]] = cells.slice(1).join(" | ");
  }
  for (const dt of document.querySelectorAll("dt")) {
    const key = clean(dt.textContent),
      dd = dt.nextElementSibling;
    if (key && dd) result.fields[key] = clean(dd.textContent);
  }
  for (const block of document.querySelectorAll(
    'section,article,.panel,.card,[class*="section"],[class*="detail"]',
  )) {
    const text = clean(block.innerText);
    if (text && text.length > 20) result.sections.push(text);
  }
  for (const a of document.querySelectorAll("a[href]")) {
    const text = clean(a.textContent);
    const href = new URL(a.getAttribute("href"), location.href).href;
    if (
      text &&
      /download|document|attachment|spec|plan|pdf|file/i.test(text + " " + href)
    )
      result.documents.push({ text, href });
  }
  result.bodyText = clean(
    document.querySelector("main")?.innerText || document.body.innerText,
  );
  return result;
})();

Site-Specific Gotchas

  • Authentication is session-based; direct report URLs work only after login. Never place credentials in URLs, recipes, or extracted output.
  • The report search form submits to nsi_reports.php; multi-select parameters require bracket notation, encoded as formstateprovid%5B%5D and formworktype%5B%5D.
  • formscope is the direct URL keyword filter and works for project names, institutions, campuses, cities, and other distinctive terms. Search terms such as SSC, Stephenville, and Tarleton State can be tried separately when a combined phrase is too restrictive.
  • archive=1 and formstatus=all are needed when the request includes older or closed solicitations. o=2&d=1 is the observed ordering used for date-oriented results.
  • Pagination is controlled by the p query parameter. Fetch every page and deduplicate because keyword and category searches can overlap.
  • DEM is the observed option value for Demolition. Treat abatement as a requested keyword or inspect returned scope/title rather than assuming it has a separate work-type code. ASB can be combined with DEM by repeating the encoded multi-select parameter when both categories are requested.
  • Report links contain opaque formprjid identifiers. Resolve them from result-page anchors and use the returned href; never invent an ID from a title or project name.
  • Result tables contain mixed navigation and records. Restrict the list extractor to a[href*="reports3.php?formprjid="] and use the containing table row for the column mapping; review any candidate whose title or row text is not clearly a solicitation.
  • Date query values should use the format accepted by the live NAPC form; always verify returned posting and bid dates against the requested range client-side.

Expected Output

Return one record per matching solicitation with report number or identifier, title, agency/owner, location, posting and bid dates, work type, scope/description, priority status, contacts, all relevant detail fields, document/attachment URLs, and canonical source URL. Include pagination coverage, searches or filters used, and identify requested keywords or priority items found.

Call it

GET https://production-sfo.browserless.io/skills?token=TOKEN-HERE&domain=napc.pro&task=pull-solicitation-details