Files
iqon/scripts/iqon-generate-redirect-map.py
T
2026-05-20 17:21:42 +03:00

124 lines
3.7 KiB
Python
Executable File

#!/usr/bin/env python3
"""Generate a draft redirect map from the iqon sitemap inventory.
SPEC: seo/url-cleanup-plan-2026-05-20.md#redirect-drafts
Compliance date: 2026-05-20
Launch type: manual SEO migration planning
Registry: iqon redirect map generator
"""
from __future__ import annotations
import csv
import sys
import urllib.parse
from pathlib import Path
DIRECT_PATH_REDIRECTS = {
"/homepage/": "/",
"/hows-business/": "/",
"/industry-specific-services/": "/services-solutions/industry-specific-services/",
"/services-solutions/industry-specific-services/retail-wholesail/": "/services-solutions/industry-specific-services/retail-wholesale/",
}
RU_PREFIX = "/услуги-решения/"
EN_PREFIX = "/services-solutions/"
def normalize_path(path: str) -> str:
if not path.startswith("/"):
path = "/" + path
if not path.endswith("/"):
path += "/"
return path
def target_for(decoded_path: str) -> tuple[str, str, str]:
path = normalize_path(decoded_path)
if path in DIRECT_PATH_REDIRECTS:
return DIRECT_PATH_REDIRECTS[path], "301", "direct_alias_or_typo"
if path.startswith(RU_PREFIX):
english_path = EN_PREFIX + path[len(RU_PREFIX) :]
return normalize_path(english_path), "301", "ru_to_english_default_policy"
return "", "", "manual_review"
def read_inventory(path: Path) -> list[dict[str, str]]:
with path.open(encoding="utf-8", newline="") as handle:
return list(csv.DictReader(handle))
def write_redirect_csv(rows: list[dict[str, str]], output_path: Path) -> None:
seen: set[tuple[str, str]] = set()
output_rows: list[dict[str, str]] = []
for row in rows:
source = normalize_path(row["decoded_path"])
target, status, reason = target_for(source)
if not target:
continue
key = (source, target)
if key in seen:
continue
seen.add(key)
output_rows.append(
{
"source_path": source,
"target_path": target,
"status": status,
"reason": reason,
"source_url": row["url"],
}
)
with output_path.open("w", encoding="utf-8", newline="") as handle:
writer = csv.DictWriter(handle, fieldnames=["source_path", "target_path", "status", "reason", "source_url"])
writer.writeheader()
writer.writerows(output_rows)
def write_nginx_map(redirect_csv: Path, output_path: Path) -> None:
rows = read_inventory_csv(redirect_csv)
with output_path.open("w", encoding="utf-8") as handle:
handle.write("# IQON redirect draft generated from seo/sitemap-inventory-2026-05-20.csv\n")
handle.write("# Review before deployment. Not applied to live hosting.\n\n")
for row in rows:
source = urllib.parse.quote(row["source_path"], safe="/-._~")
target = urllib.parse.quote(row["target_path"], safe="/-._~")
escaped_source = source.strip("/")
handle.write(f"rewrite ^/{escaped_source}/?$ {target} permanent;\n")
def read_inventory_csv(path: Path) -> list[dict[str, str]]:
with path.open(encoding="utf-8", newline="") as handle:
return list(csv.DictReader(handle))
def main() -> int:
if len(sys.argv) != 4:
print(
"Usage: iqon-generate-redirect-map.py <inventory.csv> <redirects.csv> <nginx.conf>",
file=sys.stderr,
)
return 2
inventory_path = Path(sys.argv[1])
redirect_csv = Path(sys.argv[2])
nginx_conf = Path(sys.argv[3])
rows = read_inventory(inventory_path)
write_redirect_csv(rows, redirect_csv)
write_nginx_map(redirect_csv, nginx_conf)
return 0
if __name__ == "__main__":
raise SystemExit(main())