#!/usr/bin/env python3
"""StudioSoft Healthcare — run cron hc43 (22 Sep 2026 02:00 WIB / 21 Sep 19:00 UTC).

Topik Threads : "Kenapa Rumah Sakit dan Klinik Wajib Punya Strategi Digital Marketing di 2026"
                (artikel https://studiosoft.id/rs-klinik-digital-marketing-2026/ — RE-AIR:
                 audit 2.826 jadwal Repliz 21 Sep (URL-presisi) -> terakhir tayang carousel
                 Threads 27 Agu 2026/hc22, belum pernah di-re-air, kandidat paling tua;
                 nama file slide BARU ss_hc43_dmrs26_*).
LinkedIn      : TEXT-only, artikel `strategi-digital-marketing-rumah-sakit` (di LinkedIn SS
                hanya pernah ERROR 24 Agu -> belum pernah tayang).
Facebook      : TEXT-only tanpa media, replies=[], artikel `data-analytics-rumah-sakit-kebutuhan-
                pasien` (di FB SS hanya pernah ERROR 23 Agu -> belum pernah tayang).

ATURAN (FIXED 5 & 15 Sep 2026):
- HANYA THREADS SS yang boleh carousel/image. FB & LinkedIn SS: TEXT saja.
- IG SS tidak disentuh (punya desain sendiri).
- Carousel = 09:00 WIB. Threads <=500 char, hashtag huruf kecil, 0 mention, ada pancingan diskusi.
- Standar desain 16 Sep 2026: engine ss_carousel_lib.py, bg #122B63, rata kiri, blok tengah
  vertikal, tanpa separator, bullet dot, Poppins Bold 85 / Regular 36 / sub 30, auto-fit.
"""
import json, os, re, subprocess, sys
from datetime import datetime, timezone, timedelta

WS = "/home/mantapman/.hermes/profiles/atun/workspace"
sys.path.insert(0, WS)
from PIL import Image
from repliz_client import ReplizClient, ACC, IMG_DIR, IMG_BASE_URL
from diskusi_engine import tambahkan_diskusi

DRY = os.environ.get("DRY", "") == "1"
_DOCS = None
c = ReplizClient()

TITLE = "Kenapa Rumah Sakit dan Klinik Wajib Punya Strategi Digital Marketing di 2026"
ARTICLE_URL = "https://studiosoft.id/rs-klinik-digital-marketing-2026/"
LI_TITLE = "7 Strategi Digital Marketing Rumah Sakit yang Terbukti Meningkatkan Jumlah Pasien"
LI_URL = "https://studiosoft.id/strategi-digital-marketing-rumah-sakit/"
FB_TITLE = "Bagaimana Data Analytics Membantu Rumah Sakit Memahami Kebutuhan Pasien"
FB_URL = "https://studiosoft.id/data-analytics-rumah-sakit-kebutuhan-pasien/"
HASHTAG_LINE = "#studiosoft #digitalmarketingrs #rumahsakit #klinik #kesehatan"

MEDIA_FILES = [
    "ss_hc43_dmrs26_01_cover.jpg",
    "ss_hc43_dmrs26_02_data.jpg",
    "ss_hc43_dmrs26_03_risiko.jpg",
    "ss_hc43_dmrs26_04_langkah.jpg",
    "ss_hc43_dmrs26_05_cta.jpg",
]
SRC_PNG = os.path.join(WS, "studiosoft_hc43_carousel")
MEDIA_URLS = [IMG_BASE_URL.rstrip("/") + "/openclaw-img/" + f for f in MEDIA_FILES]
INGRESS = ["103.84.155.153", "103.84.155.217"]

THREADS_BASE = """Pasien baru pindah rumah dan butuh dokter spesialis terdekat. Ia buka Google, bandingkan tiga RS dari website dan review, baru booking.

Digital marketing bukan lagi pilihan, tapi keharusan. 77% pasien cari informasi RS online sebelum booking \u2014 yang tidak muncul di pencarian kehilangan pasien.

""" + ARTICLE_URL

LI_BASE = """Digital marketing rumah sakit punya tantangan khas: kontennya harus informatif sekaligus etis, dan persaingan antar RS di satu kota bisa ketat.

Dari pengalaman mendampingi RS dan klinik, ada tujuh langkah yang paling konsisten memberi hasil.

Pertama, SEO lokal. Ketika pasien mencari \u201cdokter spesialis kandungan di Surabaya\u201d, RS yang muncul di tiga besar Google Maps-lah yang dapat pasien. Kuncinya Google Business Profile: foto berkualitas, informasi lengkap, dan review yang dikelola.

Kedua, konten edukasi dokter di Instagram dan Facebook. Pasien lebih percaya RS yang rajin berbagi edukasi. Video singkat soal gejala atau mitos versus fakta, tiga sampai empat kali per minggu.

Ketiga, landing page booking online. Form yang sederhana, lalu tautannya dipasang di bio Instagram, CTA Facebook, Google Maps, dan iklan. Konversi booking bisa naik hingga 40 persen.

Keempat, iklan yang tepat sasaran. Iklan layanan kandungan diarahkan ke perempuan usia 25 sampai 40 di radius 15 km dari RS, sesuai regulasi kesehatan.

Kelima, WhatsApp chatbot untuk follow-up. Banyak pasien sudah booking tapi tidak datang karena lupa. Reminder otomatis bisa menaikkan show rate hingga 30 persen.

Keenam, Google Ads untuk layanan unggulan. Untuk layanan kompetitif seperti operasi katarak atau lasik, pasien yang sudah punya niat mencari langsung melihat iklan Anda.

Ketujuh, data analytics untuk mengukur hasil. Lacak dari mana pasien datang dan tingkat konversi booking, supaya alokasi budget jadi lebih efisien.

Mulai dari SEO lokal dan konten edukasi, ukur hasilnya, lalu tambah yang lain.

Bacaan lengkapnya: """ + LI_URL

FB_BASE = """Setiap hari RS dan klinik menghasilkan data: pendaftaran pasien, kunjungan website, interaksi sosial media, feedback pasca periksa.

Masalahnya, sebagian besar data itu tidak pernah dibaca. Padahal di dalamnya tersimpan jawaban atas pertanyaan paling penting: apa yang sebenarnya diinginkan pasien?

Contoh nyata dari klien kami. Sebuah RS di Surabaya melihat dari data Google Analytics bahwa 60 persen trafik website-nya datang dari pencarian \u201cbiaya operasi katarak\u201d. Mereka lalu menambah halaman khusus soal biaya dan prosedur operasi katarak. Booking layanan itu naik 35 persen dalam dua bulan.

Tanpa data, permintaan sebesar itu tidak akan pernah terlihat. Mereka hanya akan terus menebak.

Contoh lain: dari data sosial media, RS menemukan konten parenting dan tumbuh kembang anak paling banyak di-save dan di-share. Frekuensi konten itu dinaikkan, dan booking poli anak naik 20 persen.

Data analytics bukan laporan bulanan yang dibaca sekilas. Ia alat untuk mendengar pasien tanpa harus bertanya satu per satu. Bacaan lengkapnya: """ + FB_URL

CTA_RE = re.compile(r"komentar|pendapat|diskusi|setuju|share|pengalaman|menurut|bagaimana|apa yang|komen|tanggapan|pandangan", re.I)
BAN_RE = re.compile(r"kecantikan|skincare|jerawat|sembuh total", re.I)
# Pancingan diskusi KURASI (on-topic: sudut pandang manajemen RS/klinik). Bauran run ini:
# Threads opini + LinkedIn netral + FB netral (2 netral : 1 opini ~ 60/25).
CURATED_T = {
    "opini": "Bold statement: RS yang serius menggarap digital marketing akan mengalahkan RS besar yang masih mengandalkan brosur. Setuju atau tidak?",
    "netral": "Menurut Anda, channel digital mana yang paling cepat mendatangkan pasien baru? Yuk diskusi di komentar.",
    "sanjungan": "Buat yang pasien barunya sudah datang dari Google dan sosial media, Anda selangkah lebih maju! \U0001f44f",
}
CURATED_LI = {
    "netral": "Menurut Anda, dari tujuh strategi itu mana yang paling sulit dijalankan RS di Indonesia? Yuk diskusi di komentar.",
    "opini": "Bold statement: tanpa data analytics, budget marketing RS hanya jadi tebak-tebakan. Setuju atau tidak?",
    "sanjungan": "Buat tim RS yang ketujuh strateginya sudah jalan terukur, Anda selangkah lebih maju! \U0001f44f",
}
CURATED_FB = {
    "netral": "Menurut Anda, data apa yang paling sering dilewatkan RS padahal paling berguna? Yuk diskusi di komentar.",
    "opini": "Bold statement: data analytics itu investasi, bukan biaya tambahan. Setuju atau tidak?",
    "sanjungan": "Buat yang keputusan marketingnya sudah berbasis data, Anda selangkah lebih maju! \U0001f44f",
}


def build(base, platform, industri, tones, limit=None, ban=None, max_try=120):
    for tone in tones:
        for _ in range(max_try):
            txt = tambahkan_diskusi(base, platform=platform, industri=industri, tone=tone, force=True)
            if not CTA_RE.search(txt[-200:]):
                continue
            if limit is not None and len(txt) > limit:
                continue
            if ban is not None and ban.search(txt):
                continue
            if "..." in txt:
                continue
            return txt
    return None


def build_curated(base, curated, tones, limit=None):
    for tone in tones:
        hook = (curated or {}).get(tone)
        if not hook:
            continue
        cand = base.rstrip() + "\n\n" + hook
        if CTA_RE.search(cand[-200:]) and (limit is None or len(cand) <= limit) and "@" not in cand:
            return cand
    return None


def build_safe(base, platform, industri, tones, limit=None, ban=None, curated=None):
    txt = build_curated(base, curated, tones, limit=limit)
    if txt is not None:
        return txt
    txt = build(base, platform, industri, tones, limit=limit, ban=ban)
    if txt is not None:
        return txt
    for tone in tones:
        hook = (curated or {}).get(tone)
        if not hook:
            continue
        cand = base.rstrip() + "\n\n" + hook
        if CTA_RE.search(cand[-200:]) and (limit is None or len(cand) <= limit):
            return cand
    return None


THREADS = build_safe(THREADS_BASE, "threads", "rumah_sakit", ["opini", "netral"], limit=500, ban=BAN_RE,
                     curated=CURATED_T)
LI = build_safe(LI_BASE, "linkedin", "rumah_sakit", ["netral", "opini"], ban=BAN_RE, curated=CURATED_LI)
FB = build_safe(FB_BASE, "facebook", "rumah_sakit", ["netral", "opini"], ban=BAN_RE, curated=CURATED_FB)

REPLY1 = ("Baca selengkapnya: %s\n\nButuh partner digital marketing untuk RS atau klinik Anda?\n"
          "www.studiosoft.id" % ARTICLE_URL)
REPLY2 = HASHTAG_LINE


def check_caption(name, txt, limit=None, require_cta=True):
    ok = True
    if txt is None:
        print("  \u274c %s: gagal disusun" % name)
        return False
    if limit and len(txt) > limit:
        print("  \u274c %s: %d char > %d" % (name, len(txt), limit))
        ok = False
    m = re.findall(r"@\w+", txt)
    if m:
        print("  \u274c %s: ada mention %s" % (name, m))
        ok = False
    caps = [t for t in re.findall(r"#(\w+)", txt) if t != t.lower()]
    if caps:
        print("  \u274c %s: hashtag KAPITAL %s" % (name, caps))
        ok = False
    if "..." in txt:
        print("  \u274c %s: ada elipsis" % name)
        ok = False
    if require_cta and not CTA_RE.search(txt[-200:]):
        print("  \u274c %s: tidak ada pancingan diskusi di ekor caption" % name)
        ok = False
    print("  %s %s: %d char" % ("\u2705" if ok else "\u26a0\ufe0f", name, len(txt)))
    return ok


def get_retry(ep, tries=6):
    import time
    last = None
    for i in range(tries):
        try:
            return c.get(ep)
        except Exception as e:
            last = e
            time.sleep(3 + 3 * i)
    raise last


def all_docs():
    """Ambil semua jadwal SEKALI (cache) — dump berulang bikin run lambat/timeout."""
    global _DOCS
    if _DOCS is not None:
        return _DOCS
    docs, page = [], 1
    while True:
        d = get_retry("/public/schedule?page=%d&limit=100" % page)
        docs.extend(d.get("docs", []))
        if page >= (d.get("totalPages") or 1):
            break
        page += 1
    _DOCS = docs
    print("   (dump jadwal: %d docs)" % len(docs))
    return _DOCS


def scan_media(statuses=("pending", "scheduled", "draft")):
    hits = []
    for s in all_docs():
        if s.get("accountId") not in (ACC.STUDIOSOFT_THREADS, ACC.STUDIOSOFT_LINKEDIN,
                                      ACC.STUDIOSOFT_FB, ACC.STUDIOSOFT_IG):
            continue
        if (s.get("status") or "").lower() not in statuses:
            continue
        for m in (s.get("medias") or []):
            base = os.path.basename((m.get("url") or "").split("?")[0]).lower()
            if base in [f.lower() for f in MEDIA_FILES]:
                hits.append((s.get("accountId"), (s.get("scheduleAt") or "")[:16],
                             (s.get("title") or "")[:40], s.get("type"), s.get("status")))
                break
    return hits


def busy_by_account(aid):
    busy = set()
    for s in all_docs():
        if s.get("accountId") == aid and (s.get("status") or "").lower() in ("pending", "scheduled", "draft"):
            busy.add((s.get("scheduleAt") or "")[:19])
    return busy


def next_free_09():
    busy = busy_by_account(ACC.STUDIOSOFT_THREADS)
    now = datetime.now(timezone.utc)
    start = datetime(now.year, now.month, now.day, tzinfo=timezone.utc)
    if now.hour >= 2:
        start += timedelta(days=1)
    for day in range(0, 20):
        cand = (start + timedelta(days=day)).strftime("%Y-%m-%dT02:00:00")
        if cand not in busy:
            lab = (datetime.fromisoformat(cand) + timedelta(hours=7)).strftime("%d %b %Y 09:00 WIB")
            return cand + ".000Z", lab
    return None, None


def next_free_li():
    SLOTS = [("05:00 WIB", 22, -1), ("08:00 WIB", 1, 0), ("11:00 WIB", 4, 0), ("14:00 WIB", 7, 0),
             ("17:00 WIB", 10, 0), ("20:00 WIB", 13, 0), ("23:00 WIB", 16, 0)]
    busy = busy_by_account(ACC.STUDIOSOFT_LINKEDIN)
    now = datetime.now(timezone.utc) + timedelta(minutes=15)
    now_naive = now.replace(tzinfo=None)
    today = datetime.now(timezone.utc)
    for day in range(0, 60):
        for label, uh, shift in SLOTS:
            d0 = datetime(today.year, today.month, today.day, tzinfo=timezone.utc) + timedelta(days=day + shift)
            cand = "%sT%02d:00:00" % (d0.strftime("%Y-%m-%d"), uh)
            if datetime.fromisoformat(cand) <= now_naive:
                continue
            if cand not in busy:
                lab = (datetime.fromisoformat(cand) + timedelta(hours=7)).strftime("%d %b %Y %H:%M WIB")
                return cand + ".000Z", lab
    return None, None


def next_free_fb():
    busy = busy_by_account(ACC.STUDIOSOFT_FB)
    now = datetime.now(timezone.utc) + timedelta(minutes=30)
    now_naive = now.replace(tzinfo=None)
    today = datetime.now(timezone.utc)
    for day in range(0, 21):
        d0 = datetime(today.year, today.month, today.day, tzinfo=timezone.utc) + timedelta(days=day)
        cand = "%sT12:30:00" % d0.strftime("%Y-%m-%d")
        if datetime.fromisoformat(cand) <= now_naive:
            continue
        if cand not in busy:
            lab = (datetime.fromisoformat(cand) + timedelta(hours=7)).strftime("%d %b %Y 19:30 WIB")
            return cand + ".000Z", lab
    return None, None


def prep_media():
    ok = True
    for png in sorted(os.listdir(SRC_PNG)):
        if not (png.endswith(".png") and png.startswith("ss_hc43_dmrs26_")):
            continue
        jpg = png.replace(".png", ".jpg")
        src = Image.open(os.path.join(SRC_PNG, png))
        if src.mode != "RGB":
            src = src.convert("RGB")
        src.save(os.path.join(IMG_DIR, jpg), "JPEG", quality=85, optimize=True)
        print("  \u2705 %s -> %s %s" % (png, jpg, src.size))
    for f in MEDIA_FILES:
        try:
            im = Image.open(os.path.join(IMG_DIR, f))
            im.verify()
            im = Image.open(os.path.join(IMG_DIR, f))
            print("  \u2705 PIL %s %s %s %d bytes" % (f, im.size, im.mode,
                                                      os.path.getsize(os.path.join(IMG_DIR, f))))
        except Exception as e:
            print("  \u274c PIL %s: %s" % (f, e))
            ok = False
    return ok


def verify_urls(tries=4):
    """Ingress 103.84.155.217 kadang lambat (21-30s) -> timeout 60s + 4 percobaan."""
    ok = True
    for u in MEDIA_URLS:
        for ip in INGRESS:
            code = "000"
            for _ in range(tries):
                r = subprocess.run(["curl", "-s", "-o", "/dev/null", "-w", "%{http_code}", "--max-time", "60",
                                    "--resolve", "mantapserver.tail3947bb.ts.net:443:%s" % ip, u],
                                   capture_output=True, text=True)
                code = r.stdout.strip()
                if code == "200":
                    break
            print("  %s @%s: %s" % (u.split("/")[-1], ip, code))
            if code != "200":
                ok = False
    return ok


def verify_link(url):
    r = subprocess.run(["curl", "-sIL", "--http1.1", "--max-time", "200",
                        "-A", "Mozilla/5.0 (Windows NT 10.0; Win64; x64)", url,
                        "-o", "/dev/null", "-w", "%{http_code}"], capture_output=True, text=True)
    code = (r.stdout or "").strip()
    print("  langsung %s: %s" % (url, code))
    if code != "200":
        r2 = subprocess.run(["curl", "-s", "--max-time", "120", "-o", "/dev/null", "-w", "%{http_code}",
                             "https://r.jina.ai/" + url], capture_output=True, text=True)
        code = (r2.stdout or "").strip()
        print("  via r.jina.ai: %s" % code)
    return code == "200"


def main():
    wib = datetime.now(timezone(timedelta(hours=7)))
    print("Time WIB: %s" % wib.strftime("%Y-%m-%d %H:%M"))

    print("\n1) SLOT:")
    SLOT_UTC, SLOT_LABEL = next_free_09()
    if not SLOT_UTC:
        print("  \u274c tidak ada slot 09:00 WIB bebas \u2014 batal.")
        return False
    print("  \u2705 carousel Threads: %s (%s)" % (SLOT_LABEL, SLOT_UTC))
    LI_SLOT_UTC, LI_SLOT_LABEL = next_free_li()
    FB_SLOT_UTC, FB_SLOT_LABEL = next_free_fb()
    print("  \u2705 LinkedIn text: %s (%s)" % (LI_SLOT_LABEL, LI_SLOT_UTC))
    print("  \u2705 Facebook text (tanpa media, replies=[]): %s (%s)" % (FB_SLOT_LABEL, FB_SLOT_UTC))
    if not (LI_SLOT_UTC and FB_SLOT_UTC):
        print("  \u274c slot LI/FB tidak ditemukan \u2014 batal.")
        return False

    print("\n2) CAPTION:")
    ok = True
    ok &= check_caption("Threads", THREADS, 500)
    ok &= check_caption("LinkedIn", LI)
    ok &= check_caption("Facebook", FB)
    ok &= check_caption("Reply1", REPLY1, require_cta=False)
    ok &= check_caption("Reply2", REPLY2, require_cta=False)
    if not ok:
        print("\n\u274c caption gagal aturan \u2014 batal.")
        return False

    print("\n3) IDEMPOTENSI (media hc43 sudah pernah dijadwalkan?):")
    hits = scan_media(("pending", "scheduled", "draft", "success"))
    if hits:
        print("  \u26a0\ufe0f sudah ada jadwal dgn media hc43 -> SKIP create:")
        for h in hits:
            print("   ", h)
        return True
    print("  \u2705 belum ada")

    print("\n4) MEDIA: PNG standar baru -> JPEG openclaw-img + PIL:")
    if not prep_media():
        print("  \u274c media rusak \u2014 batal.")
        return False

    print("\n5) MEDIA JALUR INTERNET (2 ingress):")
    if not verify_urls():
        print("  \u274c ada media tidak 200 \u2014 batal.")
        return False

    print("\n6) LINK ARTIKEL:")
    for u in (ARTICLE_URL, LI_URL, FB_URL):
        if not verify_link(u):
            print("  \u274c link tidak 200 \u2014 batal.")
            return False

    if DRY:
        print("\n[DRY RUN] semua cek lolos; tidak ada create.")
        return True

    base_dt = datetime.fromisoformat(SLOT_UTC.replace("Z", "+00:00"))
    r2 = (base_dt + timedelta(minutes=1)).strftime("%Y-%m-%dT%H:%M:%S.000Z")
    r3 = (base_dt + timedelta(minutes=2)).strftime("%Y-%m-%dT%H:%M:%S.000Z")
    medias = [c.make_media(u, "image", "slide%d" % (i + 1)) for i, u in enumerate(MEDIA_URLS)]

    results = {}
    print("\n7) CREATE \u2014 THREADS (album + 2 replies):")
    try:
        alb = c.schedule_album(ACC.STUDIOSOFT_THREADS, THREADS, SLOT_UTC, medias, title=TITLE)
        pid = alb.get("scheduleId") or alb.get("_id") or alb.get("id")
        print("  album -> %s" % pid)
        c.create_schedule(ACC.STUDIOSOFT_THREADS, REPLY1, r2, type_="text", replies=[pid])
        c.create_schedule(ACC.STUDIOSOFT_THREADS, REPLY2, r3, type_="text", replies=[pid])
        print("  2 replies OK")
        results["threads"] = pid
    except Exception as e:
        print("  GAGAL %s" % e)
        results["threads"] = None

    print("\n   LINKEDIN (text-only, %s):" % LI_SLOT_LABEL)
    try:
        res = c.create_schedule(ACC.STUDIOSOFT_LINKEDIN, LI, LI_SLOT_UTC, title=LI_TITLE,
                                type_="text", medias=[], replies=[])
        print("  text -> %s" % (res.get("scheduleId") or res.get("_id") or res.get("id")))
        results["linkedin"] = res.get("scheduleId") or res.get("_id") or res.get("id")
    except Exception as e:
        print("  GAGAL %s" % e)
        results["linkedin"] = None

    print("\n   FACEBOOK (TEXT saja, TANPA media, replies=[], %s):" % FB_SLOT_LABEL)
    try:
        res = c.create_schedule(ACC.STUDIOSOFT_FB, FB, FB_SLOT_UTC, title=FB_TITLE,
                                type_="text", medias=[], replies=[])
        print("  text -> %s" % (res.get("scheduleId") or res.get("_id") or res.get("id")))
        results["facebook"] = res.get("scheduleId") or res.get("_id") or res.get("id")
    except Exception as e:
        print("  GAGAL %s" % e)
        results["facebook"] = None

    print("\n" + "=" * 55)
    ok = all(results.values())
    print("SEMUA PLATFORM TERJADWAL" if ok else "GAGAL: %s" % [k for k, v in results.items() if not v])

    if ok:
        print("\n8) VERIFIKASI (fetch ULANG dari API per jadwal):")
        for name, sid in results.items():
            d = get_retry("/public/schedule/%s" % sid)
            doc = d.get("doc") or d.get("data") or d
            print("  %s: status=%s at=%s type=%s medias=%d replies=%d" % (
                name, doc.get("status"), doc.get("scheduleAt"), doc.get("type"),
                len(doc.get("medias") or []), len(doc.get("replies") or [])))
        for h in scan_media(("pending",)):
            print("   ", h)

        st = json.loads(open(WS + "/studiosoft_healthcare_state.json").read())
        st["index"] = st.get("index", 38) + 1
        st["topic"] = TITLE
        st["last_post"] = wib.strftime("%Y-%m-%d %H:%M")
        json.dump(st, open(WS + "/studiosoft_healthcare_state.json", "w"))
        with open(WS + "/studiosoft_healthcare_log.txt", "a") as f:
            f.write("\n[%s WIB] hc43 (RE-AIR Threads: kenapa RS & klinik wajib digital marketing 2026 "
                    "\u2014 terakhir tayang carousel 27 Agu 2026/hc22, belum pernah di-re-air; slide "
                    "BARU ss_hc43_dmrs26_*) | %s | %s | Threads album %s (%s) + 2 replies | "
                    "LinkedIn TEXT \"%s\" %s (%s) | FB TEXT \"%s\" (tanpa media, replies=[]) %s | "
                    "catatan: stok artikel healthcare baru masih HABIS (WP REST 21 Sep: 25 artikel "
                    "terbaru, terbaru 20 Sep `jasa-branding-surabaya-membangun-identitas-brand-kuat` "
                    "= non-healthcare); audit 2.826 jadwal Repliz URL-presisi -> Threads: artikel ini "
                    "terakhir muncul 27 Agu 2026, kandidat re-air paling tua (belum masuk hc27-hc42); "
                    "LinkedIn: `strategi-digital-marketing-rumah-sakit` hanya pernah ERROR 24 Agu "
                    "(belum pernah tayang); FB: `data-analytics-rumah-sakit-kebutuhan-pasien` hanya "
                    "pernah ERROR 23 Agu (belum pernah tayang). PITFALL run ini: os.path.expanduser(\"~\") "
                    "di env cron = /home/mantapman/.hermes/profiles/atun/home -> WAJIB path absolut "
                    "workspace. state index -> %s.\n"
                    % (wib.strftime("%Y-%m-%d %H:%M"), TITLE, ARTICLE_URL, SLOT_LABEL,
                       SLOT_UTC[11:16] + "Z", LI_TITLE, LI_SLOT_LABEL, LI_SLOT_UTC,
                       FB_TITLE, FB_SLOT_LABEL, st["index"]))
        print("Log + state ditulis.")
    return ok


if __name__ == "__main__":
    sys.exit(0 if main() else 1)
