Add Healthcare/NPI section to nav dropdown across all static pages
The site's pre-rendered public/**/index.html pages each embed their own copy of the Services mega-dropdown and do not read src/partials/nav.html, so the earlier nav.html-only edit never appeared. inject_healthcare_nav.py adds the canonical Healthcare block (Medicare Revalidation, Medicare Enrollment, NPI/ NPPES Services, free NPI Compliance Check) to the desktop Column 3 + mobile menu of all 80 static pages. Idempotent.
This commit is contained in:
parent
e212f20a34
commit
5cfe9702e2
82 changed files with 332 additions and 160 deletions
82
scripts/inject_healthcare_nav.py
Normal file
82
scripts/inject_healthcare_nav.py
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Inject the Healthcare nav column into the pre-rendered static pages.
|
||||
|
||||
The site is mostly static HTML under site/public/**/index.html, each carrying
|
||||
its own copy of the Services mega-dropdown (desktop + mobile). The Astro
|
||||
Base.astro layout reads src/partials/nav.html, but the static pages do NOT,
|
||||
so adding a sector to nav.html alone does not show up on those pages.
|
||||
|
||||
This injects the (canonical) Healthcare block from nav.html into every static
|
||||
page that has the dropdown but is missing Healthcare. Idempotent.
|
||||
"""
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
PUBLIC = ROOT / "site" / "public"
|
||||
NAV = ROOT / "site" / "src" / "partials" / "nav.html"
|
||||
|
||||
nav = NAV.read_text()
|
||||
|
||||
m_desk = re.search(
|
||||
r'(<p class="text-\[11px\][^>]*>Healthcare</p>.*?npi-compliance-check.*?</a>)',
|
||||
nav, re.S,
|
||||
)
|
||||
m_mob = re.search(
|
||||
r'(<p class="text-xs font-semibold[^>]*>Healthcare</p>.*?npi-compliance-check.*?</a>)',
|
||||
nav, re.S,
|
||||
)
|
||||
if not (m_desk and m_mob):
|
||||
sys.exit("Could not extract Healthcare blocks from nav.html")
|
||||
|
||||
DESKTOP_BLOCK = m_desk.group(1)
|
||||
MOBILE_BLOCK = m_mob.group(1)
|
||||
|
||||
# Desktop insertion: place the Healthcare block at the end of Column 3, right
|
||||
# before the "Form a Business" CTA that closes that column.
|
||||
DESKTOP_ANCHOR = '<a href="/order/formation" class="mt-3 block py-2 px-3 text-sm font-medium text-white bg-pw-700 hover:bg-pw-800 rounded-lg text-center transition-colors">Form a Business</a>'
|
||||
|
||||
# Mobile insertion: place Healthcare right before the mobile Corporate heading.
|
||||
MOBILE_ANCHOR = '<p class="text-xs font-semibold text-slate-500 uppercase tracking-wider px-2 pt-3">Corporate</p>'
|
||||
|
||||
|
||||
def inject(html: str) -> tuple[str, bool]:
|
||||
if "services-menu" not in html:
|
||||
return html, False # no dropdown on this page
|
||||
if "npi-compliance-check" in html:
|
||||
return html, False # already has Healthcare
|
||||
|
||||
changed = False
|
||||
|
||||
if DESKTOP_ANCHOR in html and DESKTOP_BLOCK not in html:
|
||||
html = html.replace(DESKTOP_ANCHOR, DESKTOP_BLOCK + " " + DESKTOP_ANCHOR, 1)
|
||||
changed = True
|
||||
|
||||
if MOBILE_ANCHOR in html and MOBILE_BLOCK not in html:
|
||||
html = html.replace(MOBILE_ANCHOR, MOBILE_BLOCK + " " + MOBILE_ANCHOR, 1)
|
||||
changed = True
|
||||
|
||||
return html, changed
|
||||
|
||||
|
||||
def main():
|
||||
files = sorted(PUBLIC.rglob("index.html")) + [PUBLIC / "404.html"]
|
||||
touched, skipped, nodrop = 0, 0, 0
|
||||
for f in files:
|
||||
if not f.exists():
|
||||
continue
|
||||
html = f.read_text()
|
||||
new, changed = inject(html)
|
||||
if changed:
|
||||
f.write_text(new)
|
||||
touched += 1
|
||||
elif "services-menu" not in html:
|
||||
nodrop += 1
|
||||
else:
|
||||
skipped += 1
|
||||
print(f"injected: {touched} already-had/partial: {skipped} no-dropdown: {nodrop}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
90
scripts/probe_npi_undetected.py
Normal file
90
scripts/probe_npi_undetected.py
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
"""Probe whether our undetected (patchright) browser can reach NPPES / PECOS
|
||||
and how detectable it looks. Honest, no assertions from memory — it visits
|
||||
real endpoints and a fingerprint-detection page and prints what it sees.
|
||||
|
||||
Run: python3 scripts/probe_npi_undetected.py
|
||||
"""
|
||||
import asyncio
|
||||
import sys
|
||||
|
||||
sys.path.insert(0, "scripts")
|
||||
from workers.services.telecom.undetected_browser import ( # noqa: E402
|
||||
undetected_browser, is_using_patchright,
|
||||
)
|
||||
|
||||
TARGETS = [
|
||||
# NPPES public registry UI (where NPI lookups/updates happen)
|
||||
("NPPES registry", "https://npiregistry.cms.hhs.gov/"),
|
||||
# NPPES public API (already used by our free tool — sanity check)
|
||||
("NPPES API", "https://npiregistry.cms.hhs.gov/api/?version=2.1&number=1234567893"),
|
||||
# PECOS / I&A login surface (Identity & Access)
|
||||
("PECOS portal", "https://pecos.cms.hhs.gov/pecos/login.do"),
|
||||
("I&A portal", "https://nppes.cms.hhs.gov/IAWeb/login.do"),
|
||||
]
|
||||
|
||||
# Public bot-detection fingerprint check.
|
||||
SANNYSOFT = "https://bot.sannysoft.com/"
|
||||
|
||||
|
||||
async def probe(headless: bool):
|
||||
print(f"\n{'='*60}\nbackend = {'patchright' if is_using_patchright() else 'vanilla-playwright'} | headless={headless}\n{'='*60}")
|
||||
async with undetected_browser(headless=headless) as (ctx, page):
|
||||
# 1. navigator.webdriver + a couple of fingerprint signals
|
||||
try:
|
||||
await page.goto("about:blank")
|
||||
fp = await page.evaluate("""() => ({
|
||||
webdriver: navigator.webdriver,
|
||||
plugins: navigator.plugins.length,
|
||||
languages: navigator.languages,
|
||||
chrome: typeof window.chrome,
|
||||
ua: navigator.userAgent,
|
||||
})""")
|
||||
print("fingerprint:", fp)
|
||||
except Exception as e:
|
||||
print("fingerprint eval failed:", e)
|
||||
|
||||
# 2. real target reachability
|
||||
for name, url in TARGETS:
|
||||
try:
|
||||
resp = await page.goto(url, wait_until="domcontentloaded", timeout=30000)
|
||||
status = resp.status if resp else "?"
|
||||
title = await page.title()
|
||||
body = (await page.content())[:400].lower()
|
||||
blocked = any(w in body for w in [
|
||||
"access denied", "are you a human", "captcha", "blocked",
|
||||
"incapsula", "akamai", "unusual traffic", "request unsuccessful",
|
||||
])
|
||||
print(f" [{status}] {name:14} blocked={blocked} title={title[:60]!r}")
|
||||
except Exception as e:
|
||||
print(f" [ERR] {name:14} {type(e).__name__}: {str(e)[:80]}")
|
||||
|
||||
# 3. sannysoft fingerprint scorecard (count red FAILs)
|
||||
try:
|
||||
await page.goto(SANNYSOFT, wait_until="networkidle", timeout=30000)
|
||||
await asyncio.sleep(2)
|
||||
fails = await page.evaluate("""() => {
|
||||
const rows = [...document.querySelectorAll('tr')];
|
||||
const bad = [];
|
||||
for (const r of rows) {
|
||||
const cls = r.className || '';
|
||||
const txt = r.innerText.replace(/\\s+/g,' ').trim();
|
||||
if (/fail|warn/i.test(cls)) bad.push(txt.slice(0,80));
|
||||
}
|
||||
return bad;
|
||||
}""")
|
||||
if fails:
|
||||
print(f" sannysoft FAIL/WARN rows ({len(fails)}):")
|
||||
for f in fails:
|
||||
print(f" - {f}")
|
||||
else:
|
||||
print(" sannysoft: no FAIL/WARN rows detected (clean)")
|
||||
except Exception as e:
|
||||
print(" sannysoft check failed:", e)
|
||||
|
||||
|
||||
async def main():
|
||||
await probe(headless=True)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
Loading…
Add table
Add a link
Reference in a new issue