Books: ba mức của một quyển sách
Mỗi cộng đồng ở tokyo có một kệ 12 quyển. Sách hiện ở tab /c/:slug/books và có trang riêng ở /books/:slug.
Đổi tên 07.09.2026
Sản phẩm này trước đây tên DeepBooks, đường dẫn /deep-books. Từ 07.09.2026 nó là Books, đường dẫn /books; đường cũ redirect, API giữ bí danh /:slug/ebooks bên cạnh /:slug/books. Bảng trong D1 vẫn tên legacy_ebooks*, đó là tên bảng, không phải tên sản phẩm.
Ba mức
Cột legacy_ebooks.outline_level giữ mức của một quyển. Nó khác cột level sẵn có (level là độ khó: beginner/intermediate/advanced).
| Mức | Có gì | is_published |
|---|---|---|
| 1, Khung | Tên sách; chân dung người đọc (jobs to be done + pains); tên chương = Compelling Question, mỗi chương một Essential Question, section = Guiding Question; prompt tạo ảnh bìa; authoring meta | 0 |
| 2, Xương nội dung | Thêm content_portable cho từng section, SCQA nơi cần, mỗi section có ít nhất một con số làm lại được | 0 |
| 3, Trọn quyển | Thêm bìa dọc (cover_url), quiz theo chương, và một lượt rà soát | 1 |
Không nhảy mức. Khung sai thì viết nội dung chỉ làm cái sai dày thêm.
Chân dung người đọc, prompt bìa và authoring meta (mức 1)
Ba nhóm trường thêm 08.09.2026, tất cả đều thuộc mức 1 vì cả ba đều là thứ dùng để viết tiếp, không phải thứ trang trí:
| Trường | Là gì | Vì sao ở mức 1 |
|---|---|---|
jobs_to_be_done_vi | Việc người đọc thuê quyển sách làm | Đã có từ đầu |
reader_pains_vi | 3–6 rào cản khiến họ chưa làm được việc đó | Jobs nói muốn tới đâu, pains nói vì sao chưa tới, chương sách sống ở chỗ thứ hai |
reader_context_vi | Bối cảnh nghề và quy mô của người đọc | Khai một lần cho cả kệ |
cover_prompt | Lời dặn tạo ảnh bìa | Ảnh làm ở mức 3, nhưng lời dặn viết cùng lúc với tên sách, khi ý còn tươi |
authoring_meta_json | voice, units, evidence, forbidden, section_target, example_policy | Mức 2 và 3 đọc nó để viết cho khớp mức 1 |
Không có authoring_meta thì người (hay engine) viết tiếp sẽ tự chọn giọng, tự chọn đơn vị tiền và tự quyết được bịa số hay không, và quyển sách rời ra thành nhiều giọng giữa các chương.
cover_prompt phải nêu bố cục dọc, vật thể cụ thể, ánh sáng và bảng màu lấy từ token của packages/design-system/theme.css. Ba điều cấm: không chữ trong ảnh (tên sách đã nằm dưới thẻ), không nhận diện thương hiệu có thật, không ảnh stock kiểu quảng cáo.
Mức 1 sinh bằng BookPack
Nguồn sự thật là JSON trong repo, không phải D1:
cd api.conan.school
python3 scripts/book_pack_to_sql.py --check content/books/packs/<community>.json
python3 scripts/book_pack_to_sql.py content/books/packs/<community>.json \
> migrations/<YYYYMMDD><chữ>_books_<community>_12.sql--check bắt lỗi đếm được: số chương ngoài khoảng 4–7, số section ngoài khoảng 3–6, id/slug trùng, và câu hỏi không kết thúc bằng dấu hỏi. Cái cuối bắt lỗi hay gặp nhất: người viết trượt từ câu hỏi sang tiêu đề mục lục, và mất luôn thứ làm nên mức 1.
Kệ đã soạn: content/books/packs/food.json, content/books/packs/drink.json (mỗi kệ 12 quyển).
Mức 3: ba thứ phải có
- Bìa, ảnh dọc, màu lấy từ token của
packages/design-system/theme.css. - Quiz, câu hỏi vào
legacy_ebook_quiz_questions; bắt buộc chạyscripts/balance_quiz_answers.pytrước khi sinh SQL, vì người viết (kể cả model) dồn đáp án đúng vào một vị trí. Đo thật 07.09.2026 khi soạn khoáedu-classroom-management: 19/20 câu ở cùng một vị trí, cả 9 lesson. - Rà soát, mỗi chương có trả lời được Essential Question của nó không; số liệu và tên thương hiệu có kiểm được không; giọng có phải giọng người trong nghề không. Sau khi CI áp, chạy
POST /ops/audit/:slug.
Bẫy: kệ trông rỗng mà không hỏng
Sách mức 1 và 2 để is_published = 0, nên /api/communities/:slug/books không trả về chúng. Một cộng đồng vừa soạn xong 12 quyển khung sẽ chỉ hiện các quyển đã ở mức 3. Đếm mức bằng SQL, đừng đếm bằng mắt trên trang:
SELECT outline_level, COUNT(*) FROM legacy_ebooks WHERE community_id='cm-ind-food' GROUP BY 1;Xem thêm: course-packs, known-risks, skill book-pack.
Quyển đầu tiên đi trọn ba mức: "Việc chỉ tôi làm được" (Founder)
Kệ Founder mở bằng một quyển đi hết đường: content/books/packs/founder.json (mức 1) → 20260909a khung → 20260909b/20260909c nội dung 15 section → 20260909d quiz và xuất bản. Đọc bốn file đó theo thứ tự là thấy đủ hình dạng của một quyển mức 3.
Ba điều rút ra khi làm quyển này, đáng nhớ cho quyển sau:
- Chẻ migration nội dung ngay từ lúc sinh. Gộp cả 15 section vào một file ra 74 KB, sát đúng mốc đã làm D1 bỏ dở giữa chừng ở kệ thư viện. Chẻ theo chương thành 44 KB + 31 KB.
section_targetlà cửa kiểm đếm được, hãy đếm thật. Chín section viết lần đầu ra 424–471 chữ, dưới mốc 500 do chính quyển đặt trongauthoring_meta_json. Không script nào bắt lỗi này; đếm bằng tay rồi bồi thêm cho đủ.- Kiểm "mở bằng Guiding Question" bằng SQL, đừng kiểm bằng mắt.
json_extract(content_portable, '$[0].children[0].text') LIKE guiding_question_vi || '%'chạy trên sqlite3 cho ra 15/15 trong vài giây.
Bìa: một ngoại lệ có ghi
Quyển này có bìa vector trong repo (com.conan.school/public/images/book-covers/…svg) và cover_url là đường dẫn cùng gốc, khác với quy ước chính là JPEG trên image.conan.school/books/covers/<slug>.jpg. Lý do: đường tải ảnh lên R2 đi qua giao diện admin và cần đăng nhập, còn tệp trong repo thì CI deploy được mà không cần bí mật nào. Đổi lại, bìa vector là hình vẽ phẳng chứ không phải ảnh chụp.
Màu trong SVG là giá trị bake thẳng từ theme.css, tệp trong /public nằm ngoài cây CSS của app nên không đọc được biến token. Đổi token thì phải sửa tay tệp đó; chỗ này là ngoại lệ, không phải mẫu để chép.
Trước đây quyển đã is_published = 1 khi cover_url còn NULL, và kệ hiện thẻ lùi (bg-accent + chữ cái đầu) thay vì bìa. Đó là trạng thái đã thiết kế sẵn ở BookCard, không phải lỗi, nhưng theo skill book-pack thì mức 3 đòi đủ bìa, quiz và rà soát, và bìa là thứ chưa có. cover_prompt đã nằm sẵn trong legacy_ebooks từ mức 1: tạo ảnh từ đúng câu đó rồi cập nhật cover_url, đừng viết lại prompt ở bước ấy.
Audit sách đi đường book-engine, không đi đường course
POST /ops/audit/:slug của course-intelligence tra courseBySlug, nên slug sách chỉ nhận 404. Sách dùng POST /api/book-engine/legacy-import/<slug> với {"persist": true} để dựng BookPackage, rồi POST /api/book-engine/packages/<id>/audit (hoặc /audit-runs để chạy qua workflow). Cả hai đều đòi đăng nhập, curl trần từ máy trả 401.
Danh mục và sách yêu thích (25.09.2026)
Năm danh mục dùng chung cho mọi cộng đồng: Vận hành · Tiền và giá · Khách và thương hiệu · Con người · Mở rộng. Mỗi quyển thuộc đúng một. Dùng chung chứ không đặt riêng từng cộng đồng, vì kệ nào cũng lặp cùng một khuôn (sổ tay vận hành, "Tiền của X", "Cơ sở thứ hai", "… được kể lại")
năm tên riêng cho mười tám kệ là 90 tên phải giữ cho nhất quán.
Nguồn sự thật:
api.conan.school/content/books/categories.json→scripts/book_categories_to_sql.py(--checktừ chối nếu số danh mục ngoài 3–5 hay có slug lạ). Bảngbook_categories, cộtlegacy_ebooks.category.Thêm quyển mới thì thêm id vào
bookscủa file đó. Quyển không có danh mục vẫn hiện ở "Tất cả" nhưng rơi khỏi mọi bộ lọc, không có gì báo lỗi./api/communities/:slug/bookstrả thêmcategories(chỉ những danh mục có sách trên kệ này) vàis_favoritecho từng quyển. Lọc ở trình duyệt; bộ lọc sống trong URL (?category=,?fav=1).Yêu thích:
POST /api/communities/:slug/books/:bookId/favorite{on}, bảngbook_favorites(user_idkhông khoá ngoại, cùng khuônjtbd_bookmarks). Không gác Pro, đánh dấu không mở nội dung nào, nhưng quyển phải nằm trên kệ của cộng đồng đó.
Quiz mức 3 bằng bộ biên dịch
scripts/book_quiz_to_sql.py <ebook_id> đọc content/books/quizzes/<ebook_id>.json, sinh quiz + câu hỏi + bật is_published. Nó từ chối khi một vị trí đáp án chiếm quá nửa số câu, balance_quiz_answers.py chỉ đọc CoursePack, không đọc được hình dạng "phương án dùng chung" của quiz sách. Quyển đầu tiên đi đường này: drink-book-11 "Quán nước có tên" (20 section, 5 quiz × 4 câu, phân bố 5/5/5/5).
Trang gom sách yêu thích
/books/favorites ở tokyo (mục "Sách yêu thích" trong sidebar, khai ở CẢ HAI danh sách của Sidebar.tsx) đọc GET /api/communities/me/book-favorites: mọi quyển đã bấm tim, từ mọi cộng đồng, nhóm theo cộng đồng. Thẻ sách là một component chung (components/community/BookCard.tsx) cho cả kệ và trang này. Bỏ tim ở trang gom không xoá thẻ ngay, để bấm nhầm còn bấm lại được.
Rà soát toàn kệ 25.09.2026
Đo trên D1 (156 quyển của 13 kệ, không tính 29 bản nháp ở chua-phan-loai):
| Hạng mục | Kết quả |
|---|---|
| Khung mức 1 (EQ, GQ, pains, meta, cover_prompt) | đủ ở cả 156 |
| Bìa | cả 156 có bìa SVG; 12 quyển có bìa vẽ riêng qua cover_url |
| Thân bài | 111/3.031 section có nội dung trước đợt này |
| Quiz | 155/156 quyển không có |
Sáu quyển *-book-01 đang publish | 6 section mỗi quyển, 230–415 chữ, dưới ngưỡng 500, không quiz: chưa đạt mức 3 thật |
Đã sửa trong đợt: sáu quyển -book-01 được bồi lên 500–650 chữ mỗi section (phần gốc giữ nguyên, thêm một tiểu mục có phép tính và đoạn việc phải làm) và có 3 quiz × 4 câu; drink-book-11 đi trọn ba mức. Nguồn nội dung nay nằm ở content/books/sections/<id>.json và content/books/quizzes/.
Bìa: 50/156 tên sách ngắt dòng để lại chữ mồ côi, phần lớn còn chẻ đôi từ ghép ("Tiền của trung / tâm", "Người trong hệ / thống"). Lỗi ở hàm wrap ngắt tham lam trong scripts/book_cover_svg.py; nay ngắt cân bằng, giữ từ ghép (COMPOUNDS), và nới bề ngang 4 ký tự khi điều đó xoá được mọi dòng một chữ. Bìa sinh lại ở 20260925m–y. Không script nào bắt được lỗi này, nó chỉ hiện khi chụp trang --preview ra mà nhìn.
Còn lại: ~2.900 section của 146 quyển mức 1. Viết tay trọn bộ không làm được trong một phiên; đường đúng là máy sinh (BookSectionDraftWorkflow), và chọn model là quyết định về tiền của người soạn.
Cả kệ lên mức 3 (26.09.2026)
148 quyển mức 1–2 của 13 kệ được viết tay trọn trong một phiên Claude Code, không qua API model nào (người soạn chọn đường này thay cho máy sinh). Mỗi quyển: 20 section 500–750 chữ, 5 quiz × 4 câu, bìa minh hoạ vector. Nhánh books/level3-all, migration 20260926b001–b149.
Sau đợt này, mọi quyển thật của 13 kệ ở mức 3 và is_published = 1. Kiểm trên sqlite dựng từ cấu trúc D1: 0 section rỗng, 2.960 câu quiz đều trỏ vào section có thật.
Bìa minh hoạ theo kiểu kệ Drink. Người soạn chọn kiểu bìa của kệ Drink (nền kem, một cảnh vật cụ thể của nghề, bốn màu token, không chữ) cho mọi quyển, thay bìa SVG thuật toán. Tệp ở com.conan.school/public/images/book-covers/<slug>.svg; lời dặn tạo từng bìa ở cover_prompt + cover_url trong pack của kệ (content/books/packs/<kệ>.json), gộp về đây 26.09.2026, thư mục covers/vector/ đã bỏ.
Cách làm, để lặp lại được. Một bản hướng dẫn chung cho mọi quyển (giọng, luật số liệu, khuôn section, quiz, bìa, cách chẻ migration), mỗi quyển một agent, chạy 4 quyển song song, commit theo kệ. Đọc báo cáo từng quyển trước khi commit: nhiều lỗi chỉ hiện ở đó (số của nhân vật không cộng khớp, chuỗi đáp án quiz lặp giữa các chương, lọt tên kỳ thi có thật).
Việc người soạn còn phải làm: đọc lại, đặc biệt các đoạn chạm pháp lý, thuế, an toàn thực phẩm, sức khoẻ (mỗi quyển đã đánh dấu trong báo cáo), rồi chạy audit book-engine từ phiên admin.
Bản tiếng Anh của thân bài và quiz (28.09.2026)
Trước đợt này nút EN trên trang sách vẫn bấm được và trang vẫn hiện, chỉ là thân bài và quiz vẫn tiếng Việt, không lỗi nào ở đâu. Giờ mỗi quyển có hai nguồn trong git:
| Thứ | Nguồn | Bộ biên dịch | Cột D1 |
|---|---|---|---|
| Thân bài | content/books/sections_en/<id>.json (cùng hình dạng bản Việt) | scripts/book_section_body_en_to_sql.py | legacy_ebook_sections.content_portable_en |
| Quiz | trường title_en, description_en, choices_en, q_en, why_en trong content/books/quizzes/<id>.json | scripts/book_quiz_to_sql.py <id> --en | *_en của legacy_ebook_section_quizzes và legacy_ebook_quiz_questions |
Cột mới ở migration 20260927d_books_en_columns.sql; hai cột JSON phải khai trong src/generated/legacyColumnTypes.ts, và lockEbookSection trả content_portable_en: null cho section bị khoá (cổng trả phí phải chặn cả bản Anh). Frontend chọn bản Anh khi lang === 'en' và bản Anh có nội dung, nếu không thì rơi về tiếng Việt, tokyo ở SectionView.tsx, www ở books.$slug.sections.$sectionId.tsx. Quiz bản Anh chỉ dùng choices_en khi độ dài khớp choices, vì correct_choice_index trỏ vào cùng một vị trí.
--check của bản Anh so với bản Việt: cùng tập section, cùng số tiểu mục và số đoạn, mọi chữ số trong bản Việt có mặt trong bản Anh, tiêu đề không phải câu hỏi, đoạn cuối mở bằng "What to do after reading this section". Phép so chữ số hiểu tên tháng (August = 8) và "5 p.m." (đếm cả 5 lẫn 17), trước khi thêm, người dịch phải viết "August (month 8)" để qua cửa. Xem known-risks.
Phép so chữ số bắt được thật: bốn lỗi số học/đánh máy trong bản Việt lộ ra nhờ người dịch phải khớp từng số (tiêu đề "15 ly" khi thân bài là 6 ly, "Mười 4 khách", "Con số 74" khi phép tính ra 73). Đã sửa ở 20260927b6.
Migration bản Anh: 20260928e000 (pilot) … e154, mỗi quyển một tiền tố, thân bài chẻ ≤ 45 KB theo câu lệnh. Sửa bản Anh rồi thì sinh lại bằng cách chạy bộ biên dịch và chẻ lại, đừng sửa tay file .sql.
Dọn sau đợt dịch (26.09.2026)
Người dịch phải đặt "What to do after reading this section" ở đoạn cuối, nên lộ ra 20 section bản Việt có đoạn "Việc phải làm" nằm giữa (13 ở fin-book-04, còn lại ở sal-book-02, mkt-book-02, home-book-11, fin-book-09). Đã đưa về cuối ở cả hai bản (20260929a1–a7). --check bản Việt chỉ đòi đoạn đó có mặt, không đòi nó ở cuối, cửa bản Anh chặt hơn cửa bản Việt, nên chính việc dịch là lần kiểm thứ hai cho bản Việt.
fin-book-07 ch04-s3 trông như sai số học (tiền điện 600 triệu ở chương 4, 250 triệu ở chương 3) nhưng đúng: 250 là 5 tháng đầu, 600 là cả năm. Đã ghi rõ phép tính ngay trong câu thay vì để người đọc tự nối hai chương.
Hai câu hỏi của một chương (06.10.2026)
Mỗi chương mang hai câu hỏi, vai khác nhau:
- Tên chương (
title_vi/_en) là Compelling Question: câu hỏi có độ căng, chạm một niềm tin phổ biến hay một nghịch lý của nghề, không có đáp án hiển nhiên. Việc của nó là kéo người đọc mở chương. Ví dụ: "Quán tôi thiếu người, hay thiếu một chỗ đứng?" - Essential Question (
essential_question_vi/_en): hẹp, đo được, người đọc trả lời được khi đọc xong. Ví dụ: "Trong hai tiếng đông nhất, chỗ tắc thật của quán tôi nằm ở đâu?"
Trước ngày này tên chương là cụm danh từ hoặc câu hỏi bỏ dấu, đọc lẫn với EQ. Cả 1.175 chương đã có tên mới; tên cũ giữ ở title_old_vi/_en. Nguồn: api.conan.school/content/books/chapter-titles/<community>.json, biên dịch bằng scripts/book_chapter_titles_to_sql.py (migration 20261018a00..a19). Chi tiết ở skill book-pack.
Lượt đọc lại cùng ngày sửa 143 tên (gần trùng trong kệ, khuôn câu lạm dụng, tiếng Anh gượng), migration 20261019a.
Ba quyển chưa phân loại đã xuất bản (06.10.2026)
WBS và Thang tư duy Bloom từng xuất bản khi mới ở mức 1 (9 section trống mỗi quyển); nay đủ mức 3: Guiding Question, thân bài VI+EN, 3 quiz × 4 câu (20261021a11..a25). The Founder Trap có thêm bản Anh cho 180 câu quiz (20261021a31); bản Việt của quiz đã viết lại thành 180 câu tình huống (20261022a1..a2).
26 quyển chưa phân loại đã xếp chỗ (06.10.2026): 23 quyển xuất bản ở bảy cộng đồng, kệ ceo-executive có quyển đầu tiên. Xem bảng.