================================================================================ INSTAGRAM COMMENT HEALTH REPORT — CLAUDE COWORK PROMPT v6 8.5" × 11" PDF | Post Thumbnails + Clickable Links | Print & Email Ready ================================================================================ BEFORE YOU RUN THIS: • Make sure you are logged into Instagram in Chrome • If Instagram shows a login wall at any point, stop and log in manually FILL IN THESE TWO FIELDS: Instagram Handle: [PASTE HANDLE — no @ symbol, e.g. connexuschurch] Weekend Attendance: [PASTE NUMBER — e.g. 1800] ================================================================================ PHASE 1 — INSTAGRAM: COLLECT TOP POST DATA + THUMBNAILS ================================================================================ This phase navigates Instagram directly to collect post URLs, stats, and screenshot thumbnails for the top posts. Instagram must be logged in. -------------------------------------------------------------------------------- STEP 1A — RESIZE BROWSER FOR CONSISTENT SCREENSHOTS -------------------------------------------------------------------------------- Resize the browser window to exactly 1400 × 900 pixels: Use the resize_window tool: width=1400, height=900 This ensures consistent viewport coordinates for every screenshot crop. -------------------------------------------------------------------------------- STEP 1B — NAVIGATE TO THE INSTAGRAM PROFILE -------------------------------------------------------------------------------- Navigate to: https://www.instagram.com/[HANDLE] Wait 5 seconds. You should see the profile grid (3-column post thumbnails). If you see a login wall, stop and tell the user to log into Instagram first. Dismiss any "Turn on notifications" popup if it appears (click Not Now). Take a screenshot to confirm the grid is visible. -------------------------------------------------------------------------------- STEP 1C — COLLECT DATA FROM 12 MOST RECENT POSTS -------------------------------------------------------------------------------- For each of the 12 most recent posts (working left to right, top to bottom): 1. Click the post thumbnail from the grid 2. Wait 3 seconds for the post to load 3. Record from the browser: URL: window.location.href (e.g. https://www.instagram.com/p/ABC123/) Date: document.querySelector('time[datetime]')?.getAttribute('datetime') Likes: Parse from "Liked by X and N others" → total = N + 1 OR if shown as plain number, use that Comments: Run this JS to get comment count: const viewAll = Array.from(document.querySelectorAll('span')) .find(s => s.innerText.match(/^View all (\d+) comments/)); const m = viewAll ? viewAll.innerText.match(/\d+/) : null; m ? m[0] : '0'; Type: Check for video element → 'video', else → 'image' 4. SCREENSHOT THE POST IMAGE: - Run this JS to get the media element coordinates: const mediaEl = document.querySelector('article img') || document.querySelector('article video'); const r = mediaEl ? mediaEl.getBoundingClientRect() : null; r ? JSON.stringify({x:Math.round(r.x), y:Math.round(r.y), w:Math.round(r.width), h:Math.round(r.height)}) : 'none'; - Take a screenshot (save_to_disk: true), naming it post_[N].png - Record the media coordinates {x, y, w, h} alongside the screenshot ID 5. Press Escape or click X to close the post and return to the grid 6. Wait 2 seconds before clicking the next post IMPORTANT PACING: Wait 2-3 seconds between each post click. If Instagram shows a rate limit or error, wait 10 seconds and continue. After all 12 posts, you have a table like: Post | URL | Likes | Comments | Date | SS_ID | Coords 1 | https://www.instagram.com/p/ABC123/ | 127 | 3 | 2026-04-03 | ss_xxxxxx | {x,y,w,h} 2 | ... | ... | ... | ... | ... | ... ... (12 rows total) -------------------------------------------------------------------------------- STEP 1D — IDENTIFY TOP 3 BY LIKES AND TOP 3 BY COMMENTS -------------------------------------------------------------------------------- Sort your 12-post table: - By likes descending → top_liked[0], top_liked[1], top_liked[2] - By comments descending → top_commented[0], top_commented[1], top_commented[2] A post can appear in both lists — that's fine. Record for each of these up to 6 posts: - post_url (the instagram.com/p/... URL) - likes (number) - comments (number) - date (formatted as "Apr 03, 2026") - screenshot_id (the ss_xxxxx ID from Step 1C) - coords ({x, y, w, h} from Step 1C) -------------------------------------------------------------------------------- STEP 1E — CROP POST THUMBNAILS FROM SCREENSHOTS -------------------------------------------------------------------------------- For each of the top 6 posts (3 liked + 3 commented), crop the post image from its saved screenshot using Python: from PIL import Image import os def crop_post_thumb(screenshot_path, coords, out_path, size=(180, 180)): """ Crop the post image from a full browser screenshot. coords = {x, y, w, h} in viewport pixels from getBoundingClientRect() The screenshot is JPEG from Cowork at ~1400x900 resolution. """ try: ss = Image.open(screenshot_path).convert("RGB") ss_w, ss_h = ss.size # Instagram posts render as portrait rectangles. # Crop just the image portion (exclude caption sidebar). # For a square/portrait crop, take full width and proportional height. x1 = max(0, coords['x']) y1 = max(0, coords['y']) x2 = min(ss_w, coords['x'] + coords['w']) y2 = min(ss_h, coords['y'] + coords['h']) cropped = ss.crop((x1, y1, x2, y2)) # Resize to uniform thumbnail size thumb = cropped.resize(size, Image.LANCZOS) thumb.save(out_path, "JPEG", quality=85) return out_path except Exception as e: print(f"Crop failed for {out_path}: {e}") return None # Cowork saves screenshots to a temp path — find them: # The screenshot IDs (ss_xxxxx) correspond to files in Cowork's temp dir. # Cowork will know the actual paths — use the paths it returns from save_to_disk. # For each top post, crop and save to /tmp/: # liked_thumb_1.jpg, liked_thumb_2.jpg, liked_thumb_3.jpg # commented_thumb_1.jpg, commented_thumb_2.jpg, commented_thumb_3.jpg # Example (replace with actual paths and coords): posts_to_crop = [ # (screenshot_path, coords_dict, output_path) ("/path/to/post_1.png", {"x": 225, "y": 24, "w": 414, "h": 517}, "/tmp/liked_thumb_1.jpg"), ("/path/to/post_2.png", {"x": 225, "y": 24, "w": 414, "h": 517}, "/tmp/liked_thumb_2.jpg"), # ... etc ] for ss_path, coords, out_path in posts_to_crop: result = crop_post_thumb(ss_path, coords, out_path) print(f"Saved: {result}" if result else f"Failed: {out_path}") After running, verify each /tmp/liked_thumb_*.jpg and /tmp/commented_thumb_*.jpg exists. If a crop fails, set that thumbnail path to None — the PDF will use a clean placeholder with the stat numbers instead. ================================================================================ PHASE 2 — INFLACT: COLLECT ACCOUNT-LEVEL ANALYTICS ================================================================================ Inflact provides engagement rate, avg comments, sentiment, and hashtag data that Instagram's own UI doesn't show directly. -------------------------------------------------------------------------------- STEP 2A — NAVIGATE TO INFLACT -------------------------------------------------------------------------------- Navigate to: https://inflact.com/tools/profile-analyzer/ Wait 4 seconds. Dismiss any cookie/consent banner (click Accept or Close). Set the search input and submit: const input = document.querySelector('input[enterkeyhint="search"]'); input.value = '[HANDLE]'; input.dispatchEvent(new Event('input', { bubbles: true })); input.dispatchEvent(new Event('change', { bubbles: true })); document.querySelector('button[type="submit"]').click(); Wait 10 seconds. URL should become: https://inflact.com/tools/profile-analyzer/?profile=[HANDLE] -------------------------------------------------------------------------------- STEP 2B — EXTRACT ACCOUNT ANALYTICS FROM INFLACT -------------------------------------------------------------------------------- Run this JavaScript and record all values: const t = document.body.innerText; const g = (label) => { const m = t.match(new RegExp(label + '[\\s\\S]{0,30}?([\\d,.]+%?)')); return m ? m[1].trim() : 'N/A'; }; const nameEl = document.querySelector('[class*="ProfileAnalyzerProfile-module__name"]'); const sentPos = (t.match(/positive\n([\d.]+)/i)||[])[1] || '0'; const sentNeu = (t.match(/neutral\n([\d.]+)/i)||[])[1] || '0'; const sentNeg = (t.match(/negative\n([\d.]+)/i)||[])[1] || '0'; const hashMatches = (t.match(/#\w+/g) || []).slice(0, 8); const capSection = t.match( /Top caption words[\s\S]*?From the last 100 posts\n([\s\S]*?)Semantic/i ); const capWords = capSection ? capSection[1].trim().split('\n') .filter(l => l.trim() && !l.match(/^\d+$/) && l.trim().length > 1) .slice(0, 8) : []; const timeMatch = t.match(/Most popular post time\n(\w+) \| (\d+:\d+)/); JSON.stringify({ churchName: nameEl ? nameEl.innerText.trim() : '[HANDLE]', engagement: g('Engagement'), followers: g('Followers'), uploads: g('Uploads'), avgLikes: g('Avg\\. likes'), avgComments: g('Avg\\. comments'), sentPos, sentNeu, sentNeg, topHashtags: hashMatches, topWords: capWords, bestDay: timeMatch ? timeMatch[1] : 'N/A', bestTime: timeMatch ? timeMatch[2] : 'N/A', }); Record all values. These feed the analytics sections of the report. ================================================================================ PHASE 3 — CALCULATE BENCHMARKS ================================================================================ Using Inflact data + attendance: A. COMMENTS PER 1,000 ATTENDERS avg_c = float(avgComments.replace(',','')) score = round(avg_c / ([ATTENDANCE] / 1000.0), 2) B. COMMENT TIER score < 0.5 → "LAGGING" score < 3.0 → "TYPICAL" score < 5.0 → "THRIVING" else → "ELITE" C. ENGAGEMENT TIER eng = float(engagement.replace('%','')) eng < 5 → tier="BELOW AVERAGE", note="Low relative to follower base." eng < 15 → tier="TYPICAL", note="Healthy baseline. Most churches land here." eng < 40 → tier="STRONG", note="Content landing well. EastLake (57%), Anthem (52%)." else → tier="EXCEPTIONAL", note="Top tier. V1 Church (78%) leads the dataset." D. SENTIMENT LABEL pos=float(sentPos); neu=float(sentNeu); neg=float(sentNeg) pos > 15 and pos > neg → "Positive" neg > 15 and neg > pos → "Negative" neg > 10 → "Mixed — Negative Signals Present" else → "Mostly Neutral" interpretation: Positive: "Audience responds warmly — a trust signal that drives referrals." Negative: "Notable negative sentiment. Review recent threads before it affects your reputation." Mixed: "More negative signals than most churches. Check which post types cause friction." Mostly Neutral: "Most comments are neutral affirmations. Posts inviting personal stories generate deeper sentiment." E. COMMENT-TO-LIKE RATIO avg_l = float(avgLikes.replace(',','')) ratio = round(avg_c / (avg_l + avg_c + 0.001) * 100, 1) ratio < 0.5 → "Very low — almost no conversation" ratio < 2 → "Low — likes dominate" ratio < 5 → "Moderate — some conversation" ratio < 12 → "Healthy — real interactions present" else → "Strong — conversation-heavy account" F. THREE RECOMMENDATIONS LAGGING: r1="Add a real question to every caption — posts with questions get 26% more comments." r2="Start captions with YOU — 'you' openings average 15.4 comments, the highest hook type." r3="Set up a keyword DM funnel (e.g. 'Comment SERMON') — churches using ManyChat see 6.7× more comments." TYPICAL: r1="You're posting but not prompting — add a specific answerable question to every caption." r2="Set up a keyword DM funnel on your next 3 sermon posts and watch comment volume jump." r3="Assign someone to respond by @name within the first hour of every post." THRIVING: r1="Sharpen first-hour response — are you replying by @name within 60 min, every post?" r2="Post one humor/relatable post per month — these average 9.1 comments and attract non-attenders." r3="Lean into Identity & Calling topics — they average 6.8 comments, highest in the dataset." ELITE: r1="Top tier. Now focus on converting commenters into first-time visitors." r2="Every DM reply needs a warm, specific next-step: service time, campus, direct invitation." r3="Try staff spotlight posts — they average 10.8 comments, highest category in the dataset." ================================================================================ PHASE 4 — BUILD THE PDF REPORT ================================================================================ Install if needed: pip install reportlab pillow --break-system-packages Replace ALL [PLACEHOLDER] values before running. Save to: ~/Downloads/Instagram_CommentReport_[HANDLE]_[YYYY-MM-DD].pdf from reportlab.lib.pagesizes import letter from reportlab.lib.colors import HexColor from reportlab.pdfgen import canvas as rl_canvas from reportlab.lib.units import inch from reportlab.platypus import Image as RLImage from datetime import date from PIL import Image as PILImage import os, io # ── FILL ALL VALUES ────────────────────────────────────────────────── HANDLE = "[HANDLE]" CHURCH_NAME = "[e.g. Connexus Church]" ATTENDANCE = 1800 REPORT_DATE = date.today().strftime("%B %d, %Y") ENGAGEMENT = "11.19%" FOLLOWERS = "5.66K" UPLOADS = "1.62K" AVG_LIKES = "52.5" AVG_COMMENTS = "0.25" BEST_DAY = "Sunday" BEST_TIME = "18:00 UTC" SENT_POS = "6.47" SENT_NEU = "88.49" SENT_NEG = "5.04" SENT_LABEL = "Mostly Neutral" SENT_INTERP = "Most comments are neutral affirmations. Posts inviting personal stories generate deeper positive sentiment." TOP_HASHTAGS = ["#Tag1","#Tag2","#Tag3","#Tag4","#Tag5","#Tag6"] TOP_WORDS = ["word1","word2","word3","word4","word5","word6","word7"] SCORE = 0.14 TIER = "LAGGING" ENG_TIER = "TYPICAL" ENG_NOTE = "Healthy baseline. Most churches in the dataset land here." RATIO = 0.5 RATIO_LABEL = "Low — likes dominate" RECS = [ "Add a real question to every caption — posts with questions get 26% more comments.", "Start captions with YOU — 'you' openings average 15.4 comments, the highest hook type.", "Set up a keyword DM funnel (e.g. 'Comment SERMON') — churches using ManyChat see 6.7× more.", ] # Top posts from Phase 1 # Each: (thumb_path_or_None, post_url, likes, comments, date_str) LIKED_POSTS = [ ("/tmp/liked_thumb_1.jpg", "https://www.instagram.com/p/XXXXX/", "127", "0", "Apr 03, 2026"), ("/tmp/liked_thumb_2.jpg", "https://www.instagram.com/p/XXXXX/", "107", "1", "Apr 05, 2026"), ("/tmp/liked_thumb_3.jpg", "https://www.instagram.com/p/XXXXX/", "80", "0", "Apr 06, 2026"), ] COMMENTED_POSTS = [ ("/tmp/commented_thumb_1.jpg", "https://www.instagram.com/p/XXXXX/", "62", "1", "May 17, 2025"), ("/tmp/commented_thumb_2.jpg", "https://www.instagram.com/p/XXXXX/", "47", "1", "Apr 07, 2026"), ("/tmp/commented_thumb_3.jpg", "https://www.instagram.com/p/XXXXX/", "33", "0", "Apr 04, 2026"), ] # ── COLORS ─────────────────────────────────────────────────────────── NAVY = HexColor("#0A1628"); BLUE = HexColor("#01ADEF") DKBLUE = HexColor("#0070A8"); LGRAY = HexColor("#8AA0B0") DGRAY = HexColor("#2A3A4A"); MGRAY = HexColor("#D0DAE3") OFFWHT = HexColor("#F4F8FB"); WHITE = HexColor("#FFFFFF") GREEN = HexColor("#166534"); GREENBG= HexColor("#DCFCE7") GOLD = HexColor("#92400E"); GOLDBG = HexColor("#FEF3C7") RED = HexColor("#991B1B"); REDBG = HexColor("#FEE2E2") BLUEBG = HexColor("#DBEAFE") TIER_C = {"LAGGING":(RED,REDBG),"TYPICAL":(GOLD,GOLDBG), "THRIVING":(DKBLUE,BLUEBG),"ELITE":(GREEN,GREENBG)} ENG_C = {"BELOW AVERAGE":(RED,REDBG),"TYPICAL":(GOLD,GOLDBG), "STRONG":(DKBLUE,BLUEBG),"EXCEPTIONAL":(GREEN,GREENBG)} tier_tc, tier_bg = TIER_C.get(TIER, (DGRAY, OFFWHT)) eng_tc, eng_bg = ENG_C.get(ENG_TIER,(DGRAY, OFFWHT)) PW, PH = letter MG = 0.55 * inch CW = PW - 2 * MG out_path = os.path.expanduser( f"~/Downloads/Instagram_CommentReport_{HANDLE}_{date.today()}.pdf" ) c = rl_canvas.Canvas(out_path, pagesize=letter) # ── HELPERS ─────────────────────────────────────────────────────────── def hr(y, color=MGRAY, lw=0.5): c.setStrokeColor(color); c.setLineWidth(lw) c.line(MG, y, PW-MG, y) def section_label(y, text): c.setFillColor(LGRAY); c.setFont("Helvetica-Bold", 7) c.drawString(MG, y, text.upper()) hr(y-5, MGRAY, 0.4) return y - 17 def wrap_text(text, max_w, fn, fs): words = text.split(); lines, cur = [], [] for w in words: test = ' '.join(cur+[w]) if c.stringWidth(test, fn, fs) <= max_w: cur.append(w) else: if cur: lines.append(' '.join(cur)) cur = [w] if cur: lines.append(' '.join(cur)) return lines def pill(x, y, text, tc, bg, fs=7): tw = c.stringWidth(text,"Helvetica-Bold",fs) pw2 = tw+10 c.setFillColor(bg); c.roundRect(x,y-2,pw2,13,3,fill=1,stroke=0) c.setFillColor(tc); c.setFont("Helvetica-Bold",fs) c.drawString(x+5,y+1,text) return pw2 def stat_box(x, y, w, h, label, value, sub, tc, bg): c.setFillColor(bg); c.roundRect(x,y,w,h,3,fill=1,stroke=0) c.setStrokeColor(tc); c.setLineWidth(0.5) c.roundRect(x,y,w,h,3,fill=0,stroke=1) c.setFillColor(tc); c.roundRect(x,y+h-4,w,4,2,fill=1,stroke=0) c.setFont("Helvetica-Bold",18) vw=c.stringWidth(value,"Helvetica-Bold",18) c.drawString(x+(w-vw)/2,y+h-26,value) c.setFillColor(DGRAY); c.setFont("Helvetica",7) lw2=c.stringWidth(label,"Helvetica",7) c.drawString(x+(w-lw2)/2,y+h-38,label) if sub: c.setFillColor(LGRAY); c.setFont("Helvetica",6.5) sw2=c.stringWidth(str(sub)[:28],"Helvetica",6.5) c.drawString(x+(w-sw2)/2,y+h-50,str(sub)[:28]) def paste_thumb(thumb_path, x, y, w, h): """Paste a thumbnail image at PDF coords (x,y) with size (w,h) pts.""" if thumb_path and os.path.exists(thumb_path): try: c.drawImage(thumb_path, x, y, width=w, height=h, preserveAspectRatio=True, mask='auto') return True except Exception as e: print(f"Image paste failed: {e}") # Placeholder: dark rect with camera icon c.setFillColor(HexColor("#1E3A5F")) c.roundRect(x, y, w, h, 3, fill=1, stroke=0) c.setFillColor(LGRAY); c.setFont("Helvetica", 7) c.drawCentredString(x+w/2, y+h/2-4, "—") return False # ═══════════════════════════════════════════════════════════════════ # HEADER # ═══════════════════════════════════════════════════════════════════ c.setFillColor(NAVY) c.rect(0, PH-62, PW, 62, fill=1, stroke=0) c.setFillColor(BLUE) c.rect(0, 0, 4, PH, fill=1, stroke=0) c.setFillColor(WHITE); c.setFont("Helvetica-Bold", 15) c.drawString(MG+4, PH-24, CHURCH_NAME) c.setFillColor(BLUE); c.setFont("Helvetica", 9) c.drawString(MG+4, PH-38, f"@{HANDLE} · {FOLLOWERS} followers · {UPLOADS} posts") c.setFillColor(WHITE); c.setFont("Helvetica-Bold", 8) c.drawRightString(PW-MG, PH-22, "INSTAGRAM COMMENT HEALTH REPORT") c.setFillColor(LGRAY); c.setFont("Helvetica", 8) c.drawRightString(PW-MG, PH-35, f"Generated: {REPORT_DATE}") c.setFont("Helvetica", 6.5) credit = "Benchmarks from 100 fast-growing churches · unSeminary / Church Growth Incubator 2025" cw2 = c.stringWidth(credit,"Helvetica",6.5) c.drawString((PW-cw2)/2, PH-54, credit) y = PH - 75 # ═══════════════════════════════════════════════════════════════════ # SECTION 1: ACCOUNT OVERVIEW # ═══════════════════════════════════════════════════════════════════ y = section_label(y, "Account Overview") BH = 58; BW = (CW - 4*6)/5 for i,(lbl,val,sub,tc,bg) in enumerate([ ("Engagement Rate", ENGAGEMENT, ENG_TIER, eng_tc,OFFWHT if eng_bg==OFFWHT else eng_bg), ("Avg Comments/Post", AVG_COMMENTS, "median: 2.0",BLUE, OFFWHT), ("Avg Likes/Post", AVG_LIKES, None, DGRAY, OFFWHT), ("Followers", FOLLOWERS, None, DGRAY, OFFWHT), ("Best Post Time", BEST_DAY, BEST_TIME, DGRAY, OFFWHT), ]): stat_box(MG+i*(BW+6), y-BH, BW, BH, lbl, val, sub, tc, bg) y -= BH + 12 # ═══════════════════════════════════════════════════════════════════ # SECTION 2: COMMENT BENCHMARK # ═══════════════════════════════════════════════════════════════════ y = section_label(y, "Comment Benchmark — Comments per 1,000 Attenders") BAND_H = 52 c.setFillColor(OFFWHT); c.roundRect(MG,y-BAND_H,CW,BAND_H,4,fill=1,stroke=0) c.setStrokeColor(MGRAY); c.setLineWidth(0.4) c.roundRect(MG,y-BAND_H,CW,BAND_H,4,fill=0,stroke=1) c.setFillColor(tier_tc); c.setFont("Helvetica-Bold",30) c.drawString(MG+10, y-BAND_H+15, f"{SCORE:.2f}") c.setFillColor(DGRAY); c.setFont("Helvetica",7.5) c.drawString(MG+10, y-BAND_H+6, f"per 1,000 attenders (attendance: {ATTENDANCE:,})") c.setStrokeColor(MGRAY); c.setLineWidth(0.5) c.line(MG+105, y-BAND_H+8, MG+105, y-8) tier_defs = [ ("LAGGING","<0.5","No comment strategy", RED, REDBG), ("TYPICAL","1–2", "Posting, not prompting", GOLD, GOLDBG), ("THRIVING","3+", "Crosspoint 2.5/Lakeside", DKBLUE,BLUEBG), ("ELITE", "5+", "Anthem 5.6/Mosaic 5.7", GREEN, GREENBG), ] tb_w = (CW-115)/4 - 5 for i,(tl,ts,tsub,ttc,tbg) in enumerate(tier_defs): tx = MG+113+i*(tb_w+5); ty = y-BAND_H+4; th = BAND_H-8 is_cur = (tl==TIER) c.setFillColor(tbg if is_cur else HexColor("#F8FBFD")) c.roundRect(tx,ty,tb_w,th,2,fill=1,stroke=0) if is_cur: c.setStrokeColor(ttc); c.setLineWidth(1.5) c.roundRect(tx,ty,tb_w,th,2,fill=0,stroke=1) c.setFillColor(ttc if is_cur else LGRAY) c.setFont("Helvetica-Bold",6.5) lw3=c.stringWidth(tl,"Helvetica-Bold",6.5) c.drawString(tx+(tb_w-lw3)/2, ty+th-11, tl) c.setFont("Helvetica-Bold",13 if is_cur else 10) sw3=c.stringWidth(ts,"Helvetica-Bold",13 if is_cur else 10) c.drawString(tx+(tb_w-sw3)/2, ty+th-26, ts) c.setFillColor(LGRAY); c.setFont("Helvetica",5.5) sw4=c.stringWidth(tsub,"Helvetica",5.5) c.drawString(tx+(tb_w-sw4)/2, ty+5, tsub) y -= BAND_H + 12 # ═══════════════════════════════════════════════════════════════════ # SECTION 3: ENGAGEMENT + SENTIMENT # ═══════════════════════════════════════════════════════════════════ y = section_label(y, "Engagement Analysis & Sentiment Breakdown") COL_H=100; GAP=10; LW=CW*0.40; RW=CW-LW-GAP # Engagement c.setFillColor(eng_bg); c.roundRect(MG,y-COL_H,LW,COL_H,4,fill=1,stroke=0) c.setStrokeColor(eng_tc); c.setLineWidth(0.6) c.roundRect(MG,y-COL_H,LW,COL_H,4,fill=0,stroke=1) c.setFillColor(eng_tc); c.setFont("Helvetica-Bold",7.5) c.drawString(MG+8, y-13, "ENGAGEMENT RATE") c.setFont("Helvetica-Bold",22); c.drawString(MG+8, y-35, ENGAGEMENT) eng_x = MG+8+c.stringWidth(ENGAGEMENT,"Helvetica-Bold",22)+6 pill(eng_x, y-35, ENG_TIER, eng_tc, eng_bg, 7) c.setFillColor(DGRAY); c.setFont("Helvetica",7.5) note_lines = wrap_text(ENG_NOTE, LW-16, "Helvetica", 7.5) for li,line in enumerate(note_lines[:2]): c.drawString(MG+8, y-50-li*11, line) c.setFillColor(LGRAY); c.setFont("Helvetica",7) c.drawString(MG+8, y-COL_H+10, f"Comment-to-like ratio: {RATIO}% — {RATIO_LABEL}") # Sentiment rx = MG+LW+GAP c.setFillColor(OFFWHT); c.roundRect(rx,y-COL_H,RW,COL_H,4,fill=1,stroke=0) c.setStrokeColor(MGRAY); c.setLineWidth(0.4) c.roundRect(rx,y-COL_H,RW,COL_H,4,fill=0,stroke=1) c.setFillColor(NAVY); c.setFont("Helvetica-Bold",7.5) c.drawString(rx+8, y-13, "COMMENT SENTIMENT") c.setFillColor(DKBLUE); c.setFont("Helvetica-Bold",11) c.drawString(rx+8, y-27, f"Dominant: {SENT_LABEL}") bar_y=y-42; bar_max=RW-88 for slabel,spct,scol in [ ("Positive",float(SENT_POS),GREEN), ("Neutral", float(SENT_NEU),LGRAY), ("Negative",float(SENT_NEG),RED)]: c.setFillColor(DGRAY); c.setFont("Helvetica",7.5) c.drawString(rx+8, bar_y, slabel) c.setFillColor(MGRAY) c.roundRect(rx+58, bar_y-1, bar_max, 9, 2, fill=1, stroke=0) c.setFillColor(scol) c.roundRect(rx+58, bar_y-1, max(int(bar_max*spct/100),2), 9, 2, fill=1, stroke=0) c.setFillColor(DGRAY); c.setFont("Helvetica-Bold",6.5) c.drawString(rx+58+bar_max+4, bar_y, f"{spct:.1f}%") bar_y -= 15 c.setFillColor(LGRAY); c.setFont("Helvetica",6.5) for li,line in enumerate(wrap_text(SENT_INTERP,RW-16,"Helvetica",6.5)[:2]): c.drawString(rx+8, y-COL_H+14-li*9, line) y -= COL_H + 12 # ═══════════════════════════════════════════════════════════════════ # SECTION 4: CONTENT PATTERNS # ═══════════════════════════════════════════════════════════════════ y = section_label(y, "Content Patterns — from Last 100 Posts") PAT_H=44; PAT_W=(CW-GAP)/2 for pi,(title,items,color) in enumerate([ ("TOP HASHTAGS", [" ".join(TOP_HASHTAGS[:6])[:64]], BLUE), ("TOP CAPTION WORDS",[" · ".join(TOP_WORDS[:7])[:64]], DGRAY), ]): px = MG + pi*(PAT_W+GAP) c.setFillColor(OFFWHT); c.roundRect(px,y-PAT_H,PAT_W,PAT_H,3,fill=1,stroke=0) c.setStrokeColor(MGRAY); c.setLineWidth(0.4) c.roundRect(px,y-PAT_H,PAT_W,PAT_H,3,fill=0,stroke=1) c.setFillColor(NAVY); c.setFont("Helvetica-Bold",7) c.drawString(px+8, y-12, title) c.setFillColor(color); c.setFont("Helvetica",8) c.drawString(px+8, y-24, items[0]) c.setFillColor(LGRAY); c.setFont("Helvetica",6.5) c.drawString(px+8, y-36, "From the last 100 posts") y -= PAT_H + 12 # ═══════════════════════════════════════════════════════════════════ # SECTION 5: TOP POSTS WITH THUMBNAILS + LINKS # ═══════════════════════════════════════════════════════════════════ y = section_label(y, "Top Posts — Tap to View on Instagram") PANEL_W = (CW - GAP) / 2 THUMB_W = (PANEL_W - 32) / 3 # 3 thumbs per panel THUMB_H = THUMB_W * 1.1 # slight portrait crop PANEL_H = THUMB_H + 48 # room for stats + link below each thumb def post_panel(px, py, pw, posts, title, accent): # Panel background c.setFillColor(OFFWHT); c.roundRect(px,py-PANEL_H,pw,PANEL_H,3,fill=1,stroke=0) c.setStrokeColor(accent); c.setLineWidth(0.7) c.roundRect(px,py-PANEL_H,pw,PANEL_H,3,fill=0,stroke=1) # Title c.setFillColor(accent); c.setFont("Helvetica-Bold",7.5) c.drawString(px+8, py-12, title.upper()) # Three posts for i,(thumb_path, url, likes, comments, date_str) in enumerate(posts): tx = px + 10 + i*(THUMB_W + 6) ty = py - PANEL_H + 36 # Thumbnail paste_thumb(thumb_path, tx, ty, THUMB_W, THUMB_H) # Stats below thumbnail c.setFillColor(DGRAY); c.setFont("Helvetica-Bold",7) stats_str = f"♥ {likes} ● {comments}" sw = c.stringWidth(stats_str,"Helvetica-Bold",7) c.drawString(tx+(THUMB_W-sw)/2, ty-12, stats_str) # Date c.setFillColor(LGRAY); c.setFont("Helvetica",6) dw = c.stringWidth(date_str,"Helvetica",6) c.drawString(tx+(THUMB_W-dw)/2, ty-22, date_str) # Clickable link under each post link_label = "View →" c.setFillColor(BLUE); c.setFont("Helvetica",6.5) lw4 = c.stringWidth(link_label,"Helvetica",6.5) lx = tx + (THUMB_W-lw4)/2 c.drawString(lx, ty-34, link_label) if url and 'instagram.com' in url: c.linkURL(url, (lx, ty-37, lx+lw4, ty-27)) post_panel(MG, y, PANEL_W, LIKED_POSTS, "Most Liked Posts", GOLD) post_panel(MG+PANEL_W+GAP, y, PANEL_W, COMMENTED_POSTS,"Most Commented Posts", BLUE) y -= PANEL_H + 12 # ═══════════════════════════════════════════════════════════════════ # SECTION 6: RECOMMENDATIONS # ═══════════════════════════════════════════════════════════════════ y = section_label(y, f"Your Next Steps — Current Tier: {TIER}") REC_H = y - 46 c.setFillColor(tier_bg); c.roundRect(MG,46,CW,REC_H,4,fill=1,stroke=0) c.setStrokeColor(tier_tc); c.setLineWidth(0.8) c.roundRect(MG,46,CW,REC_H,4,fill=0,stroke=1) c.setFillColor(tier_tc); c.roundRect(MG,46,4,REC_H,2,fill=1,stroke=0) rec_y = y - 10 for i,rec in enumerate(RECS[:3]): c.setFillColor(tier_tc); c.roundRect(MG+12,rec_y-13,15,13,2,fill=1,stroke=0) c.setFillColor(WHITE); c.setFont("Helvetica-Bold",7) c.drawCentredString(MG+19, rec_y-9, str(i+1)) lines = wrap_text(rec, CW-50, "Helvetica", 8) c.setFillColor(DGRAY) for li,line in enumerate(lines[:2]): c.setFont("Helvetica-Bold" if li==0 else "Helvetica", 8) c.drawString(MG+32, rec_y-5-li*11, line) rec_y -= 14 + (11 if len(lines)>1 else 0) + 8 # ═══════════════════════════════════════════════════════════════════ # FOOTER # ═══════════════════════════════════════════════════════════════════ c.setFillColor(NAVY); c.rect(0,0,PW,40,fill=1,stroke=0) c.setFillColor(LGRAY); c.setFont("Helvetica",6.5) c.drawString(MG+4, 15, f"@{HANDLE} · Generated {REPORT_DATE} " f"· Data: Instagram + Inflact " f"· Attendance: {ATTENDANCE:,} weekend attenders") c.drawRightString(PW-MG, 15, "churchgrowthincubator.com · unSeminary") c.save() print(f"Saved: {out_path}") ================================================================================ PHASE 5 — CONFIRM OUTPUT ================================================================================ Tell the user: ✅ Report saved: ~/Downloads/Instagram_CommentReport_[HANDLE]_[DATE].pdf 📊 SUMMARY: Church: [CHURCH_NAME] Posts analyzed: 12 most recent Engagement Rate: [ENGAGEMENT] ([ENG_TIER]) Avg Comments per Post: [AVG_COMMENTS] Comments per 1,000 Attenders: [SCORE] Tier: [TIER] Sentiment: [SENT_LABEL] ⚡ PRIORITY ACTION: [First recommendation] ================================================================================ NOTES ================================================================================ - Requires Claude Cowork (desktop) with browser connected - MUST be logged into Instagram in Chrome before running - Inflact is fully public — no login required - Run time: approximately 10–15 minutes (Instagram post collection takes time) - Output: 8.5×11 PDF — thumbnails are real post images, links are clickable - For private accounts: must be following the account before running - If Instagram rate-limits mid-run: wait 30 seconds and continue from last post - Run monthly to track progress over time ================================================================================