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())
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Loading…
Add table
Add a link
Reference in a new issue