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

Topik Threads : "Strategi SEO Terbaik untuk Klinik Kecantikan di Surabaya: Tingkatkan Pasien
                Lewat Google" (https://studiosoft.id/strategi-seo-terbaik-untuk-klinik-
                kecantikan-di-surabaya-tingkatkan-pasien-lewat-google/ — audit 28 Sep 2026:
                satu-satunya artikel HC yang MASIH hidup yang BELUM PERNAH jadi topik carousel
                seri hc; jejak Threads hanya 1x text 14 Sep 2026 16:01Z).
LinkedIn      : TEXT-only, artikel `7-strategi-digital-marketing-rumah-sakit-untuk-menambah-
                pasien-setiap-bulan` (terakhir tayang di LinkedIn SS 24 Sep 2026 01:00Z =
                satu-satunya artikel HC hidup yang TIDAK ada di antrean LI pending).
Facebook      : TEXT-only tanpa media, replies=[], artikel `jasa-digital-marketing-klinik`
                (terakhir tayang di FB SS 14 Sep 2026 02:00Z; dipilih ketimbang
                cara-klinik-kecantikan 8 Sep yang temanya bentrok dgn topik Threads hari ini).

TEMUAN: stok artikel healthcare BARU masih HABIS (artikel terbaru studiosoft.id non-healthcare)
dan 7 artikel HC seri ini MASIH 404: strategi-digital-marketing-rumah-sakit, website-rumah-
sakit-gagal-pasien, iklan-facebook-rumah-sakit, klinik-kecil-butuh-digital-marketing,
konten-edukasi-kesehatan-kepercayaan-pasien, social-media-rumah-sakit-efektif, data-analytics-
rumah-sakit-kebutuhan-pasien. Semua link artikel run ini DIVERIFIKASI HTTP 200.

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, 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 = "Strategi SEO Terbaik untuk Klinik Kecantikan di Surabaya: Tingkatkan Pasien Lewat Google"
ARTICLE_URL = ("https://studiosoft.id/strategi-seo-terbaik-untuk-klinik-kecantikan-di-surabaya-"
               "tingkatkan-pasien-lewat-google/")
LI_TITLE = "7 Strategi Digital Marketing Rumah Sakit untuk Menambah Pasien Setiap Bulan"
LI_URL = ("https://studiosoft.id/7-strategi-digital-marketing-rumah-sakit-untuk-menambah-"
          "pasien-setiap-bulan/")
FB_TITLE = "Jasa Digital Marketing Klinik: Cara Menarik Pasien Baru dari Google"
FB_URL = "https://studiosoft.id/jasa-digital-marketing-klinik/"
HASHTAG_LINE = "#studiosoft #seoklinik #klinikkecantikan #digitalmarketingklinik #seolokal"

MEDIA_FILES = [
    "ss_hc49_seoklinikcan_01_cover.jpg",
    "ss_hc49_seoklinikcan_02_kenapa.jpg",
    "ss_hc49_seoklinikcan_03_fondasi.jpg",
    "ss_hc49_seoklinikcan_04_lokal.jpg",
    "ss_hc49_seoklinikcan_05_cta.jpg",
]
SRC_PNG = os.path.join(WS, "studiosoft_hc49_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 = """Klinik kecantikan baru terus bermunculan di Surabaya, tapi yang ditemukan pasien hanya yang muncul di halaman pertama Google.

Bukan soal anggaran besar, melainkan fondasi: kata kunci lokal yang tepat, halaman layanan yang rapi, dan konten yang menjawab pertanyaan pasien.

""" + ARTICLE_URL

LI_BASE = """Rumah sakit dan klinik hari ini tidak lagi cukup mengandalkan spanduk dan brosur. Calon pasien mencari lewat Google, media sosial, dan ulasan online sebelum memutuskan berobat.

Yang menarik: banyak manajemen masih menganggap ini pekerjaan satu orang admin. Padahal tujuh strategi yang benar-benar menambah pasien tiap bulan justru menyentuh hampir semua bagian: website, konten edukasi, media sosial, reputasi, iklan terarah, kemudahan pendaftaran, sampai pengukuran hasil.

Yang paling sering dilewatkan bukan yang paling mahal. Meminta ulasan pasien yang puas, membalas kritik dengan sopan, dan merapikan halaman layanan biayanya nyaris nol, tapi efeknya langsung terasa pada jumlah pendaftaran.

Pelajaran yang kami lihat dari pendampingan klien kesehatan: yang tumbuh bukan rumah sakit dengan anggaran terbesar, melainkan yang paling konsisten merawat kepercayaan pasien di kanal digitalnya.

Tidak perlu dikerjakan sekaligus. Rapikan website dan halaman layanan dulu, lengkapi profil Google, lalu tambah konten edukasi dan ulasan secara rutin. Tiap langkah kecil menumpuk menjadi kepercayaan yang sulit ditiru pesaing.

Bacaan lengkapnya: """ + LI_URL

FB_BASE = """Jasa digital marketing klinik itu berbeda dari bisnis lain: Anda tidak menjual produk yang bisa dilihat di foto, Anda menjual kepercayaan.

Calon pasien berjalan lewat empat tahap: sadar ada keluhan, mencari solusi, mencari klinik terdekat, lalu memutuskan. Di dua tahap terakhir, Google menampilkan peta dan ulasan di atas hasil biasa. Klinik yang tidak hadir di sana kalah, meski artikelnya banyak.

Karena itu fokusnya bukan sekadar ramai posting. Yang menentukan: profil Google yang lengkap, ulasan pasien yang segar, halaman layanan yang jelas menyebut kota, tombol WhatsApp yang mudah ditemukan, dan penjelasan biaya yang transparan.

Satu hal yang paling sering terlewat: menjanjikan hasil instan. Selain melanggar etika medis, itu justru merusak kepercayaan yang paling mahal dibangun klinik.

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_ANY = re.compile(r"sembuh total|dijamin sembuh", re.I)
# Pancingan diskusi KURASI (on-topic). Bauran: Threads netral + LinkedIn opini + FB netral.
CURATED_T = {
    "netral": "Menurut Anda, dari fondasi SEO itu mana yang paling sering dilewatkan klinik? Yuk diskusi di komentar.",
    "opini": "Bold statement: penampilan di halaman pertama Google lebih menentukan pasien baru daripada besarnya anggaran iklan. Setuju atau tidak?",
    "sanjungan": "Buat klinik yang halaman dan kontennya sudah rapi di mata Google, Anda selangkah lebih maju! \U0001f44f",
}
CURATED_LI = {
    "opini": "Bold statement: meminta ulasan pasien dan membalas kritik itu lebih mendatangkan pasien daripada menambah anggaran iklan. Setuju atau tidak?",
    "netral": "Menurut Anda, dari tujuh strategi itu mana yang paling sering tertunda di rumah sakit? Yuk diskusi di komentar.",
    "sanjungan": "Buat tim yang sudah konsisten merawat kanal digital rumah sakit, Anda selangkah lebih maju! \U0001f44f",
}
CURATED_FB = {
    "netral": "Menurut Anda, apa bagian tersulit menarik kepercayaan pasien baru lewat kanal digital? Yuk diskusi di komentar.",
    "opini": "Bold statement: kejelasan biaya dan jam praktik lebih menentukan pasien menghubungi daripada banyaknya konten edukasi. Setuju atau tidak?",
    "sanjungan": "Buat klinik yang sudah rapi di pencarian lokal, 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", "klinik", ["netral", "opini"], limit=500,
                     ban=BAN_ANY, curated=CURATED_T)
LI = build_safe(LI_BASE, "linkedin", "klinik", ["opini", "netral"], ban=BAN_ANY, curated=CURATED_LI)
FB = build_safe(FB_BASE, "facebook", "klinik", ["netral", "opini"], ban=BAN_ANY, curated=CURATED_FB)

REPLY1 = ("Baca selengkapnya: %s\n\nButuh partner digital marketing untuk klinik atau rumah sakit "
          "Anda?\nwww.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 re.search(r"[\u3000-\u9fff\uff00-\uffef]", txt):
        print("  \u274c %s: ada karakter CJK" % 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_hc49_seoklinikcan_")):
            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", "-4", "-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", "-4", "-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("\n0) LINK ARTIKEL (WAJIB 200 — 7 artikel HC sudah dihapus dari website):")
    for u in (ARTICLE_URL, LI_URL, FB_URL):
        if not verify_link(u):
            print("  \u274c link tidak 200 \u2014 batal.")
            return False

    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 hc49 sudah pernah dijadwalkan?):")
    hits = scan_media(("pending", "scheduled", "draft", "success"))
    if hits:
        print("  \u26a0\ufe0f sudah ada jadwal dgn media hc49 -> 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

    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("\n6) 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("\n7) 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", 44) + 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] hc49 (RE-AIR/new-topic Threads: SEO klinik kecantikan Surabaya "
                    "\u2014 SATU-SATUNYA artikel HC yang masih hidup & BELUM PERNAH jadi topik "
                    "carousel seri hc; jejak Threads hanya 1x text 14 Sep 2026 16:01Z; slide BARU "
                    "ss_hc49_seoklinikcan_*) | %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 28 Sep: terbaru 27 "
                    "Sep bikin-konten-pakai-ai = non-healthcare) & 7 artikel HC seri ini MASIH 404 "
                    "(strategi-digital-marketing-rumah-sakit, website-rumah-sakit-gagal-pasien, "
                    "iklan-facebook-rumah-sakit, klinik-kecil-butuh-digital-marketing, konten-"
                    "edukasi-kesehatan-kepercayaan-pasien, social-media-rumah-sakit-efektif, data-"
                    "analytics-rumah-sakit-kebutuhan-pasien) -> hanya artikel terverifikasi 200 "
                    "yang dipakai. LinkedIn: 7-strategi-...-menambah-pasien (terakhir di LI SS 24 "
                    "Sep 2026 01:00Z; satu-satunya artikel HC hidup yang TIDAK di antrean LI "
                    "pending \u2014 antrean LI SS sudah penuh s/d 11 Okt). FB: jasa-digital-"
                    "marketing-klinik (terakhir di FB SS 14 Sep 2026 02:00Z; dipilih ketimbang "
                    "cara-klinik-kecantikan 8 Sep yang temanya bentrok dgn topik Threads hari "
                    "ini). 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)
