🌻 Quỹ Hoa Mặt Trời — illustrations · 2026-05-28

Positioning anchor · the mission spine

Education-focused charity in Vietnam

Not just scholarships. The Foundation works through an 8-petal holistic-development model: financial support + skills + confidence + social-emotional growth + real-world exposure.

Every caption ties back to a petal. A piece that reads as generic "helping poor kids" has missed the position.

Cùng em tiến bướcNâng bước tương laiTrao gửi yêu thương

The 8-petal model — the position trustees ask every post to reflect

Source: Knowledge/Brand/program-model.md · official deck filed 2026-05-21. Each petal is also a ready Stream-B content theme.

1 Hỗ trợ học tập 2 Thuyết trình tranh luận 3 Cảm xúc ứng xử 4 Sáng tạo suy ngẫm 5 Sức khỏe thể chất 6 Thế giới quan 7 Nghề nghiệp 8 Lan tỏa yêu thương Phát triển toàn diện
  1. Hỗ trợ học tập — học bổng, dụng cụ, khích lệ tinh thần học tập.
  2. Thuyết trình & tranh luận — sinh hoạt kỹ năng hàng tháng.
  3. Cảm xúc & ứng xử — SE assessments, ứng xử tích cực.
  4. Suy ngẫm & sáng tạo — viết, vẽ, cuộc thi sáng tác.
  5. Sức khỏe thể chất — hội thao thường niên, nhảy dây, đồng đội.
  6. Thế giới quan — tham quan trải nghiệm hằng năm.
  7. Định hướng nghề nghiệp — tham quan doanh nghiệp, gặp chuyên gia.
  8. Lan tỏa yêu thương — các em đi phụng sự cộng đồng.

Why this matters for voice: the outcome-for-the-child beat in every Stream-A caption maps to one of these 8 petals. Not naming a petal in the post itself — naming the kind of outcome (a skill, a confidence moment, an exposure, an SE growth) that the petal predicts.

The four-beat story shape (Stream A · event posts · 80–140 words VN)

Trustees, 2026-05-27: "strong hook, clear outcome/meaning, donor-friendly close, not generic." Codified in .claude/rules/brand-voice.md § Story shape and Knowledge/Brand/system-prompt.md.

1

Strong hook

One line. A person, a place, or a number that becomes a scene.

Never: date stamp, process noun (Buổi sinh hoạt…), salutation formula (Quỹ xin gửi…).

2

Why it matters

One short sentence connecting the moment to the programme's purpose.

3

Outcome for the child

Required. Verb-led. In the child's own agency — skill, confidence, exposure, SE growth, a next step.

Not "the volunteers were moved." The outcome is for the child.

4

Donor-friendly close

An achievable action — follow, share, attend, volunteer — or a light reader question. Specific to the moment.

No donation ask until the Foundation supplies approved CTA wording.

Sentence-level signatures (from N=50 in Kho bai.xlsx → Kho content)

These rhetorical moves appear in both first-drafts and trustee-edited finals. They are the voice — not AI tells. Two clusters: A · reportorial · Stream AB · contemplative · Stream B (QA's voice).

S1 · the "không chỉ X, mà Y" reframe

The single most common move. Hook, body, or pivot.

"Kiên trì — không phải là không biết mệt. Kiên trì là khi đã mệt, vẫn chọn 'cố thêm một chút nữa'."— corpus #2
"Mỗi giải thưởng không chỉ là một tấm bằng, mà là một câu chuyện về ý chí và niềm tin."— corpus #7 (32 em – 32 giải)

S2 · the cascading parallel trio

Three (or four) short lines, same opener, different completion. The defining beat of Cluster B.

"Có em mang về một ước mơ mới. / Có em mang về một câu hỏi lớn về tương lai. / Có em chỉ mang về một suy nghĩ rất đơn giản…"— corpus #30

S3 · the "có những X…" observation

A soft entry into a generalisation. Almost always followed by S1 or S2.

"Có những đứa trẻ lớn lên sớm hơn tuổi."— corpus #34
"Có những thay đổi không đến từ những điều quá lớn lao."— corpus #39

S4 · the "đủ" cluster

"Đủ" as a standalone adverbial, repeated to define what a moment needs.

"đủ kiên nhẫn / đủ an toàn / đủ lâu / đủ thời gian / đủ những người sẵn sàng đồng hành"— corpus #23, #31, #36

S5 · the closing reader question

~40 of 50 entries. The strongest single structural signature of the whole corpus.

"💛 Theo bạn, trẻ nên được khen vì thành tích, hay vì quá trình cố gắng?"— corpus #22
"💛 Nếu là bạn, bạn sẽ nói gì với một đứa trẻ đang nghĩ rằng mình không đủ giỏi?"— corpus #24

Lexicon fingerprint

Recurring vocabulary across drafts and edits.

Verbs: đồng hành · lan tỏa · gieo · nuôi dưỡng · chắp cánh · thắp · ươm · vững bước · trỗi dậy · tin rằng.

Nouns: hành trình · ước mơ · niềm tin · kiên trì · khoảnh khắc · ánh mắt · nụ cười · hạt mầm · nội lực · mái nhà · mỗi em.

Audience markers: các em (children) · em (one child) · con (parent voice) · chúng tôi (Foundation) · bạn (reader).

The hard rules — what the voice is not

1 · No pity / poverty-porn

No "tội nghiệp", no helpless framing, no donor/volunteer as hero. People are agents in their own story.

Non-overridable. Gate 4 + gate 8.

2 · No em-dash / en-dash

No and no in prose. Use comma, semicolon, parentheses, or split.

The single most reliable AI tell in published copy. brand-voice-editor rewrites every em-dash on the Stream-A pass.

3 · No bureaucratic opening

No date stamp, no process noun, no "Quỹ xin gửi lời chúc mừng…".

Strongest trustee edit pattern in the corpus (rows 12, 16, 46).

4 · No fabricated quote or number

Quotes verbatim and attributed. Numbers traceable to a Foundation record (logged in STATUS.md → claim_sources[]).

Gate 6 (claim-checker) fires only when a post asserts a number or a CTA.

5 · No donation CTA (yet)

Until the Foundation supplies approved CTA + banking wording, captions don't carry an ask.

"Donor-friendly close" means the shape: follow, share, attend, volunteer.

6 · Restraint on emoji

🌻 as paragraph anchor · 💛 at the closing question · ✨ for an insight beat. Never mid-word, never triple-stacked.

Editor call 2026-05-27 — no strict number, but not more emoji than blank lines.

Where the rules come from — a trustee edit, side-by-side

Corpus row 12 · "32 học sinh – 32 giải": the canonical example of E1 (strip the bureaucratic opening) and E5 (convert dry congratulations into momentum poetry).

✏️ Bài gốc — first draft, field-team

Quỹ Hoa Mặt Trời xin gửi lời chúc mừng nồng nhiệt tới Trường THPT Lương Thế Vinh — ngôi trường đã có 32 học sinh đạt giải học sinh giỏi cấp tỉnh năm học 2025–2026.

Mỗi giải thưởng là một câu chuyện về sự cố gắng, kiên trì và niềm tin của thầy cô, gia đình và các em…

Failures: bureaucratic salutation lede · long single-paragraph body · em-dash · no scene.

🌻 Bản biên tập — trustee final

32 học sinh.
32 giải thưởng học sinh giỏi cấp tỉnh.

Một con số khiến ai nghe cũng phải dừng lại.

Nhưng phía sau con số ấy không chỉ là thành tích, mà là hàng trăm giờ học, là những lần không bỏ cuộc, là sự đồng hành bền bỉ của thầy cô.

Wins: number-as-scene hook · fragmented stanza form · S1 reframe · ✨ insight beat · paragraph breathing room.

How this signal lands in the system: the diff above (and 5 other pairs in the corpus) is captured in Knowledge/Brand/voice-signatures.md as Signal 1 (bureaucratic openings get rewritten) and Signal 2 (literary flourish pruned, names + numbers kept). Trustee instructions from the deck review feed trustee-feedback-log.md. Both files are read by every producer agent at draft time.

2.1 · How a Stream-A caption is developed

Rule (Editor, 2026-05-28): bullets + pictures, not pictures alone. The Coordinator supplies a factual spine via the brief form; Claude composes around it. The model never guesses load-bearing facts from photos.

Input · what Claude sees

  • brief.yaml — bullets from the Coordinator form (activity, school, date, programme, outcome, named people, quotes, sensitive flags).
  • renditions/ — 4–5 EXIF-stripped, retouched photos (Pillow + optional MediaPipe + opt-in Gemini polish).
  • System promptsystem-prompt.md + few-shot exemplars from few-shot.yaml, prompt-cached.

Compose · the four beats

  1. Hook — person · place · number-becomes-scene.
  2. Why it matters — one sentence, programme purpose.
  3. Outcome for the child — verb-led, child's agency, ties to a petal.
  4. Donor-friendly close — achievable action or reader question.

+ alt-text per image (≤140 chars), 4–6 CamelCase hashtags, #HoaMatTroi always included.

Output · what gets written

  • copy/facebook.md — 80–140 words VN, blank line between paragraphs.
  • copy/web.md — same beats, longer body.
  • DRAFT — INTERNAL marker at top; stripped only at pack time.
  • EN block only when milestone: true.

2.2 · A worked example — Stream-A draft from a real brief

Fictional but representative — based on corpus row 16 (Đại An · "Câu hỏi phụ có thưởng").

brief.yaml (Coordinator input)

activity_type: Sinh hoạt kỹ năng hàng tháng school: THCS Đại An event_date: 2026-04-18 program: Cùng em tiến bước event_summary: | Buổi sinh hoạt chủ đề "Kiên trì — không bỏ cuộc". Các em xem video, thảo luận, viết câu trả lời lên 22 bảng nhỏ. Có 14 câu được chấm điểm cao, 5 câu được giải thưởng nhỏ. Cô Hiệu trưởng dự cùng. named_people: | Thầy Nguyễn Minh Đức — Hiệu trưởng quotes: | "Bắt đầu thì dễ, đi đến tận cùng mới khó." — câu khẩu hiệu buổi sinh hoạt outcome_for_child: | Các em mạnh dạn nói ra suy nghĩ của mình trước cả lớp; nhiều em ngày thường rụt rè đã chủ động viết câu trả lời lên bảng. petal_anchor: 2 — Thuyết trình và tranh luận sensitive_flags: [] coordinator_name: Quỳnh Anh

copy/facebook.md (Claude draft)

> DRAFT — INTERNAL. Chưa duyệt; không phân phối.

🌻 "BẮT ĐẦU THÌ DỄ, ĐI ĐẾN TẬN CÙNG MỚI KHÓ." 🌻

Một dòng chữ trên bảng. 22 chiếc bảng nhỏ. 14 câu trả lời được chấm điểm cao, 5 câu được giải.

Buổi sinh hoạt kỹ năng tháng này của lớp Cùng em tiến bước tại THCS Đại An không xoay quanh điểm số. Nó xoay quanh việc dám viết ra một suy nghĩ của riêng mình.

✨ Có những em ngày thường rất ít nói, hôm nay lại là người chủ động cầm phấn đầu tiên. Có em viết một câu ngắn, rồi nán lại rất lâu để gạch dưới một chữ. Khi Thầy Hiệu trưởng Nguyễn Minh Đức dừng đọc, cả lớp lặng đi một nhịp, rồi vỗ tay.

Quỹ tin rằng kiên trì không phải là không biết mệt; kiên trì là khi đã mệt, vẫn chọn cố thêm một chút nữa, trước mặt cả lớp.

💛 Còn bạn, bạn nhớ nhất một câu trả lời nào của tuổi học trò?

#HoaMatTroi #CungEmTienBuoc #KienTri #THCSDaiAn #LanToaYeuThuong

121 words VN · 4 paragraphs · ✓ hook is a quote-becomes-scene · ✓ outcome beat in child's agency · ✓ reader question close · ✓ no em-dash · ✓ named teacher kept · ✓ verbatim quote attributed.

2.3 · How the draft is reviewed — 8 gates in order

Codified in .claude/rules/qc-enforcement.md. First blocking failure halts; the bundle goes back to the producer with a one-line finding.

Gate 8 is the firewall. Never automated, never overridden by a non-safeguarding role, never collapsed into the Approver role. The only programmatic backstop is the route-regression rule: a bundle that reaches awaiting-approval with empty gate_findings[] is flagged as a regression.

2.4 · The audit trail — what STATUS.md captures

Every transition is appended to Sandbox/posts/<slug>/STATUS.md and mirrored into index/registry.sqlite (WAL, nightly file-copy backup) as the system of record for asset → rendition → caption → post lineage.

--- event_slug: 2026-04-18__dai-an-cau-hoi-phu stream: A state: awaiting-approval assignee: editor last_updated: 2026-04-21T09:14:00+07:00 gate_findings: - gate: 3-brand-voice status: passed note: "hook is quote-as-scene; 121 words; 0 em-dash" - gate: 4-dignity status: passed note: "outcome beat present, child agency clear" - gate: 6-claims status: no-op note: "no impact number asserted" dignity_flags: [] claim_sources: [] override_log: [] SAFEGUARDING: cleared by Hằng at 2026-04-21T09:11:00+07:00 --- # Reviewer notes - 5 photos shortlisted, 4 in renditions/fb (Editor decision on 9:16 reframe pending). - Coordinator confirmed Thầy Đức's name is OK to print (named_people list). - Suggested schedule: 2026-04-22 19:00 +07 (Tuesday evening, weekly cadence slot).

What this enables: every bad post that ships, every override, every gate-3 correction is one line in _system/runs/qc-log-<YYYY-MM>.jsonl. Monthly, the top 2 recurring corrections become a template or field-card change — that is the quality ratchet. The quality-auditor agent reports the trailing-30-day mix (80/20 value:ask), cadence, accessibility coverage, dignity, safeguarding KPI, and journey-anonymity in /system-review. It does not gate publication; it audits after.

Coordinator (Stream A/B) QC chain (automated) Manual safeguarding (gate 8) Approver / Editor
1
Field capture
Photographer
2
Drive upload
Photographer
3
Brief form
Coordinator
4
/intake
Coordinator
5
/curate
Coordinator
6
/draft
Claude Opus
7
/qc-run
7 gates
8
Safeguarding
Named role
9
/approve
Editor
10
Pack & post
Coordinator

Stage 3 · The Coordinator brief form — the spine of every post

Google Form on the Foundation's domain. Phone-friendly, <5 min to fill. Responses land in a Sheet under 08_Phieu-thong-tin-bai-dang/ on the Shared Drive; tools/intake_brief.py writes a brief.yaml into the matching event folder. No required fields at the form; downstream spine validation (activity, school, date, outcome) lives in intake_brief.py.

🌻 Phiếu thông tin bài đăng — Quỹ Hoa Mặt Trời

① Stream A — Thông tin sự kiện

Sinh hoạt kỹ năng · Cuộc thi viết · Thể thao · Tham quan trải nghiệm · Tham quan doanh nghiệp · SE assessment · Lan tỏa yêu thương · Thăm gia đình…
THCS Tam Thanh · THCS Đại An · THCS Tân Khánh · THCS Vĩnh Hào · THPT Lương Thế Vinh · THPT Nguyễn Bính · …
2026-04-18
Cùng em tiến bước · Nâng bước tương lai · Trao gửi yêu thương
Buổi sinh hoạt thứ 4 của lớp Cùng em tiến bước. Các em được học chủ đề Kiên trì, thảo luận theo nhóm, tự thiết kế poster. Có cô Hiệu trưởng dự cùng…

② Stream A — Người và lời nói

Cô Bùi Thị Nga — Hiệu trưởng THCS Tam Thanh
Em Phạm Thanh Quyền — lớp 8A
"Em không cần giỏi hơn ai cả. Em chỉ cần đừng bỏ cuộc với chính mình." — cô Hương, GV chủ nhiệm

③ Stream A — Điều gì là điểm sáng? outcome beat · trustee requirement

Các em đã dám đứng trước lớp thuyết trình lần đầu tiên. Một em ngày thường rất rụt rè đã chủ động kể về ước mơ của mình.
1 Hỗ trợ học tập · 2 Thuyết trình & tranh luận · 3 Cảm xúc & ứng xử · 4 Suy ngẫm & sáng tạo · 5 Sức khỏe · 6 Thế giới quan · 7 Nghề nghiệp · 8 Lan tỏa

④ Stream B — Bài chia sẻ / nhận thức (bỏ qua nếu là bài sự kiện)

Kết nối cộng đồng · Tư duy giáo dục · Hậu trường, testimonial · Dữ liệu tổng quan · Dữ liệu sau hoạt động · Dữ liệu theo hành trình · Triết lý Quỹ HMT…
Trẻ nhút nhát không phải vì thiếu khả năng, mà vì chưa từng được tin rằng mình xứng đáng được thử.

⑤ Link tới ảnh

https://drive.google.com/drive/folders/…

⑥ Lưu ý đặc biệt safeguarding

☐ Hoàn cảnh khó khăn của một em cụ thể · ☐ Mất mát / biến cố trong gia đình · ☐ Sức khỏe · ☐ Chưa có sự đồng ý dùng ảnh · ☐ Trường yêu cầu kiểm tra trước · ☐ Khác…
Bất cứ điều gì khác — câu chuyện hậu trường, lý do tổ chức, mong muốn đặc biệt của trường, hoặc "không cần nhắc đến X".
Quỳnh Anh · Hằng · c An…

3.3 · The 10-stage SOP — owner, action, definition of done

Source: RUNBOOK.md. Three roles never collapsed: CoordinatorApproverSafeguarding Sign-off.

# Stage Owner Tool / skill Definition of done
1Field capturePhotographerOnly the agreed shot-list; dignity respected; nothing posted by the photographer.
2Drive uploadPhotographer01_Tai-lieu-thuc-dia/<YYYY-MM-DD>__<slug>/Folder shape correct; uploaded within 48h.
3Brief formCoordinatorGoogle Form → Sheet 08_Phieu-thong-tin-bai-dang/Spine fields filled: activity · school · date · outcome.
4IntakeCoordinator/intake · tools/intake_brief.py · media_sanitise.pyBundle skeleton + index rows; EXIF/GPS stripped (gate 2).
5CurationCoordinator/curate · media_triage.py · media_retouch.py4–5 dignified, channel-ready renditions; optional Gemini polish recipe recorded.
6DraftingClaude Opus / post-writer/draft · caption_draft.pycopy/*.md + alt-text + hashtags; DRAFT marker present; four beats; 80–140 words.
7Editorial QCApprover/qc-run · gates 1–7 (format-tester, brand-voice-editor, claim-checker)All gate findings recorded in STATUS.md; corrections applied.
8Safeguarding sign-offSafeguarding role (≠ Coordinator)safeguarding-signoffGate 8 — explicit SAFEGUARDING: cleared by <name> at <ISO> line.
9Final approvalApprover / Editor/review-queue/approveSchedule chosen; state: approved + publish.yaml clean.
10Pack & postCoordinatorpublish_pack.py → manual FB post → posted_id loggedManual post; community-manager takes over for engagement.

3.4 · Swim-lane view — who touches the bundle, in order

Photographer
Field capture Upload to Drive 01_Tai-lieu-thuc-dia/
Coordinator
Fill brief form /intake /curate (cull · retouch · watermark · subs) /draft (invokes Claude)
Claude Opus
system-prompt + brief + photos caption JSON (vn.fb · vn.web · alt_text[] · hashtags[]) copy/*.md written
QC chain
1 format 2 EXIF 3 voice 4 dignity 5 terminology 6 claims* 7 platform
Safeguarding
8 manual sign-off · never overridden SAFEGUARDING: cleared by … in STATUS.md
Editor
/review-queue /approve or /revise / /reject
Coordinator
publish_pack.py Manual post to Facebook log posted_id community-manager monitors
Audit
index/registry.sqlite — asset→rendition→caption→post lineage _system/runs/qc-log-<YYYY-MM>.jsonl monthly /system-review by quality-auditor

3.5 · What the bundle looks like on disk — the unit of work

Sandbox/posts/<event-slug>/

2026-04-18__dai-an-cau-hoi-phu/ ├── brief.yaml # spine from form ├── STATUS.md # state · gates · sign-off ├── publish.yaml # channel × schedule × state │ ├── source/ # EXIF/GPS-stripped originals │ ├── img_001.jpg │ ├── img_002.jpg │ └── ... │ ├── renditions/ # channel renditions │ ├── fb/ │ │ ├── *__fb__feed-1.jpg │ │ ├── *__fb__feed-2.jpg │ │ └── *.recipe.yaml │ └── web/ │ └── ... │ └── copy/ ├── facebook.md # 80–140w VN · 4 beats └── web.md # longer body

Bundle states (the state machine)

draft in-review safeguarding awaiting-approval
awaiting-approval approved packed published
awaiting-approval revising (back to producer)
awaiting-approval rejected _rejected/ · reason logged for autoimprove

The publish.yaml pin: channel-publisher is never cron-triggered in Phase A. publish_pack.py only runs if state: approved and gate-1 is clean. It strips the DRAFT marker, emits the renditions plus a single human-readable post sheet per channel under Sandbox/_review-prints/<slug>/, and flips state to packed. A human still does the Facebook post.

3.6 · After publication — logs, ratchet, monthly recap

Per-bundle audit

  • STATUS.md · state machine + gate findings + overrides
  • renditions/*.recipe.yaml · regenerable transform recipe + AI-polish recipe
  • publish.yaml · channel × scheduled-time × approval × posted_id

Workspace-wide

  • index/registry.sqlite · 6-table lineage system of record (WAL · nightly backup)
  • _system/runs/qc-log-<YYYY-MM>.jsonl · pattern → fix lines
  • _system/runs/<date>__<tag>/ · run artefacts & failure surface

The Phase-A absolutes (non-overridable): no hook, cron, agent or skill publishes without an explicit /approve. The safeguarding sign-off is never automated and never overridden by a non-safeguarding role. /setup-schedules refuses to register any channel-publisher job while current_phase: A. The firewall holds: no data leaves this workspace.

4.1 · Two categories of editing — corrective vs presentational

Codified in .claude/rules/image-polish.md. The polish-vs-fabrication line is the load-bearing rule; the chrome layer is the second editing pass that happens after polish.

① Category 1 — pixel polish (AI corrective)

Engine: Gemini Nano Banana Pro (model gemini-3-pro-image-preview). Opt-in, paid cloud. The image still shows the same moment.

Allowed: exposure, white balance, contrast, denoise, sharpen, mild upscale, clutter removal, background blur, privacy blur on bystander faces.

Forbidden: face beautification, fabricated scene elements, changing what a subject is doing or holding, compositing two moments, "restoring" detail the camera didn't capture.

Every polished file writes a recipe sidecar: model id, full verbatim prompt, before/after SHA-256, system-prompt hash. Gate 8 reviews any bundle with ai_polished: true.

② Category 2 — brand chrome (overlay)

Engine: Pillow + project fonts, free, deterministic, regenerable from a recipe. No AI in this step.

Allowed: logo + watermark, gradient wash, tagline lockup, picture frame, programme bug, end-card / outro graphic.

Discipline: brand palette only · ≥4.5:1 contrast · logo never over a face · one composite unit, no double-stacking on top of it.

No ai_polished flag; the chrome layer is presentation, not pixel synthesis. Records all parameters + tagline string in the rendition recipe so the look is regenerable.

4.2 · Stream-A photo chrome composite — the corpus standard

Calibrated from all 13 Stream-A finals in Trang tính3. The four elements below are one indivisible unit, applied to the hero photo of every event piece.

Anatomy of the composite

photo subject
(face-aware crop)
hoa mặt trời
QUỸ TỪ THIỆN
Không chỉ trao học bổng
— chúng tôi trao cơ hội trưởng thành.
5:4 landscape
  • Canvas · 5:4 landscape, ~1440 px on the long edge.
  • Wash · yellow #FED138 → α 0, ~14° diagonal sweep, height 42% of canvas, peak α 0.65 at bottom-left.
  • Logo · bottom-left, 8.5% canvas width, 3.5% inset. No white chip — the wash provides contrast.
  • Tagline · bottom-right, Playfair Display SemiBold ~2.8% of canvas height, white with navy shadow, right-aligned, line-height 1.55.

Corpus reference · default stock tagline

Hương Sơn, Trang tính3 row 2. The default "Không chỉ trao học bổng — chúng tôi trao cơ hội trưởng thành." tagline appears on 9 of 13 trustee-approved finals.

Hương Sơn group portrait with HMT chrome — logo bottom-left, default tagline bottom-right on a yellow gradient wash

Knowledge/Brand/kho-content-2026-05-27/images/row-02/image20.png

4.3 · Two more references — per-event override + Stream-B card

Per-event tagline override · Đại An "Bữa cơm"

Same composite, different tagline. When the brief supplies a short 2-line tagline (here: "Bữa cơm nhỏ / Tình thần lớn."), it replaces the stock string for that event series.

Đại An classroom meal scene with HMT chrome — logo bottom-left, per-event tagline 'Bữa cơm nhỏ — Tình thần lớn.' bottom-right

row-07/image3.png · brief.yaml field tagline: supplies the override

Stream-B quote card · different template family

Stream-B contemplative posts ship as a typographic card, not a chromed photo. Cream background, navy Playfair Display, teal ellipse outline, top-centre logo, no tagline. Square 1:1.

Stream-B quote card on cream background — navy headline 'Một đứa trẻ cần điều gì để không cảm thấy một mình?' inside a thin teal ellipse, logo top-centre, yellow/teal sparkle accents

row-13/image15.png · Stream-A composite never applied to Stream-B

4.4 · The pipeline reproduces the standard — raw → chrome composite

tools/media_retouch.py with the corpus-calibrated CHROME_DEFAULTS (2026-05-28). Raw photo on the left, the same photo through the pipeline on the right. No prompts, no AI, no fabrication — just smart-crop + gradient wash + logo + tagline.

📷 Raw source

Raw photograph: HMT staff and beneficiary children walking past a red gate, no branding or chrome applied

2024-05-19 HMT × Hope School Bana visit. EXIF/GPS already stripped at gate 2.

🌻 After media_retouch.py

Same photograph rendered through media_retouch.py with the Stream-A chrome composite: yellow gradient wash bottom-left, HMT logo bottom-left, default tagline bottom-right

Wins: 5:4 smart-crop · gradient wash anchored bottom-left · logo lockup at 3.5% inset · default tagline right-aligned in Playfair Display SemiBold · recipe sidecar records every parameter for regenerability.

4.5 · The four-step editing sequence inside Stage 5 (/curate)

What page 3 collapses into one box ("Stage 5 · Curation · media_triage.py · media_retouch.py") actually runs four ordered sub-steps. The first is mandatory and non-overridable; the third is opt-in paid.

① Sanitise

media_sanitise.py

EXIF + GPS strip on every original. exiftool wrapper; hard-fails if exiftool is missing (gate 2 cannot be silently downgraded).

Non-overridable · free · deterministic.

② Triage

media_triage.py

Cull 7–30 raw → 4–5 keepers. Pillow HighFreqSharpness baseline + optional pyiqa-BRISQUE + imagededup PHash clustering + MediaPipe face boost.

Free · 4–5 photos preferred · 7 max (platform-policy).

③ Polish paid, opt-in

photo_polish.py

Gemini Nano Banana Pro corrective polish. Hard-coded system prompt forbids fabrication. Records full prompt + before/after hash + system-prompt hash in the recipe.

Skipped when GEMINI_API_KEY is absent · gate 8 reviews.

④ Retouch + chrome

media_retouch.py

Face-aware smart-crop → per-channel renditions → chrome composite. Wash + logo + tagline as one unit. Recipe sidecar per rendition.

Free · deterministic · regenerable.

4.6 · What the recipe sidecar records — every rendition is regenerable

Every output writes a *.recipe.json next to it. The recipe is the regenerability contract: original photo + recipe → byte-identical rendition. AI-polished outputs additionally carry a YAML polish recipe so the AI step is auditable.

Pillow chrome rendition recipe

tool: "media_retouch" source: "source/img-01.jpg" spec: channel: fb purpose: feed-5x4 aspect: [5, 4] longest_edge: 1440 format: JPEG focus: [0.52, 0.41] # mediapipe face centre chrome: wash_color: [254, 209, 56] wash_alpha: 0.65 wash_height_pct: 0.42 wash_angle_deg: 14.0 logo_width_pct: 0.085 tagline_size_pct: 0.028 tagline: "Không chỉ trao học bổng — chúng tôi trao cơ hội trưởng thành." watermark: null # legacy path off when chrome is on

AI polish recipe (when polish ran)

tool: "photo_polish" model: "gemini-3-pro-image-preview" polisher: "media-curator" intent: "denoise low-light + remove stray bottle on left" polished_at: 2026-04-20T11:14:00+07:00 before_sha256: f1a3d2c… after_sha256: 9e4b71f… system_instruction_hash: a7c2… ai_polished: true # Gate 8 surfacing dignity_flags: - ai-polished # scanned by bundle_state

Polished output is preserved alongside the recipe; unlike the Pillow chrome recipe it is not bit-for-bit regenerable (generative model).

Where the standard is documented: spec → .claude/rules/image-polish.md § Stream-A photo chrome composite (corpus-calibrated 2026-05-28) · canonical targets → Knowledge/Brand/assets/social-templates/stream-a-chrome-target.png + stream-a-chrome-target-per-event-tagline.png + stream-b-quote-card-target.png · tagline strings → Knowledge/Brand/brand-assets.md § Stream-A photo chrome · implementation → tools/media_retouch.py CHROME_DEFAULTS + apply_chrome_composite() · field findings → _system/notes/kho-content-2026-05-27-observations.md §5.

Pre-pilot blockers: (1) GEMINI_API_KEY is not yet in .env — Category-1 polish runs only via --polish-fake until the Foundation mints a key. (2) The logo redesign flagged in brand-assets.md 2026-05-27 is still pending; current renditions use hmt-logo-on-dark.png as supplied. (3) End-to-end Crawl-pilot on one real event hasn't run yet — the chrome composite was validated on a 2024 Đà Nẵng raw photo + synthetic selftest, not on a 2026 Vụ Bản event.