#!/usr/bin/env python3
"""StudioSoft Healthcare — run cron hc52 (1 Okt 2026 02:00 WIB / 30 Sep 19:00 UTC).

Topik Threads : "Pembuatan Website Rumah Sakit: Fitur Wajib dan Vendor"
                https://studiosoft.id/pembuatan-website-rumah-sakit/
                ARTIKEL BARU (publish 30 Sep 2026 08:00Z, WP id 9158) — pertama sejak 13 Sep 2026.
                Audit hc52_audit.py: jejak Threads/LinkedIn/Facebook SS untuk artikel ini = 0
                di ketiga platform (belum pernah dipakai). Slide BARU ss_hc52_webrs_*.
LinkedIn      : TEXT-only, artikel YANG SAMA (baru, belum ada di antrean LI pending) —
                BUKAN SKIP seperti hc50/hc51, karena antrean LI penuh dengan 11 artikel HC lama
                dan artikel baru ini belum masuk antrean sama sekali.
Facebook      : TEXT-only tanpa media, replies=[], artikel `7-aplikasi-mental-health-anak-usia-
                4-10-tahun-yang-harus-dicoba-orang-tua` (terakhir tayang di FB SS 14 Sep 2026
                07:00Z => jejak FB paling tua di antara artikel HC hidup).

TEMUAN: 7 artikel HC lama 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 sebelum create.

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
import os
import re
import subprocess
import sys
from datetime import datetime, timedelta, timezone

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 = "Pembuatan Website Rumah Sakit: Fitur Wajib dan Vendor"
ARTICLE_URL = "https://studiosoft.id/pembuatan-website-rumah-sakit/"
LI_TITLE = "Pembuatan Website Rumah Sakit: Fitur Wajib dan Vendor"
LI_URL = ARTICLE_URL
FB_TITLE = "7 Aplikasi Mental Health Anak Usia 4-10 Tahun yang Harus Dicoba Orang Tua"
FB_URL = ("https://studiosoft.id/7-aplikasi-mental-health-anak-usia-4-10-tahun-yang-harus-"
          "dicoba-orang-tua/")
HASHTAG_LINE = "#studiosoft #websiterumahsakit #digitalmarketingrs #rumahsakit #kesehatan"

MEDIA_FILES = [
    "ss_hc52_webrs_01_cover.jpg",
    "ss_hc52_webrs_02_kenapa.jpg",
    "ss_hc52_webrs_03_struktur.jpg",
    "ss_hc52_webrs_04_fitur.jpg",
    "ss_hc52_webrs_05_cta.jpg",
]
SRC_PNG = os.path.join(WS, "studiosoft_hc52_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 yang butuh layanan rumah sakit biasanya sedang tidak tenang. Ia membuka ponsel untuk mencari jadwal dokter, nomor IGD, atau alamat, dan semuanya harus ketemu dalam hitungan detik.

Kalau informasi itu salah tayang atau sulit ditemukan, orang datang sia-sia. Website rumah sakit bukan pajangan, tapi pintu masuk layanan.

""" + ARTICLE_URL

LI_BASE = """Beberapa tahun terakhir saya ikut menggarap website untuk klien di sektor kesehatan. Pelajaran pertama yang saya dapat bukan soal desain, melainkan soal siapa yang membuka halaman itu.

Orang datang ke website rumah sakit hampir selalu dalam kondisi tidak tenang. Ada yang mencari jadwal dokter, ada yang memastikan layanan gawat darurat tersedia, ada yang sekadar butuh alamat. Mereka tidak datang untuk membaca profil perusahaan.

Dari situ, tiga hal membuat website rumah sakit berbeda. Penggunanya sedang terburu-buru. Isinya menyangkut keselamatan, sehingga jam praktik yang salah tayang atau nomor telepon yang mati bukan sekadar tidak nyaman. Dan aturannya lebih ketat, karena promosi layanan kesehatan tidak bebas seperti menjual barang.

Yang sering terlewat justru hal paling dasar: struktur halaman. Profil dan izin operasional, daftar layanan dan poliklinik, jadwal dokter yang mudah diperbarui, informasi IGD yang bisa diklik langsung, alur pendaftaran, sampai penjelasan biaya dan asuransi. Kalau fondasi ini belum lengkap, mempercantik tampilan hanya menutupi masalah.

Sisi vendor juga menentukan apakah proyeknya bertahan lama. Dua pertanyaan yang saya sarankan diajukan sejak awal: siapa yang menangani isi konten dan siapa yang memverifikasi kebenaran medisnya, lalu atas nama siapa domain serta hosting terdaftar. Website yang sehat adalah website yang bisa dirawat rumah sakit sendiri, bukan yang bergantung pada vendor selamanya.

""" + LI_URL

FB_BASE = """Kesehatan mental anak jarang dibicarakan sampai masalahnya terlihat: anak jadi mudah marah, sulit tidur, atau menarik diri.

Yang sering saya amati, orang tua sebenarnya mau membantu, tapi bingung mulai dari mana. Anak usia 4 sampai 10 tahun belum selalu bisa bilang apa yang dia rasakan. Yang keluar justru tantrum, atau tangis tanpa sebab yang jelas.

Ada jalan yang cukup ramah untuk dicoba: aplikasi. Bukan untuk menggantikan pendampingan orang tua, tapi supaya anak punya cara bercerita: lewat cerita, permainan, dan latihan napas yang ringan. Mulai dari satu aplikasi saja, dampingi 10 sampai 15 menit, lalu tanyakan bagaimana perasaannya setelah dipakai.

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)
CURATED_T = {
    "netral": "Menurut Anda, bagian mana dari website rumah sakit yang paling sering terlewat? Yuk diskusi di komentar.",
    "opini": "Bold statement: website rumah sakit yang tidak bisa dirawat sendiri oleh tim internal akan cepat usang. Setuju atau tidak?",
    "sanjungan": "Buat tim rumah sakit yang websitenya sudah rapi dan informatif, Anda selangkah lebih maju! \U0001f44f",
}
CURATED_LI = {
    "netral": "Menurut Anda, apa yang paling menentukan: tampilannya, atau kemudahan tim internal merawat isinya? Yuk diskusi di komentar.",
    "opini": "Bold statement: website rumah sakit yang bagus bukan yang paling banyak animasinya, tapi yang paling cepat membuat pasien menemukan jawaban. Setuju atau tidak?",
}
CURATED_FB = {
    "netral": "Menurut Anda, aplikasi semacam ini membantu atau justru menambah waktu layar anak? Yuk diskusi di komentar.",
    "opini": "Bold statement: yang membuat anak tumbuh sehat secara emosional bukan aplikasinya, melainkan kebiasaan orang tua menemaninya. Setuju atau tidak?",
}


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", "kesehatan", ["netral", "opini"], limit=500,
                     ban=BAN_ANY, curated=CURATED_T)
LI = build_safe(LI_BASE, "linkedin", "rumah_sakit", ["netral", "opini"], ban=BAN_ANY,
                curated=CURATED_LI)
FB = build_safe(FB_BASE, "facebook", "kesehatan", ["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():
    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_hc52_webrs_")):
            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 lama sudah dihapus dari website):")
    for u in (ARTICLE_URL, FB_URL):
        if not verify_link(u):
            print("  \u274c link tidak 200 - 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 - batal.")
        return False
    print("  \u2705 carousel Threads: %s (%s)" % (SLOT_LABEL, SLOT_UTC))
    LI_SLOT_UTC, LI_SLOT_LABEL = next_free_li()
    if not LI_SLOT_UTC:
        print("  \u274c tidak ada slot LinkedIn bebas - batal.")
        return False
    print("  \u2705 LinkedIn text: %s (%s)" % (LI_SLOT_LABEL, LI_SLOT_UTC))
    FB_SLOT_UTC, FB_SLOT_LABEL = next_free_fb()
    if not FB_SLOT_UTC:
        print("  \u274c slot FB tidak ditemukan - batal.")
        return False
    print("  \u2705 Facebook text (tanpa media, replies=[]): %s (%s)" % (FB_SLOT_LABEL, FB_SLOT_UTC))

    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 - batal.")
        return False

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

    print("\n5) JALUR INTERNET (2 ingress):")
    if not verify_urls():
        print("  \u274c ada media tidak 200 - 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 - 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=[])
        sid = res.get("scheduleId") or res.get("_id") or res.get("id")
        print("  text -> %s" % sid)
        results["linkedin"] = sid
    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=[])
        sid = res.get("scheduleId") or res.get("_id") or res.get("id")
        print("  text -> %s" % sid)
        results["facebook"] = sid
    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", 47) + 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] hc52 (TOPIK BARU Threads: artikel healthcare BARU pertama sejak "
                    "13 Sep 2026 `pembuatan-website-rumah-sakit` (publish 30 Sep 2026 08:00Z, WP id "
                    "9158) - jejak Threads/LinkedIn/FB SS = 0 di ketiga platform; slide BARU "
                    "ss_hc52_webrs_*) | %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 KEMBALI ADA (sejak 13 Sep habis) & 7 "
                    "artikel HC lama 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 TIDAK SKIP (beda dari hc50/"
                    "hc51): artikel baru ini belum ada di antrean LI pending (antrean penuh s/d 11 "
                    "Okt 04:00Z). FB: %s (terakhir di FB SS 14 Sep 2026 07:00Z = jejak FB paling "
                    "tua di antara artikel HC hidup; artikel ini topik carousel Threads hc51 yang "
                    "tayang 1 Okt 09:00 WIB, caption FB dibuat angle berbeda). 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[11:16] + "Z",
                       FB_TITLE, FB_SLOT_LABEL, FB_TITLE, st["index"]))
        print("Log + state ditulis.")
    return ok


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