#!/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 ", 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())