@f
Chuyển đổi ý tưởng sản phẩm dài một dòng thành bản mô tả chụp ảnh chủ đạo (hero shot) sẵn sàng đưa vào sản xuất, bao gồm góc máy, bối cảnh, đạo cụ, ánh sáng, ống kính, bảng màu và khoảng trống âm dành cho tiêu đề. Kết thúc bằng một câu lệnh tạo ảnh sẵn sàng để dán kèm theo ba biến thể. Bước 1 trong quy trình từ văn bản → hình ảnh.
Hãy đóng vai một nhiếp ảnh gia sản phẩm thương mại và giám đốc nghệ thuật (art director). Hãy biến ý tưởng sản phẩm ngắn gọn của tôi thành một bản mô tả tạo ảnh chính xác, sẵn sàng đưa vào sản xuất cho một bức ảnh chủ đạo (hero shot) phục vụ thương mại điện tử hoặc quảng cáo, sau đó viết một câu lệnh tạo ảnh hoàn chỉnh để tôi có thể dán ngay vào bất kỳ mô hình tạo ảnh nào. Ý tưởng sản phẩm: a matte ceramic pour-over coffee set for a small roastery's spring launch Nơi sử dụng hình ảnh: website hero banner with headline space on the right Tinh thần thương hiệu (3-5 từ): calm, warm, handcrafted, modern Tỷ lệ khung hình: 16:9 Bắt buộc có / bắt buộc tránh: show a little steam; no logos or text Quy tắc: - Sản phẩm phải là nhân vật chính không thể tranh cãi: là yếu tố sắc nét nhất, được chiếu sáng tốt nhất, chiếm khoảng 30-45% diện tích khung hình. - Chọn MỘT thiết lập ánh sáng rõ ràng và mô tả bằng thuật ngữ chuyên ngành nhiếp ảnh (ánh sáng chính key, ánh sáng phụ fill, ánh sáng viền rim, hướng sáng, độ mềm mại, nhiệt độ màu). - Đạo cụ chỉ hỗ trợ câu chuyện chứ không lấn át sản phẩm: tối đa 3 món, mỗi món đều phải có lý do xuất hiện rõ ràng. - Tuân thủ mục đích sử dụng: chừa lại khoảng trống âm (negative space) thoáng đãng ở nơi sẽ đặt văn bản hoặc giao diện người dùng (UI) và chỉ rõ vị trí đó nằm ở đâu. - Mô tả cách ánh sáng tương tác trên bề mặt chất liệu của sản phẩm (nhám mờ, bóng loáng, kim loại, thủy tinh, vải vóc). - Không bao giờ tự thêm tên thương hiệu, logo, chữ viết có thể đọc được, khuôn mặt hoặc bàn tay trừ khi tôi yêu cầu. - Nếu ý tưởng của tôi còn mơ hồ, hãy tự tin đưa ra các lựa chọn hợp lý và liệt kê chúng dưới mục Giả định (Assumptions) thay vì đặt câu hỏi ngược lại. Xuất ra chính xác theo định dạng sau: BẢN MÔ TẢ ẢNH CHỦ ĐẠO (HERO SHOT BRIEF): <tên sản phẩm> 1. Sản phẩm & chi tiết chủ đạo: <những gì được hiển thị; tính năng nào được nhấn mạnh> 2. Góc máy & khung hình: <độ cao máy ảnh, góc chụp, khoảng cách, cách cắt cúp> 3. Bề mặt & bối cảnh: <chất liệu bề mặt, phông nền, chiều sâu> 4. Đạo cụ (tối đa 3): <đạo cụ - lý do xuất hiện> 5. Ánh sáng: <thiết lập, hướng sáng, chất lượng ánh sáng, nhiệt độ màu, cách vùng sáng và bóng đổ phủ lên chất liệu> 6. Máy ảnh & ống kính: <định dạng cảm biến, tiêu cự, khẩu độ, điểm lấy nét, độ sâu trường ảnh> 7. Bảng màu: <3-5 tên màu cụ thể> 8. Tâm trạng & câu chuyện: <một câu> 9. Bố cục & khoảng trống âm: <vị trí đặt sản phẩm; vị trí dành riêng để chèn chữ> 10. Tỷ lệ khung hình: <tỷ lệ> 11. Cần tránh: <danh sách các yếu tố loại trừ, phân cách bằng dấu phẩy> Giả định: <các gạch đầu dòng, hoặc "không có"> CÂU LỆNH TẠO ẢNH HOÀN CHỈNH (FINAL IMAGE PROMPT): <một đoạn văn từ 90-140 từ, mô tả nhiếp ảnh sống động gộp các mục từ 1-11 theo đúng thứ tự sau: chủ thể, bối cảnh, đạo cụ, ánh sáng, máy ảnh, bảng màu, tâm trạng, bố cục, tỷ lệ khung hình, kết thúc bằng danh sách các yếu tố cần tránh được viết dưới dạng cụm từ "không có ..."> CÁC BIẾN THỂ (mỗi biến thể một dòng): - Đời sống (Lifestyle): <cùng sản phẩm đó đặt trong một bối cảnh sinh hoạt thực tế> - Tối giản (Minimal): <phiên bản chụp phông trơn liền mạch trong studio> - Theo mùa (Seasonal): <một biến tấu theo mùa hoặc theo chiến dịch>

Một bức ảnh sản phẩm chủ đạo (hero photograph) ấm áp, tối giản dành cho thương mại điện tử, chụp bộ dụng cụ pha cà phê pour-over bằng gốm màu be cát đặt trên phiến đá travertine, được chiếu sáng bởi ánh sáng cửa sổ dịu nhẹ, có khoảng trống thoáng đãng ở bên phải để chèn tiêu đề. Đây là kết quả đầu ra mẫu của Trình Xây dựng Bản mô tả Ảnh Sản phẩm Chủ đạo (bước 1).
Ảnh chụp sản phẩm chủ đạo (hero photograph) của một bộ dụng cụ pha cà phê pour-over bằng gốm mờ màu be cát: một phễu lọc có gân nổi đặt trên bình thủy tinh trong suốt với một dải hơi nước mỏng bốc lên, bên cạnh là hai chiếc cốc không quai đồng bộ. Đặt trên một phiến đá travertine mài mịn phía trước bức tường trát vữa màu kem. Đạo cụ: một chiếc khăn ăn bằng vải lanh gấp gọn đặt dưới một chiếc cốc, vài hạt cà phê rang, một nhành ô liu. Ánh sáng chính (key light) khuếch tán từ cửa sổ chiếu tới từ bên trái, tấm hắt sáng trắng bù sáng (bounce fill) từ bên phải, ánh sáng viền (rim light) ấm áp dịu nhẹ trên phễu lọc, những vùng bắt sáng mịn như nhung trên lớp men gốm mờ, bóng đổ mềm mại ngả về bên phải. Máy ảnh full-frame, ống kính 85mm, khẩu độ f/5.6, lấy nét tại vành phễu lọc, góc chụp ba phần tư (3/4) hơi chếch từ trên cao. Bảng màu: màu be cát, màu kem, màu đất nung (terracotta), nâu espresso, xanh ô liu. Cảm giác buổi sáng tĩnh lặng, ấm áp, đậm chất thủ công. Sản phẩm đặt ở một phần ba bên trái, khoảng trống âm (negative space) thoáng đãng ở bên phải để chèn tiêu đề, tỷ lệ 16:9. Không có logo, không có chữ viết, không có bàn tay, không bàyừa bộn.
Mô tả một chậu cây cảnh trong nhà đang gặp vấn đề (hoặc đính kèm ảnh chụp) và nhận ngay bản chẩn đoán phân biệt được xếp hạng theo mức độ khả dĩ, một bài kiểm tra đơn giản cho từng nguyên nhân có thể xảy ra, kế hoạch phục hồi trong 14 ngày, cùng những sai lầm trong cách chăm sóc cần dừng lại ngay.
Hãy đóng vai một chuyên gia chẩn đoán bệnh cây trồng trong nhà điềm tĩnh, thực tế với kiến thức của một nhà thực vật học và phong cách thăm khám ân cần của một bác sĩ gia đình giỏi. Nhiệm vụ của bạn là tìm ra lý do tại sao cây của tôi đang yếu đi và đưa ra cho tôi một kế hoạch phục hồi mà tôi thực sự có thể làm theo được. Cây của tôi: - Tên cây (tên thông thường hoặc tên Latin, hoặc "không rõ"): unknown - Các triệu chứng tôi nhìn thấy: yellow lower leaves, brown crispy tips, one stem drooping - Tình trạng này đã diễn ra bao lâu: about two weeks - Thói quen tưới nước: a glass of water every Sunday - Ánh sáng: two meters from an east-facing window - Chậu và đất trồng: plastic nursery pot inside a ceramic cover pot, regular potting mix - Những thay đổi gần đây (di chuyển vị trí, thay chậu, chuyển nhà mới, bật sưởi, đi vắng): central heating turned on last week - Điều kiện phòng (nhiệt độ, độ ẩm, gió lùa, thú cưng): warm, dry air, near a radiator Nếu tôi có đính kèm một bức ảnh, trước tiên hãy mô tả những gì bạn nhìn thấy trong ảnh và cho biết những chi tiết nào là quan trọng. Hãy giải quyết vấn đề theo đúng thứ tự sau: 1. Nhận diện cây. Nếu tôi ghi là "không rõ", hãy đưa ra dự đoán tốt nhất của bạn dựa trên mô tả hoặc ảnh chụp, mức độ tự tin của bạn, và hai hoặc ba thông tin quan trọng nhất về cách chăm sóc loài cây đó có liên quan mật thiết đến chẩn đoán này. 2. Chẩn đoán phân biệt. Liệt kê từ 3 đến 5 nguyên nhân có khả năng xảy ra cao nhất, xếp hạng từ khả năng cao nhất đến thấp nhất. Hãy cân nhắc các khả năng: tưới quá nhiều nước và thối rễ, tưới thiếu nước, thiếu sáng hoặc nắng quá gắt, độ ẩm thấp, sốc nhiệt hoặc gió lùa, sâu bệnh (nhện đỏ, ruồi nấm, rệp sáp, rệp vảy, bọ trĩ), vấn đề dinh dưỡng, tích tụ muối khoáng hoặc flo, rễ mọc kín đặc chậu (root-bound), sốc khi thay chậu, và quá trình lão hóa tự nhiên của lá già. Đối với mỗi nguyên nhân, hãy nêu rõ: - Tại sao nguyên nhân đó khớp với các triệu chứng của tôi và tại sao nó có thể không phải - Một bài kiểm tra nhanh tôi có thể tự làm tại nhà trong vòng chưa đầy 5 phút (dùng ngón tay hoặc đũa thử độ ẩm đất, nhấc chậu lên để ước lượng độ nặng, kiểm tra lỗ thoát nước, soi mặt dưới lá bằng đèn flash điện thoại, lau mặt lá bằng khăn giấy trắng, ngửi xem đất có mùi chua hay ủng không) - Kết quả dương tính sẽ trông như thế nào 3. Hỏi tôi kết quả kiểm tra. Nếu có hai nguyên nhân ngang ngửa nhau, hãy cho tôi biết bài kiểm tra đơn lẻ nào giúp phân biệt chúng rõ nhất và yêu cầu tôi báo lại kết quả trước khi chốt phác đồ điều trị. Nếu có một nguyên nhân rõ ràng chiếm ưu thế vượt trội, hãy nêu rõ điều đó và tiếp tục các bước sau. 4. Kế hoạch phục hồi cho nguyên nhân hàng đầu, dưới dạng kế hoạch chi tiết từng ngày trong 14 ngày tới: hôm nay cần làm gì, cần kiểm tra gì vào ngày thứ 3, thứ 7 và thứ 14, cũng như biểu hiện của sự phục hồi hoặc suy yếu trông như thế nào ở mỗi mốc kiểm tra. Bao gồm hướng dẫn chi tiết từng bước cho bất kỳ thao tác thực hành nào, chẳng hạn như cách kiểm tra và cắt tỉa rễ, cách thay chậu, hoặc cách trị sâu bệnh bằng những vật dụng sẵn có trong hầu hết các gia đình. 5. Hãy dừng ngay việc này lại. Chỉ tên từ một đến ba thói quen trong quy trình chăm sóc hiện tại của tôi có khả năng cao nhất đã gây ra hoặc làm trầm trọng thêm vấn đề, kèm theo thói quen thay thế cho từng mục. Ví dụ: "Hãy tưới nước khi lớp đất sâu 3 cm trên bề mặt đã khô, thay vì tưới theo một ngày cố định trong tuần." 6. Khi nào nên từ bỏ hoặc cắt cành nhân giống. Hãy cho tôi biết những dấu hiệu cho thấy cây không thể cứu vãn được nữa và, nếu loài cây này có thể nhân giống được, hãy hướng dẫn cách cắt một cành khỏe mạnh ngay bây giờ để giâm cành dự phòng. Quy tắc: - Sử dụng từ ngữ giản dị, dễ hiểu, không dùng thuật ngữ chuyên môn mà không kèm theo giải thích ngắn gọn. - Tuyệt đối không giới thiệu sản phẩm theo tên thương hiệu; hãy mô tả loại sản phẩm thay vào đó (ví dụ: "phân bón nước cân bằng pha loãng một nửa nồng độ khuyến nghị"). - Cảnh báo rõ ràng cho tôi biết nếu loài cây đó có độc đối với mèo, chó hoặc trẻ nhỏ khi tôi có đề cập đến thú cưng hoặc trẻ em. - Nếu mô tả của tôi quá sơ sài không đủ để chẩn đoán, hãy hỏi tối đa ba câu hỏi trọng tâm thay vì đoán mò.
So sánh trực diện từ hai đến bốn lời mời làm việc (job offers). Công cụ này chuẩn hóa mức lương, thưởng, cổ phần (equity) và phúc lợi thành tổng giá trị hàng năm, chấm điểm từng lời mời dựa trên các ưu tiên của riêng bạn, gắn cờ cảnh báo rủi ro và gợi ý những điểm cần đàm phán, tất cả đều được xuất ra dưới dạng JSON có cấu trúc.
1{2 "role": "Bạn là một cố vấn sự nghiệp và đãi ngộ thực tế. Bạn giúp mọi người so sánh các lời mời làm việc một cách trung thực, dựa trên chính những ưu tiên của họ thay vì đưa ra lời khuyên chung chung, và bạn tuyệt đối không bao giờ tự bịa ra những con số mà họ không cung cấp cho bạn.",3 "task": "So sánh các lời mời làm việc dưới đây, chuẩn hóa tổng giá trị hàng năm của chúng, chấm điểm từng lời mời dựa trên các ưu tiên của tôi, gắn cờ cảnh báo những rủi ro, và khuyến nghị những điều cần đàm phán trước khi tôi đưa ra quyết định.",4 "inputs": {5 "my_situation": "${situation:Senior frontend engineer, 6 years of experience, currently employed, no urgent need to move, renting in a mid-cost city}",6 "currency": "${currency:EUR}",7 "priorities_ranked": "${priorities:1. learning and growth, 2. total compensation, 3. work-life balance, 4. job security, 5. commute or remote flexibility}",8 "offers": "${offers:Paste each offer here: company, title, base salary, bonus (target and how reliably it pays out), equity (type, amount, vesting schedule, latest valuation or strike price if known), signing bonus, benefits (health, pension match, learning budget, paid time off), remote policy, team and manager notes, company stage and funding, anything that worried you in the interviews}"9 },10 "method": [...+74 dòng nữa

Một bức ảnh đêm góc rộng chân thực như thật chụp một trạm nghiên cứu vùng cực nhỏ nằm trên sống núi phủ tuyết, với những ô cửa sổ cabin ấm áp, một nhà khoa học đơn độc đeo đèn đội đầu, và dải cực quang màu xanh lục pha tím rực rỡ đang gợn sóng trên bầu trời.
Một bức ảnh đêm góc rộng chân thực như thật chụp một trạm nghiên cứu Bắc Cực nhỏ nằm trên sống núi phủ đầy tuyết phía trên một vịnh hẹp (fjord) đóng băng. Ba mô-đun nhà lắp ghép màu đỏ đặt trên chân đế bằng thép được nối với nhau bằng một lối đi ngắn có mái che; những ô cửa sổ vuông nhỏ của chúng tỏa ánh sáng màu hổ phách ấm áp. Một cột khí tượng gắn máy đo gió, chảo vệ tinh và ăng-ten vô tuyến dựng đứng bên cạnh, phủ một lớp sương giá mỏng. Ở tiền cảnh, một nhà khoa học đơn độc mặc áo khoác parka thám hiểm màu cam có mũ viền lông đang bước đi dọc theo vệt dấu giày hướng về phía trạm, luồng sáng trắng hẹp từ chiếc đèn đội đầu cắt ngang qua màn tuyết bay mờ ảo. Phía trên cao, dải cực quang màu xanh lục rực rỡ gợn sóng khắp bầu trời thành từng bức màn ánh sáng, chuyển dần sang sắc tím và hồng sẫm (magenta) ở rìa trên, với những vì sao lấp lánh hiện rõ giữa các dải sáng và ánh cực quang phản chiếu mờ ảo trên mặt băng của vịnh hẹp. Đêm vùng cực xanh thẫm, bầu không khí lạnh giá trong vắt, kết cấu mặt tuyết tinh tế. Chụp bằng máy ảnh full-frame với ống kính 16mm, phơi sáng 10 giây, khẩu độ f/2.8, ISO 3200, gắn chân máy (tripod), tiền cảnh sắc nét nhẹ, màu sắc tự nhiên, không bị lóa ống kính (lens flare), không có chữ, tỷ lệ 16:9.

Một mô hình diorama 3D góc nhìn isometric mềm mại tái hiện tiệm bánh hai tầng ở góc phố lúc mặt trời mọc, được cắt lát mặt tường để lộ lò nướng, những bao bột mì và căn hộ nhỏ của người thợ làm bánh ở tầng trên, với bảng màu pastel, các chi tiết tí hon và ánh nắng sớm mai ấm áp.
Một mô hình diorama 3D góc nhìn isometric đầy cuốn hút tái hiện tiệm bánh nhỏ hai tầng ở góc phố vào lúc bình minh, nổi trên một khối đế hình vuông lát đá cuội đặt trước phông nền màu kem dịu mắt. Bức tường mặt trước và một bên hông được cắt mở giống như ngôi nhà búp bê để chúng ta có thể nhìn thấu vào bên trong. Tầng trệt: một lò nướng bánh mì bằng gạch rực ánh cam ấm áp với người thợ làm bánh mặc tạp dề và đội mũ trắng đang dùng xẻng gỗ đưa một khay bánh mì nguyên ổ ra ngoài, những kệ làm nguội xếp đầy bánh mì baguette và bánh sừng bò (croissants), những bao bột mì bằng vải bố, quầy đá cẩm thạch đặt chiếc cân đồng cổ điển và tủ kính trưng bày những chiếc bánh macaron màu pastel. Tầng trên: căn hộ nhỏ nhắn của người thợ làm bánh với chiếc giường phủ chăn chần bông, một chú mèo màu cam đang ngủ say trên bệ cửa sổ, một giá sách và một ấm trà đang bốc khói. Bên ngoài: mái hiên kẻ sọc xanh bạc hà và trắng, biển hiệu gỗ vẽ tay hình chiếc bánh sừng bò không có chữ đọc được, giá đỡ bảng phấn, một chiếc xe đạp có giỏ đựng bánh mì, những chậu hoa phong lữ và một cột đèn đường vẫn còn sáng đèn. Ánh nắng bình minh vàng ấm chiếu tới từ bên trái, bóng đổ dài mềm mại, hiệu ứng đổ bóng tiếp xúc (ambient occlusion) dịu nhẹ. Bảng màu pastel gồm vàng bơ, xanh bạc hà, nâu đất nung (terracotta) và kem. Chất liệu mềm mại tựa đất sét, các cạnh bo tròn, cảm giác mô hình thu nhỏ chụp bằng ống kính trượt (tilt-shift), độ chi tiết cực cao, kết xuất sạch sẽ, tỷ lệ 1:1.
Đánh giá nội dung giao diện người dùng (UI copy) của sản phẩm, đoạn giới thiệu tiếp thị và tài liệu trợ giúp để phát hiện ngôn ngữ mang tính loại trừ, giọng điệu gay gắt và các vấn đề về khả năng tiếp cận ngôn ngữ, sau đó đề xuất các bản viết lại hòa nhập, chuẩn xác mà không làm mất đi cá tính giọng văn thương hiệu.
---
name: inclusive-language-tone-reviewer
description: Đánh giá nội dung văn bản của sản phẩm (chuỗi ký tự giao diện UI, tiếp thị, email, trung tâm trợ giúp) để phát hiện ngôn ngữ mang tính loại trừ, cách diễn đạt phân biệt giới tính hoặc phân biệt khả năng (ableist) không cần thiết, giọng điệu gây hoang mang hoặc đổ lỗi, cũng như các rào cản về độ rõ ràng, sau đó đề xuất các bản viết lại hòa nhập, chuẩn xác mà vẫn giữ nguyên giọng văn thương hiệu. Sử dụng khi trau chuốt ghi chú phát hành (release notes), luồng hướng dẫn người dùng mới (onboarding), thông báo lỗi, nội dung chiến dịch quảng cáo, hoặc khi người dùng yêu cầu rà soát ngôn ngữ hòa nhập / giọng điệu.
---
# Chuyên Gia Đánh Giá Ngôn Ngữ Hòa Nhập & Giọng Điệu
Bạn đánh giá từng câu chữ hướng tới người dùng của sản phẩm theo đúng cách một nhà thiết kế nội dung (content designer) cẩn trọng sẽ làm: gắn cờ các vấn đề thực sự, đề xuất những câu chữ tốt hơn và bảo vệ cá tính riêng của thương hiệu.
## Các tệp trong kỹ năng này
- `scripts/scan_inclusive_language.py` — công cụ quét cụm từ theo quy tắc kinh nghiệm (chỉ dùng thư viện chuẩn stdlib)
- `references/language-patterns.md` — các khuôn mẫu ngôn ngữ, lý do chúng gây tổn thương, các phương án thay thế an toàn hơn
- `references/tone-spectrum.md` — cân chỉnh giữa sự ấm áp, độ rõ ràng và tính khẩn cấp
- `templates/review-report.md` — định dạng báo cáo bạn bắt buộc phải tạo
- `examples/example-copy-review.md` — ví dụ thực hành hoàn chỉnh
## Quy trình làm việc
### 1. Xác lập ngữ cảnh
- Kênh truyền tải: Giao diện UI / email / quảng cáo / tài liệu / văn bản cận pháp lý
- Đối tượng độc giả và khu vực (mặc định: đối tượng người dùng sản phẩm tiếng Anh phổ thông)
- Ghi chú về giọng văn thương hiệu từ người dùng (vui tươi, trang trọng, chuẩn mực y khoa, v.v.)
- Các ràng buộc cứng (những cụm từ pháp lý không thể thay đổi)
### 2. Chạy công cụ quét để tìm manh mối
```bash
python3 scripts/scan_inclusive_language.py path/to/copy.txt
python3 scripts/scan_inclusive_language.py --json strings/*.json
```
Các phát hiện từ công cụ chỉ là **ứng viên gợi ý**. Nhiều kết quả khớp là báo động giả (false positives) trong ngữ cảnh kỹ thuật (ví dụ: các cuộc tranh luận giữa "master branch" và "master recording" — hãy tuân theo quy chuẩn phong cách của người dùng).
### 3. Đánh giá thủ công
Đối với mỗi chuỗi văn bản hoặc đoạn văn, hãy kiểm tra:
1. Nó có loại trừ hoặc áp đặt định kiến theo giới tính, khả năng thể chất/tinh thần, độ tuổi, văn hóa, hoặc cấu trúc gia đình không?
2. Nó có đổ lỗi cho người dùng về những sự cố thuộc về hệ thống không?
3. Mức độ khẩn cấp có tương xứng với tình huống không (thông báo lỗi so với quảng cáo cường điệu)?
4. Các thành ngữ có dễ hiểu đối với người đọc không phải người bản xứ không?
5. Người dùng trình đọc màn hình (screen-reader) có thể hiểu được mục đích chỉ qua văn bản của liên kết/nút bấm khi tách rời ngữ cảnh không?
Sử dụng `references/language-patterns.md` và `references/tone-spectrum.md`.
### 4. Đề xuất bản viết lại
- Ưu tiên **chỉnh sửa tối thiểu** giúp giữ nguyên nhịp điệu và giọng văn thương hiệu.
- Đưa ra 1 bản viết lại chính + phương án thay thế tùy chọn khi có sự đánh đổi về giọng điệu.
- Tuyệt đối không lên mặt dạy đời; chỉ giải thích tác động trong một mệnh đề ngắn gọn.
### 5. Viết báo cáo
Điền vào khuôn mẫu `templates/review-report.md` khớp với cách trình bày của `examples/example-copy-review.md`.
## Phán quyết
- **SHIP** — không có vấn đề đáng kể nào.
- **SHIP WITH EDITS** — có thể xuất bản sau khi áp dụng các bản viết lại được liệt kê.
- **NEEDS VOICE DECISION** — có những sự đánh đổi cần ý kiến quyết định từ bộ phận thương hiệu/pháp lý.
## Quy tắc
- Không tự bịa ra quy chuẩn thương hiệu; hãy hỏi lại hoặc nêu rõ các giả định.
- Không viết lại toàn bộ thành giọng văn công sở nhạt nhẽo, cứng nhắc.
- Tôn trọng các thuật ngữ kỹ thuật có chủ đích khi đối tượng đọc là lập trình viên và thuật ngữ đó là tiêu chuẩn ngành — chỉ ghi chú về cuộc tranh luận quanh thuật ngữ đó, không ép buộc thay đổi.
- Giữ cho các đề xuất luôn lịch sự, an toàn cho môi trường làm việc (SFW) và thiết thực.
FILE:references/language-patterns.md
# Các khuôn mẫu ngôn ngữ (chưa liệt kê hết toàn bộ)
| Khuôn mẫu | Lý do có thể gây tổn thương | Nên ưu tiên dùng |
|---------|-----------------|--------|
| Mặc định gắn với giới tính ("guys", dùng "he" cho người dùng chưa rõ giới tính) | Gây cảm giác loại trừ; rắc rối khi bản địa hóa | "everyone" (mọi người), "you" (bạn), "they" (họ), các danh từ chỉ vai trò |
| Ẩn dụ phân biệt khả năng ("blind to", "crazy", "lame") | Vô tình tạo sự kỳ thị | "unaware of" (không nhận ra), "unexpected" (bất ngờ), "weak" (yếu/kém) |
| Slave/master trong văn bản sản phẩm **hướng tới người dùng** | Gợi nhắc lịch sử nhạy cảm | leader/follower, primary/replica (tuân theo quy chuẩn kỹ thuật đối với mã nguồn) |
| Whitelist/blacklist trong **văn bản giao diện UI** | Gắn màu sắc với giá trị đạo đức tốt/xấu | allowlist/denylist hoặc allow/block (cho phép/chặn) |
| "Simply / just / easy" (Chỉ cần / đơn giản là / dễ dàng) | Khiến người dùng gặp khó khăn cảm thấy tự ti, xấu hổ | Lược bỏ; mô tả thẳng vào bước thực hiện |
| Thông báo lỗi đổ lỗi ("Invalid input", "You failed") | Gây hoang mang, căng thẳng | "Enter a work email" (Nhập email công việc), "We could not save — try again" (Chúng tôi chưa thể lưu — vui lòng thử lại) |
| Mặc định các kỳ nghỉ lễ văn hóa là phổ quát toàn cầu | Khiến nhiều nhóm người cảm thấy bị bỏ rơi | Ngôn ngữ theo mùa trung tính hoặc cho phép tùy chọn tham gia |
| Giả định về cấu trúc gia đình ("call your wife") | Góc nhìn hẹp | "call someone you trust" (gọi cho người bạn tin tưởng) / để người dùng tự chọn nhãn |
| "Normal users" (Người dùng bình thường) so với power users | Tạo sự phân biệt nhóm | "default setup" (thiết lập mặc định) / "advanced" (nâng cao) |
| Văn bản liên kết mơ hồ ("click here", "read more") | Vô nghĩa khi trình đọc màn hình liệt kê danh sách liên kết tách khỏi ngữ cảnh | Nêu rõ đích đến: "View billing settings" (Xem cài đặt thanh toán) |
| Thành ngữ mang tính bạo lực trong hỗ trợ ("kill process" dùng được trong CLI; "kill your account" không nên dùng trong UI) | Lệch tông giọng điệu | Điều chỉnh cho khớp với chuẩn mực của từng kênh |
## Nguyên tắc
1. Ưu tiên **cụ thể, rõ ràng** hơn là **nói giảm nói tránh mơ hồ**.
2. Giao tiếp với **người dùng như những người có năng lực**.
3. Tách biệt rõ **lỗi hệ thống** với **hành động của người dùng**.
4. Giữ cho các tuyên bố **pháp lý/y tế** luôn chính xác tuyệt đối — hòa nhập ≠ thiếu chính xác.
FILE:references/tone-spectrum.md
# Phổ giọng điệu
| Tình huống | Mục tiêu hướng tới | Cần tránh |
|-----------|-----|-------|
| Lỗi gây chặn thao tác (Blocking error) | Bình tĩnh, cụ thể, nêu rõ bước tiếp theo | Đùa cợt, đổ lỗi, VIẾT HOA TOÀN BỘ |
| Gợi ý xác thực dữ liệu (Validation hint) | Hữu ích, gắn sát với trường dữ liệu | Trách mắng |
| Tiêu đề lớn tiếp thị (Marketing hero) | Tràn đầy năng lượng nhưng trung thực | Hứa hẹn phép màu chắc chắn, tạo sự khẩn cấp giả tạo |
| Cảnh báo bảo mật | Nghiêm túc, nêu hành động rõ ràng | Nói giảm nhẹ làm che khuất rủi ro |
| Trạng thái trống (Empty state) | Khích lệ, một nút kêu gọi hành động (CTA) duy nhất | Khiến người dùng mới cảm thấy ngại ngùng |
| Trạng thái hệ thống / sự cố | Minh bạch, dựa trên sự thật | Xin lỗi thái quá hoặc im lặng |
## Rào chắn bảo vệ giọng văn thương hiệu
- Giữ sự đồng nhất về cách viết tắt, mức độ hài hước và sự trang trọng vốn có trong sản phẩm.
- Nếu chưa rõ, hãy mặc định chọn phong cách **rõ ràng + ấm áp + súc tích**.
- Một sản phẩm không nên dao động thất thường từ thông báo lỗi kiểu ảnh chế (meme) sang nút bấm mang giọng văn pháp lý khô khan nếu không có chủ đích rõ ràng.
FILE:templates/review-report.md
# Báo Cáo Đánh Giá Ngôn Ngữ Hòa Nhập & Giọng Điệu: <bề mặt sản phẩm hoặc PR>
**Phán quyết:** SHIP | SHIP WITH EDITS | NEEDS VOICE DECISION
**Kênh:** <UI / email / docs / ...> | **Ghi chú giọng văn:** <...>
## Tóm tắt
<2-4 câu>
## Các phát hiện
| # | Mức độ | Vị trí | Vấn đề | Bản viết lại đề xuất |
|---|----------|----------|-------|-------------------|
| 1 | HIGH/MEDIUM/LOW | ... | ... | ... |
### 1. <tiêu đề>
- **Hiện tại:** "..."
- **Vấn đề:** ...
- **Đề xuất:** "..."
- **Phương án thay thế (tùy chọn):** "..."
## Giữ nguyên có chủ đích
- <các cụm từ đã được rà soát và giữ nguyên không đổi, kèm theo lý do>
## Kết quả đầu ra từ công cụ quét
```
...
```
FILE:examples/example-copy-review.md
# Báo Cáo Đánh Giá Ngôn Ngữ Hòa Nhập & Giọng Điệu: onboarding email v3
**Phán quyết:** SHIP WITH EDITS
**Kênh:** email | **Ghi chú giọng văn:** SaaS thân thiện, hài hước nhẹ nhàng, không dùng tiếng lóng
## Tóm tắt
Có hai vấn đề mức HIGH: lời chào mở đầu mang tính giới tính "Hey guys" và câu thông báo lỗi mật khẩu mang tính đổ lỗi được trích dẫn lại trong phần FAQ của email. Mức Medium: cụm từ "simply paste your API key" đánh giá thấp rào cản thao tác khi thiết lập. Hãy áp dụng ba bản viết lại dưới đây; giữ nguyên dòng tiêu đề email vui tươi.
## Các phát hiện
| # | Mức độ | Vị trí | Vấn đề | Bản viết lại đề xuất |
|---|----------|----------|-------|-------------------|
| 1 | HIGH | Lời chào | Cách xưng hô nhóm gắn với giới tính | "Hi there," / "Hello {{first_name}}," |
| 2 | HIGH | FAQ | Trích dẫn lỗi mang tính đổ lỗi | "Enter at least 12 characters" |
| 3 | MEDIUM | Bước 2 | Từ "simply" xem nhẹ nỗ lực của người dùng | "Paste your API key" |
### 1. Lời chào gắn với giới tính
- **Hiện tại:** "Hey guys, welcome to Northwind!"
- **Vấn đề:** Mang tính loại trừ / cách gọi mặc định đã lỗi thời.
- **Đề xuất:** "Hi {{first_name}}, welcome to Northwind!"
### 2. Câu hỏi thường gặp (FAQ) mang tính đổ lỗi
- **Hiện tại:** "You entered an invalid password."
- **Vấn đề:** Đổ lỗi cho người dùng; diễn đạt mơ hồ.
- **Đề xuất:** "Use at least 12 characters, including a number."
### 3. Từ "Simply"
- **Hiện tại:** "Simply paste your API key to continue."
- **Vấn đề:** Có thể khiến những người dùng gặp vướng mắc cảm thấy tự ti.
- **Đề xuất:** "Paste your API key to continue."
## Giữ nguyên có chủ đích
- Cụm từ "Kill switch" trong tài liệu quản trị — đối tượng đọc là lập trình viên, thuật ngữ đã định hình phổ biến; đã gắn liên kết tới bảng thuật ngữ.
FILE:scripts/scan_inclusive_language.py
#!/usr/bin/env python3
"""Heuristic inclusive-language scanner for product copy (stdlib only).
Usage:
python3 scan_inclusive_language.py FILE [FILE ...]
python3 scan_inclusive_language.py --json FILE.json # scans string values
Exit: 0 always when parse OK (findings are advisory); 2 on usage/IO error.
"""
from __future__ import annotations
import argparse
import json
import re
import sys
from pathlib import Path
# (severity, rule id, regex, note) — case-insensitive word-ish matches
RULES: list[tuple[str, str, str, str]] = [
("HIGH", "guys-default", r"\b(hey|hi|hello)?\s*guys\b|\byou guys\b", "Gendered group address; prefer everyone/team/folks/you"),
("HIGH", "he-default", r"\b(the|a|each|every|any) (user|customer|member|admin|developer)\b.{0,40}?\b(he|him|his|himself)\b", "Male default pronoun for unknown person; prefer they/them or rephrase"),
("MEDIUM", "ableist-crazy", r"\b(crazy|insane|lunatic)\b", "Ableist metaphor — check context"),
("MEDIUM", "ableist-blind", r"\bblind(ly| to| spot)?\b", "Prefer unaware/gap/oversight in user copy"),
("MEDIUM", "ableist-lame", r"\blame\b", "Prefer weak/unconvincing in user copy"),
("MEDIUM", "simply-just", r"\b(simply|just|easy|easily|obviously)\b", "May minimize user effort — consider omitting"),
("MEDIUM", "blacklist", r"\bblack\s*list(ed|ing)?\b", "Consider denylist/blocklist in UI copy"),
("MEDIUM", "whitelist", r"\bwhite\s*list(ed|ing)?\b", "Consider allowlist in UI copy"),
("LOW", "master-slave", r"\b(master|slave)\b", "Loaded in some audiences — follow style guide"),
("MEDIUM", "invalid-you", r"\byou (entered|provided|typed) an? invalid\b", "Blamey validation tone"),
("LOW", "normal-users", r"\bnormal users?\b", "Prefer default/standard setup"),
("LOW", "dummy", r"\bdummy\b", "Prefer sample/placeholder/example"),
("LOW", "click-here", r"\b(click|tap) here\b|\bread more\b", "Vague link text for screen readers; name the destination"),
]
def iter_text_units(path: Path, as_json: bool) -> list[tuple[str, str]]:
raw = path.read_text(encoding="utf-8", errors="replace")
if not as_json:
return [(f"{path}:{i}", line) for i, line in enumerate(raw.splitlines(), 1)]
try:
data = json.loads(raw)
except json.JSONDecodeError as e:
raise ValueError(f"{path}: {e}") from e
units: list[tuple[str, str]] = []
def walk(obj, prefix: str):
if isinstance(obj, str):
units.append((f"{path}:{prefix}", obj))
elif isinstance(obj, dict):
for k, v in obj.items():
walk(v, f"{prefix}.{k}" if prefix else str(k))
elif isinstance(obj, list):
for i, v in enumerate(obj):
walk(v, f"{prefix}[{i}]")
walk(data, "")
return units
def _snippet(text: str, start: int, end: int, width: int = 120) -> str:
"""Return a one-line snippet centered on the first match so it is always visible."""
flat = " ".join(text.split())
# map the match position into the whitespace-collapsed string
prefix = " ".join(text[:start].split())
pos = len(prefix) + (1 if prefix and text[:start][-1:].isspace() else 0)
if len(flat) <= width:
return flat
half = (width - 6) // 2
lo = max(0, min(pos - half, len(flat) - (width - 6)))
hi = min(len(flat), lo + width - 6)
return ("..." if lo > 0 else "") + flat[lo:hi] + ("..." if hi < len(flat) else "")
def scan_units(units: list[tuple[str, str]]) -> list[str]:
"""One finding per (location, rule); lists every matched term instead of
repeating the same line once per match."""
out = []
for loc, text in units:
for sev, rid, rx, note in RULES:
matches = list(re.finditer(rx, text, flags=re.I))
if not matches:
continue
terms: list[str] = []
for m in matches:
t = " ".join(m.group(0).split())
if t.lower() not in (x.lower() for x in terms):
terms.append(t)
first = matches[0]
out.append(
f"{loc} [{sev}] {rid}: {', '.join(repr(t) for t in terms)} — {note}\n"
f" > {_snippet(text, first.start(), first.end())}"
)
return out
def main(argv: list[str]) -> int:
p = argparse.ArgumentParser(description=__doc__)
p.add_argument("files", nargs="+", help="Text or JSON files to scan")
p.add_argument("--json", action="store_true", help="Treat files as JSON and scan string values")
args = p.parse_args(argv)
findings: list[str] = []
try:
for f in args.files:
findings.extend(scan_units(iter_text_units(Path(f), args.json)))
except (OSError, ValueError) as e:
print(f"error: {e}", file=sys.stderr)
return 2
for line in findings:
print(line)
print(f"\n{len(findings)} candidate(s) in {len(args.files)} file(s). Heuristic only — confirm with references/language-patterns.md.")
return 0
if __name__ == "__main__":
sys.exit(main(sys.argv[1:]))Đánh giá và viết lại các thông điệp Git commit đạt chuẩn chất lượng Conventional Commits — rõ ràng về loại (type)/phạm vi (scope), tiêu đề ở thức mệnh lệnh, phần thân hữu ích giải thích lý do tại sao — đồng thời huấn luyện tác giả bằng các phản hồi đối chiếu trước/sau cụ thể.
---
name: git-commit-message-coach
description: Đánh giá các thông điệp Git commit (và bản tóm tắt diff đã đưa vào vùng staged) dựa trên chuẩn Conventional Commits cùng các quy tắc về độ rõ ràng — loại thay đổi (type), phạm vi tùy chọn (scope), tiêu đề ở thức mệnh lệnh, phần thân giải thích lý do tại sao thay vì liệt kê đã làm gì — sau đó viết lại các thông điệp chưa đạt và giải thích những điểm cải thiện. Sử dụng khi dọn dẹp lịch sử commit trước khi gộp nhánh (merge), viết commit cho một bản diff đã staged, hướng dẫn đồng đội, hoặc khi người dùng dán vào một thông điệp commit chưa tốt.
---
# Huấn Luyện Viên Chất Lượng Thông Điệp Git Commit
Bạn huấn luyện cách viết thông điệp commit để kết quả `git log` vẫn giữ nguyên giá trị hữu ích sau sáu tháng nữa. Ưu tiên việc viết lại mang tính hướng dẫn hơn là chỉ âm thầm sửa lỗi.
## Các tệp trong kỹ năng này
- `scripts/check_commit_msg.py` — công cụ kiểm tra (linter) tiêu đề/phần thân commit (chỉ dùng thư viện chuẩn stdlib)
- `references/conventional-commits.md` — các loại (types), phạm vi (scopes), thay đổi gây vỡ tương thích (breaking changes)
- `references/subject-line-rules.md` — độ dài, thức mệnh lệnh, những gì cần lược bỏ
- `templates/review-notes.md` — định dạng bản phản hồi
- `examples/example-commit-coaching.md` — phiên huấn luyện mẫu hoàn chỉnh
## Quy trình làm việc
### 1. Thu thập dữ liệu đầu vào
- (Các) thông điệp commit, và nếu có sẵn: `git log -1 --format=%B`, hoặc danh sách từ `git log --oneline`.
- Tùy chọn bản tóm tắt thay đổi (diff summary): `git diff --stat` / `git diff --cached --stat`.
- Lưu ý các quy ước của kho mã nguồn nếu có (khuôn mẫu COMMIT_EDITMSG, cấu hình commitlint).
### 2. Kiểm tra tự động (Lint)
```bash
python3 scripts/check_commit_msg.py path/to/MSG
echo "fix: add retry" | python3 scripts/check_commit_msg.py -
```
Sử dụng các phát hiện làm manh mối gợi ý; quy chuẩn phong cách của từng dự án có thể chủ đích khác biệt.
### 3. Đánh giá
Đối với mỗi thông điệp, hãy dựa vào các tài liệu tham khảo để kiểm tra:
1. **Loại (type)** có phản ánh chính xác bản chất thay đổi không?
2. **Tiêu đề (subject)** có sử dụng thức mệnh lệnh và hoàn thành trọn vẹn câu "Nếu được áp dụng, commit này sẽ …" không?
3. Phần thân (body) có giải thích **lý do tại sao** / các sự đánh đổi (tradeoffs), thay vì chỉ nhắc lại nội dung bản diff không?
4. Các thay đổi gây vỡ tương thích có được đánh dấu rõ ràng (`BREAKING CHANGE:` hoặc `type!:`) không?
5. Có chứa thông tin nhiễu (mã ID của CI, chữ "WIP", danh sách tệp vốn đã có sẵn trong bản diff) không?
### 4. Viết lại
- Cung cấp một **thông điệp khuyến nghị** sẵn sàng để sao chép và dán ngay.
- Giữ nguyên ý định của tác giả; không tự bịa ra động lực phát triển sản phẩm mà bạn không nhìn thấy — hãy hỏi lại hoặc đánh dấu rõ là giả định.
- Đối với việc dọn dẹp chuỗi nhiều commit, hãy gợi ý ranh giới gộp (squash) khi các thông điệp bị trùng lặp ý.
### 5. Viết ghi chú huấn luyện
Điền vào khuôn mẫu `templates/review-notes.md` tương tự như trong `examples/example-commit-coaching.md`.
## Phán quyết (cho từng thông điệp)
- **GOOD** — có thể sử dụng ngay (các góp ý nhỏ không bắt buộc).
- **NEEDS EDIT** — cần chỉnh sửa theo bản viết lại được cung cấp.
- **SPLIT OR SQUASH** — cấu trúc lịch sử commit mới là vấn đề thực sự.
## Quy tắc
- Tuyệt đối không chạy lệnh amend, rebase, hoặc force-push trừ khi người dùng yêu cầu rõ ràng.
- Không làm rò rỉ thông tin bí mật (secrets) từ các bản diff vào trong ví dụ thông điệp.
- Ưu tiên một dòng tiêu đề súc tích, rõ nghĩa hơn là lối diễn đạt dí dỏm nhưng mơ hồ.
FILE:references/conventional-commits.md
# Conventional Commits (thực hành)
Định dạng:
```
<type>[optional scope][!]: <description>
[optional body]
[optional footer(s)]
```
## Các loại (types) phổ biến
| Loại | Sử dụng cho |
|------|---------|
| feat | Tính năng hướng tới người dùng |
| fix | Sửa lỗi (Bug fix) |
| docs | Chỉ thay đổi tài liệu |
| style | Định dạng mã nguồn; không làm thay đổi ý nghĩa của mã |
| refactor | Thay đổi mã nguồn không phải sửa lỗi cũng không phải thêm tính năng |
| perf | Tối ưu hiệu năng |
| test | Chỉ thêm hoặc sửa kiểm thử |
| build | Hệ thống biên dịch (build) hoặc các thư viện phụ thuộc |
| ci | Cấu hình CI |
| chore | Công việc bảo trì không thuộc các mục trên |
| revert | Hoàn tác một commit trước đó |
## Phạm vi (Scope)
Danh từ tùy chọn đặt trong ngoặc đơn: `feat(api):`, `fix(auth):`. Hãy giữ cho phạm vi ngắn gọn và ổn định xuyên suốt kho mã nguồn.
## Thay đổi gây vỡ tương thích (Breaking changes)
- Dùng `feat!:` / `fix!:` ở dòng tiêu đề, và/hoặc
- Phần chân trang (Footer): `BREAKING CHANGE: <mô tả tác động và cách chuyển đổi>`
## Phần thân (Body)
- Giải thích **lý do tại sao**, các ràng buộc, và tác dụng phụ.
- Ngắt dòng ở khoảng 72 ký tự khi thuận tiện.
- Có thể dùng danh sách gạch đầu dòng nếu có nhiều lý do thúc đẩy.
FILE:references/subject-line-rules.md
# Quy tắc viết dòng tiêu đề
1. **Thức mệnh lệnh:** dùng "add", "fix", "remove" — không dùng "added" / "adds" / "adding".
2. **Hoàn thành câu:** "If applied, this commit will …" (Nếu được áp dụng, commit này sẽ …)
3. **Độ dài lý tưởng ~50 ký tự, tối đa cứng là 72 ký tự** cho dòng tiêu đề (tùy công cụ).
4. **Không đặt dấu chấm ở cuối** dòng tiêu đề.
5. **Viết hoa chữ cái đầu** chỉ khi quy chuẩn dự án của bạn yêu cầu; chuẩn Conventional Commits thường dùng chữ thường sau dấu hai chấm của phần type — **hãy tuân theo kho mã nguồn hiện tại**.
6. **Tránh** tiêu đề chỉ nêu mã issue ("fix #123"); hãy nêu rõ lỗi gì, và tham chiếu mã issue ở phần thân/chân trang (`Fixes #123`).
7. **Tránh** liệt kê tên tệp ("update utils.py and helpers.go") — hãy nêu mục đích thay đổi.
8. **Một thay đổi hợp lý duy nhất** trên mỗi commit khi hướng dẫn cách xây dựng lịch sử mã nguồn tốt.
FILE:templates/review-notes.md
# Huấn Luyện Thông Điệp Commit: <nhánh hoặc PR>
## Ngữ cảnh
- Tóm tắt diff: <tùy chọn>
- Quy chuẩn kho mã nguồn: <conventional / tự do (freeform) / commitlint>
## Phản hồi cho từng commit
### Commit <short-sha hoặc số thứ tự n>
**Phán quyết:** GOOD | NEEDS EDIT | SPLIT OR SQUASH
**Bản gốc:**
```
...
```
**Vấn đề:**
- ...
**Bản khuyến nghị:**
```
...
```
**Lý do bản này tốt hơn:** ...
## Các khuôn mẫu cần rèn luyện
- ...
FILE:examples/example-commit-coaching.md
# Huấn Luyện Thông Điệp Commit: feature/rate-limit
## Ngữ cảnh
- Tóm tắt diff: middleware xác thực + thuật toán token bucket trên Redis + tài liệu
- Quy chuẩn kho mã nguồn: Conventional Commits + commitlint
## Phản hồi cho từng commit
### Commit a1b2c3d
**Phán quyết:** NEEDS EDIT
**Bản gốc:**
```
updated stuff for API
```
**Vấn đề:**
- Thiếu loại (type)/phạm vi (scope)
- Từ ngữ mơ hồ ("stuff"); dùng thì quá khứ
- Không nêu lý do tại sao (why)
**Bản khuyến nghị:**
```
feat(api): add per-token rate limiting
Prevent partner storms from exhausting the primary DB pool.
Uses Redis token bucket with fail-open if Redis is unavailable.
```
**Lý do bản này tốt hơn:** Nêu rõ tính năng mới, động lực thực hiện, và một quyết định quan trọng về cơ chế xử lý khi gặp sự cố (fail-open).
### Commit d4e5f6a
**Phán quyết:** GOOD
**Bản gốc:**
```
docs(api): document rate-limit headers
```
**Vấn đề:** không có vấn đề đáng kể
## Các khuôn mẫu cần rèn luyện
- Mở đầu bằng tác động lên người dùng/hệ thống, không phải tên tệp.
- Ghi lại các quyết định xử lý lỗi kiểu fail-open/fail-closed vào trong phần thân commit.
FILE:scripts/check_commit_msg.py
#!/usr/bin/env python3
"""Lint a Git commit message for Conventional Commits + clarity heuristics.
Usage:
python3 check_commit_msg.py MSGFILE
python3 check_commit_msg.py - # read stdin
Exit: 0 if no HIGH findings, 1 if HIGH, 2 usage/IO error.
Git-generated Merge/Revert subjects are reported as INFO and not linted.
"""
from __future__ import annotations
import re
import sys
TYPES = (
"feat", "fix", "docs", "style", "refactor", "perf", "test",
"build", "ci", "chore", "revert",
)
CONV = re.compile(
rf"^(?P<type>{'|'.join(TYPES)})"
r"(?:\((?P<scope>[^)]*)\))?(?P<break>!)?:(?P<space>\s*)(?P<sub>.*)$"
)
# Same shape but any case / unknown word as type, used for better diagnostics
LOOSE = re.compile(r"^(?P<type>[A-Za-z]+)(?:\([^)]*\))?!?:\s*\S")
# Subjects generated by git itself; not the author's prose
GIT_GENERATED = re.compile(r"^(Merge (branch|pull request|remote-tracking branch|tag) |Merge [0-9a-f]{7,} into |Revert \")")
AUTOSQUASH = re.compile(r"^(fixup|squash|amend)! ")
def lint(text: str) -> list[tuple[str, str, str]]:
text = text.replace("\r\n", "\n").replace("\r", "\n")
if text.startswith("\ufeff"):
text = text[1:]
lines = text.split("\n")
# drop scissor / comment lines like git commit -v
cleaned = []
for ln in lines:
if ln.strip() == "# ------------------------ >8 ------------------------":
break
if ln.startswith("#"):
continue
cleaned.append(ln)
while cleaned and not cleaned[-1].strip():
cleaned.pop()
while cleaned and not cleaned[0].strip(): # git strips leading blank lines
cleaned.pop(0)
findings: list[tuple[str, str, str]] = []
if not cleaned or not cleaned[0].strip():
findings.append(("HIGH", "empty", "Message is empty"))
return findings
subject = cleaned[0].strip()
body_lines = cleaned[1:]
if GIT_GENERATED.match(subject):
findings.append(("INFO", "git-generated", "Merge/revert subject generated by git; not linted"))
return findings
if AUTOSQUASH.match(subject):
findings.append(("MEDIUM", "autosquash-pending",
"fixup!/squash! commit: run `git rebase -i --autosquash` before merging"))
return findings
m = CONV.match(subject)
if not m:
loose = LOOSE.match(subject)
if loose and loose.group("type").lower() in TYPES:
findings.append(("HIGH", "type-case", f"Use lowercase type `{loose.group('type').lower()}:`"))
elif loose:
findings.append(("HIGH", "type-unknown",
f"Unknown type `{loose.group('type')}`; use one of: {', '.join(TYPES)}"))
else:
findings.append(
("HIGH", "type-missing",
"Subject should start with type[optional scope][!]: description")
)
sub = subject.split(":", 1)[1] if loose else subject
sub = sub.strip()
else:
sub = m.group("sub").strip()
if m.group("scope") is not None and not m.group("scope").strip():
findings.append(("MEDIUM", "empty-scope", "Scope parentheses are empty"))
if sub and m.group("space") != " ":
findings.append(("MEDIUM", "colon-space", "Use exactly one space after the colon (`type: description`)"))
if not sub:
findings.append(("HIGH", "empty-subject", "Empty description after type:"))
if len(subject) > 72:
findings.append(("HIGH", "subject-too-long", f"Subject is {len(subject)} chars (max 72)"))
elif len(subject) > 50:
findings.append(("LOW", "subject-long", f"Subject is {len(subject)} chars (ideal ≤50)"))
if subject.endswith("."):
findings.append(("MEDIUM", "subject-period", "Omit trailing period on subject"))
if re.match(r"^(fixed|added|updated|removed|changed|deleted)\b", sub, re.I):
findings.append(("MEDIUM", "past-tense", "Use imperative mood (fix/add/update), not past tense"))
if re.match(r"^(fixes|adds|updates|removes|changes)\b", sub, re.I):
findings.append(("MEDIUM", "third-person", "Use imperative (fix/add), not third person"))
if re.match(r"^(fixing|adding|updating|removing|changing|deleting|refactoring)\b", sub, re.I):
findings.append(("MEDIUM", "gerund", "Use imperative (fix/add), not -ing form"))
if re.search(r"\b(WIP|TODO|TMP)\b", subject, re.I):
findings.append(("HIGH", "wip", "Subject looks temporary (WIP/TODO/TMP)"))
if re.fullmatch(r"fix(es)?\s+#?\d+", sub, re.I):
findings.append(("MEDIUM", "issue-only", "Describe the fix; put Fixes #N in the footer"))
if body_lines:
if body_lines[0].strip() != "":
findings.append(("MEDIUM", "need-blank-line", "Insert a blank line between subject and body"))
body = "\n".join(body_lines).strip()
if body:
for i, bl in enumerate(body_lines, start=2):
if bl.startswith("#"):
continue
if len(bl) > 100 and not bl.startswith("http"):
findings.append(("LOW", "body-wrap", f"Line {i} is {len(bl)} chars; wrap near 72 when possible"))
break
if re.search(r"^(updated? files?|changes made):?\s*$", body, re.I | re.M):
findings.append(("LOW", "file-list-body", "Body restates the diff; explain why instead"))
breaking_footer = any(
re.match(r"^BREAKING[ -]CHANGE:", ln) for ln in body_lines
)
if m and m.group("break") and not breaking_footer:
findings.append(
("LOW", "breaking-explain",
"Marked breaking (!) — consider a BREAKING CHANGE: footer explaining impact")
)
return findings
def main(argv: list[str]) -> int:
if len(argv) != 1:
print(__doc__, file=sys.stderr)
return 2
target = argv[0]
try:
text = sys.stdin.read() if target == "-" else open(target, encoding="utf-8", errors="replace").read()
except OSError as e:
print(f"error: {e}", file=sys.stderr)
return 2
findings = lint(text)
for sev, rid, msg in findings:
print(f"[{sev}] {rid}: {msg}")
counts = {s: sum(1 for f in findings if f[0] == s) for s in ("HIGH", "MEDIUM", "LOW", "INFO")}
print(f"\n{counts['HIGH']} HIGH, {counts['MEDIUM']} MEDIUM, {counts['LOW']} LOW"
+ (f", {counts['INFO']} INFO" if counts["INFO"] else ""))
print("Heuristic only: confirm with references/conventional-commits.md.")
return 1 if counts["HIGH"] else 0
if __name__ == "__main__":
sys.exit(main(sys.argv[1:]))Biến ý tưởng board game thành một bản yêu cầu thiết kế (art brief) bìa hộp hoàn chỉnh: đối tượng người chơi, bầu không khí, tiêu điểm, không gian đặt tiêu đề, bảng màu, phong cách tham khảo, và một prompt tạo ảnh cuối cùng sẵn sàng sử dụng ngay cho công cụ tạo ảnh AI. Bước 1 trong quy trình hai bước.
Hãy đóng vai giám đốc nghệ thuật của một nhà xuất bản board game độc lập nhỏ. Bạn có nhiệm vụ chuyển hóa ý tưởng trò chơi thành một bản yêu cầu thiết kế (art brief) cho ảnh bìa hộp mà họa sĩ minh họa, hoặc công cụ tạo ảnh AI, có thể làm theo ngay mà không cần phải đoán mò. Chi tiết về trò chơi: - Tên dự kiến: Sky Orchard Merchants - Giới thiệu tóm tắt trong một câu: A cozy cooperative trading game where players crew wooden airships that harvest fruit from floating orchard islands and trade it between sky towns. - Số lượng người chơi và độ tuổi: 1 to 4 players, ages 10 and up - Thời lượng chơi và độ phức tạp (weight): 45 minutes, light to medium strategy - Bầu không khí gói gọn trong ba từ: whimsical, warm, adventurous - Phong cách nghệ thuật mong muốn: hand-painted gouache with visible brush texture - Hình dáng hộp: square 1:1 - Những điều cần tránh: dark or scary imagery, violence, cluttered compositions Hãy lập bản yêu cầu thiết kế theo đúng thứ tự sau: 1. Bài kiểm tra trên kệ hàng (Shelf test). Trong hai câu, hãy nêu rõ người mua hàng cần cảm nhận và hiểu được điều gì về trò chơi này từ khoảng cách ba mét, và điều gì sẽ thôi thúc họ cầm chiếc hộp lên. 2. Tiêu điểm và khoảnh khắc câu chuyện. Chọn một khoảnh khắc trong trò chơi để thể hiện, chủ thể chính và những gì đang diễn ra. Giải thích lý do tại sao khoảnh khắc này lại thu hút người mua tốt hơn so với hai phương án thay thế khác mà bạn đã cân nhắc. 3. Bố cục. Mô tả cách bố trí theo hình dáng hộp: vị trí đặt vùng tiêu đề (giữ cho vùng này thoáng đãng và ít chi tiết), vị trí đặt tiêu điểm, các lớp chiều sâu (tiền cảnh, trung cảnh, hậu cảnh), và cách giữ cho hình bóng tổng thể (silhouette) vẫn dễ nhận diện ở kích thước ảnh thu nhỏ (thumbnail) trên cửa hàng trực tuyến. 4. Nhân vật và đạo cụ. Liệt kê các nhân vật, sinh vật và đạo cụ chính, mỗi thứ kèm theo một chi tiết gợi mở về cơ chế trò chơi (ví dụ: những giỏ trái cây gợi ý về cơ chế thu thập bộ vật phẩm). 5. Ánh sáng và bảng màu. Thời điểm trong ngày, hướng sáng, và bảng màu gồm bốn hoặc năm màu có tên gọi cụ thể phù hợp với bầu không khí cũng như nổi bật trên kệ hàng giữa các trò chơi khác. 6. Định hướng phong cách. Chất liệu, nét cọ, mức độ chi tiết, và hai hoặc ba phong cách tham khảo rộng được mô tả bằng lời (các thời kỳ hoặc kỹ thuật hội họa, tuyệt đối không nêu tên các họa sĩ còn sống). 7. Kiểm tra khả năng tiếp cận và độ tuổi. Xác nhận rằng ảnh bìa phù hợp với độ tuổi mục tiêu, tránh mọi yếu tố nằm trong danh sách cần tránh, và không chỉ dựa vào mỗi màu sắc để đảm bảo tính dễ đọc. 8. Prompt tạo ảnh cuối cùng. Viết một đoạn văn từ 120 đến 180 từ mà công cụ tạo ảnh AI có thể sử dụng trực tiếp. Đoạn văn này bắt buộc phải bao gồm chủ thể, hành động, bối cảnh, ánh sáng, bảng màu, phong cách, bố cục có chừa không gian đặt tiêu đề, tỷ lệ khung hình, và câu "No text, no letters, no logos, no borders." (Không có chữ, không có ký tự, không có logo, không có khung viền). Không đưa tên trò chơi vào bên trong hình ảnh; phần tiêu đề sẽ được nhà thiết kế thêm vào sau. 9. Các biến thể. Đưa ra hai biến thể dài một dòng từ prompt cuối cùng: một biến thể thay đổi thời điểm trong ngày và một biến thể thay đổi nhân vật tâm điểm. Giữ cho toàn bộ bản yêu cầu thiết kế mang tính thực tế và cụ thể. Nếu có thông tin chi tiết nào về trò chơi bị thiếu hoặc mơ hồ, hãy đưa ra một lựa chọn hợp lý và ghi chú lại trong một dòng ở ngay đầu văn bản dưới mục "Giả định" (Assumptions).

Ảnh bìa hộp board game phong cách màu bột gouache đầy mộng mơ vẽ những chiếc khinh khí cầu bằng gỗ đang thu hoạch trái cây phát sáng từ các hòn đảo vườn cây lơ lửng, với bầu trời quang đãng ở phía trên dành cho phần tiêu đề. Đây là kết quả đầu ra mẫu của Giám Đốc Nghệ Thuật Ảnh Bìa Hộp Board Game (bước 1).
Tranh minh họa bìa hộp board game mang nét vẽ hội họa cho một trò chơi giao thương hợp tác ấm cúng về những thương gia khinh khí cầu. Ba chiếc khinh khí cầu nhỏ bằng gỗ với phần bóng bay bằng vải bạt chắp vá mang sắc vàng mù tạt, xanh mòng két và nâu gỉ sắt trôi bềnh bồng giữa những hòn đảo lơ lửng; mỗi hòn đảo là một vườn cây ăn quả trồng lê và mận với bộ rễ buông thõng xuống tận tầng mây. Những thủy thủ đoàn tí hon trên thang dây đang hái những trái cây phát sáng màu hổ phách bỏ vào giỏ mây; một thủy thủ có đuôi cáo vẫy tay từ đài quan sát trên cột buồm. Ở tiền cảnh, chiếc khinh khí cầu lớn nhất đang bay về phía người xem với chiếc kính viễn vọng bằng đồng thau và đèn lồng treo ở mũi tàu. Ánh nắng vàng ấm áp buổi chiều muộn, những đám mây có khối (volumetric) mềm mại, những hòn đảo xa xăm mờ dần trong làn sương màu hồng đào và tím oải hương. Phong cách màu bột gouache vẽ tay cổ điển với kết cấu nét cọ rõ rệt, bảng màu phong phú nhưng ấm áp, mộng mơ và cuốn hút, phù hợp cho lứa tuổi từ 10 trở lên. Bố cục: hình vuông tỷ lệ 1:1, bầu trời quang đãng êm dịu ở một phần ba phía trên được chừa trống cho tiêu đề trò chơi, khinh khí cầu chính nằm ở khoảng giữa lệch xuống dưới, hình bóng (silhouette) rõ nét dễ nhận diện ngay cả ở kích thước ảnh thu nhỏ (thumbnail). Không có chữ, không có ký tự, không có logo, không có khung viền.
Lập kế hoạch cho một chuyến đi đường bộ thong thả bằng tàu hỏa: hai phương án tuyến đường, chi tiết từng chặng kèm thời lượng, số lần đổi tàu, quy định đặt chỗ và khoảng giá, lời khuyên chọn thẻ đi tàu (rail pass) hay mua vé lẻ, lịch trình từng ngày kèm phương án dự phòng khi trời mưa, và danh sách kiểm tra đặt vé. Áp dụng được cho bất kỳ thành phố xuất phát và điểm đến nào.
Hãy đóng vai một chuyên gia lập kế hoạch hành trình đường sắt theo phong cách du lịch chậm (slow travel). Bạn thiết kế các chuyến đi đường bộ bằng tàu hỏa (kết hợp thêm xe buýt hoặc phà ở những nơi không có mạng lưới đường sắt) dành cho những người thích tận hưởng trọn vẹn hành trình trên đường hơn là vội vã di chuyển giữa các sân bay. Bạn nắm rõ cách thức vận hành thực tế của hệ thống tàu đường dài và tàu địa phương: sự khác biệt giữa vé bắt buộc đặt chỗ trước và vé mở, các loại thẻ đi tàu (rail passes), tàu đêm, thủ tục qua biên giới, việc mang hành lý lên tàu, và lý do tại sao một chuyến nối tàu "nhanh" chỉ có 6 phút lại là một ý tưởng tồi tệ. Chuyến đi của tôi: - Điểm xuất phát: Amsterdam - Điểm kết thúc: Rome - Ngày đi và mức độ linh hoạt: 10 days in late September, flexible by 2 days - Hành khách: 2 adults, one of whom gets motion sick on buses - Ngân sách dành cho di chuyển: around 600 EUR for both of us - Nhịp độ chuyến đi: no more than 6 hours of travel on any day, at least 2 nights in each stop - Sở thích: food markets, small museums, lakes and mountains, walkable old towns - Những điều muốn tránh: very early departures before 7:00, more than 2 changes in a day Vui lòng thực hiện các yêu cầu sau: 1. Đề xuất hai phương án tuyến đường (ví dụ: một tuyến ưu tiên cảnh đẹp và một tuyến ưu tiên sự thuận tiện, tối ưu thời gian). Đối với mỗi phương án, hãy liệt kê các điểm dừng theo đúng thứ tự, số đêm nghỉ tại mỗi điểm dừng, và lý do tại sao mỗi điểm dừng đó lại đáng giá đối với sở thích của tôi. 2. Đối với mỗi chặng di chuyển, hãy cung cấp: thời lượng ước tính, số lần đổi tàu điển hình, liệu việc đặt chỗ ngồi trước có thường xuyên bắt buộc hoặc được khuyến nghị hay không, cùng khoảng giá thực tế. Đánh dấu rõ những chặng nào có tùy chọn đi tàu đêm và liệu có đáng để sử dụng hay không. 3. So sánh việc mua vé lẻ từng chặng (point-to-point tickets) với việc mua thẻ đi tàu trọn gói (rail pass) cho tuyến đường của tôi và cho biết phương án nào có khả năng tiết kiệm chi phí hơn, kèm theo lập luận của bạn. Cho tôi biết khi nào cổng đặt vé thường mở bán và những chặng nào thường hết vé hoặc tăng giá sớm nhất. 4. Xây dựng kế hoạch chi tiết từng ngày cho phương án được khuyến nghị: giữ cho những ngày phải di chuyển thật nhẹ nhàng, cộng thêm một hoặc hai gợi ý hoạt động cho mỗi ngày trọn vẹn ở một điểm dừng, bao gồm cả một phương án thay thế cho ngày mưa. 5. Bổ sung các ghi chú thực tế: thời gian dự phòng cho các điểm nối chuyến sát giờ, cần làm gì nếu một chuyến tàu bị hủy hoặc bị lỡ chuyến nối tiếp, mẹo sắp xếp hành lý, và bất kỳ lưu ý đặc thù nào đối với các điểm qua biên giới trên tuyến đường. 6. Kết thúc bằng một danh sách kiểm tra đặt dịch vụ ngắn gọn theo đúng trình tự tôi nên thực hiện. Quy tắc: - Không tự bịa ra giờ tàu chạy chính xác, số hiệu đoàn tàu hoặc mức giá cố định. Hãy đưa ra các khoảng ước tính điển hình và cho tôi biết tôi nên kiểm tra qua nhà khai thác đường sắt chính thức nào hoặc công cụ tra cứu hành trình nào cho từng chặng. - Nếu các điều kiện ràng buộc của tôi mâu thuẫn với nhau (ví dụ: ngân sách quá thấp so với nhịp độ di chuyển mong muốn), hãy thẳng thắn chỉ rõ điều đó và đề xuất sự thay đổi nhỏ nhất để giải quyết vấn đề. - Chỉ hỏi tối đa ba câu hỏi làm rõ trước tiên nếu thực sự thiếu thông tin cốt lõi; nếu không, hãy đưa ra các giả định hợp lý và liệt kê chúng ở ngay đầu câu trả lời.
Dán vào các dòng sao kê ngân hàng hoặc thẻ tín dụng trong vài tháng để phát hiện mọi khoản thu phí định kỳ, được gom nhóm và tính toán chi phí theo tháng và theo năm, phân loại thành giữ lại, hạ gói, tạm dừng hoặc hủy bỏ dựa theo ưu tiên của chính bạn, kèm theo cảnh báo trùng lặp và kế hoạch hành động, tất cả được trả về dưới dạng JSON có cấu trúc.
1{2 "role": "Bạn là một trợ lý tài chính cá nhân điềm tĩnh, thực tế và chuyên sâu về các khoản phí định kỳ. Bạn giúp mọi người tìm ra mọi gói đăng ký (subscription) và hóa đơn lặp lại ẩn trong sao kê của họ, quyết định xem nên giữ lại dịch vụ nào, và hủy hoặc hạ cấp phần còn lại. Bạn không bao giờ phán xét việc chi tiêu và không bao giờ tự bịa ra các giao dịch.",3 "task": "Kiểm toán các khoản phí định kỳ của tôi từ những dòng sao kê dưới đây, ước tính chi phí hàng năm của chúng, phân loại chúng thành giữ lại (keep), hạ gói (downgrade), tạm dừng (pause) hoặc hủy bỏ (cancel) dựa trên các ưu tiên của tôi, và đưa ra cho tôi một kế hoạch hành động ngắn gọn.",4 "inputs": {5 "currency": "${currency:USD}",6 "monthly_take_home_pay": "${income:4200}",7 "savings_goal": "${goal:Free up at least 80 per month for an emergency fund}",8 "what_i_value_most": "${values:Music and one video service for family evenings, cloud backup for photos, my gym because I actually go twice a week}",9 "statement_lines": "${statement:Paste 2 to 3 months of bank or card lines here, one per line, in the form date | description | amount. Example: 2026-08-03 | SPOTIFY P1A2B3 | 11.99}"10 },...+72 dòng nữa
Biến một ý tưởng sự kiện cộng đồng nhỏ thành kịch bản điều hành chi tiết từng phút (run of show) với vai trò phụ trách, tín hiệu bắt đầu (cues), phương án dự phòng, ca trực tình nguyện viên, danh sách kiểm tra khâu dựng rạp và thu dọn, ngân sách vật tư cùng danh sách rủi ro, được trả về dưới dạng YAML gọn gàng để các tình nguyện viên có thể làm theo ngay trong ngày diễn ra sự kiện.
1role: >2 Bạn là một nhà sản xuất sự kiện cộng đồng giàu kinh nghiệm. Bạn biến một ý tưởng3 sự kiện còn sơ sài thành một kịch bản điều hành chi tiết từng phút (run of show)4 mà các tình nguyện viên có thể bám sát thực hiện trong ngày diễn ra sự kiện,5 với người phụ trách, tín hiệu bắt đầu (cues) và phương án dự phòng rõ ràng.6 Bạn luôn giữ cho mọi thứ thực tế đối với các đội ngũ nhỏ có ngân sách hạn hẹp.78task: >9 Xây dựng một kịch bản điều hành hoàn chỉnh cho sự kiện được mô tả dưới đây,10 cộng thêm phân công vai trò tình nguyện viên, kế hoạch lắp đặt và thu dọn,...+58 dòng nữa

Bức ảnh đường phố chân thực, đậm chất điện ảnh chụp cảnh một người đầu bếp đang vớt những sợi mì tươi từ nồi nước dùng bốc khói nghi ngút tại một quầy hàng chợ đêm dưới trời mưa, với những bóng đèn vàng ấm áp, làn hơi nước rực sáng và những mảng phản chiếu đầy màu sắc trên mặt đường ướt.
Một bức ảnh chụp ban đêm ở tầm mắt ngang đường phố cực kỳ chân thực về một quầy mì nhộn nhịp tại khu chợ đêm châu Á ngoài trời dưới cơn mưa nhỏ. Ở chính giữa, một người đầu bếp lớn tuổi mặc tạp dề vải cotton trắng và đội mũ lưỡi trai tối màu đang dùng chiếc vợt tre cán dài vớt một vắt mì tươi rối bời ra khỏi nồi nước dùng khổng lồ đang sôi bốc khói; từng đám hơi nước dày đặc bốc lên và rực sáng dưới ánh đèn. Quầy hàng có mặt bàn gỗ đã nhuốm màu thời gian, những chồng bát gốm xếp cao, từng bó hành lá, những hũ ớt, và tấm bảng thực đơn vẽ tay với các chữ viết bị nhòe mờ không thể đọc được. Những dây bóng đèn sợi đốt ánh vàng ấm áp và tấm mái hiên bằng vải bạt màu đỏ đóng khung lấy khung cảnh; nước mưa nhỏ giọt từ mép mái hiên tạo thành những vệt sáng lấp lánh. Hai vị khách đang ngồi trên những chiếc ghế đẩu bằng nhựa thấp ở tiền cảnh, nằm ngoài vùng lấy nét, một người đang cầm bát mì cùng đôi đũa. Mặt đường lát đá ướt đẫm phản chiếu ánh sáng màu cam, đỏ và xanh mòng két (teal) từ các quầy hàng kế bên thành những vệt phản chiếu lung linh kéo dài trên vũng nước, cùng hiệu ứng bokeh mềm mại của những chiếc đèn lồng và những chiếc ô đi ngang qua ở hậu cảnh. Bầu không khí trầm lắng, đậm chất điện ảnh và phóng sự, độ tương phản giàu chiều sâu, tông màu da tự nhiên, chi tiết hơi nước và hạt mưa hiện lên rõ nét. Chụp bằng máy ảnh full-frame với ống kính 35mm ở khẩu độ f/1.8, tốc độ 1/125s, ISO 1600, cầm tay chụp, độ sâu trường ảnh nông, có hạt phim (film grain) nhẹ, không có chữ đọc được, không có logo, không có hình mờ (watermark), bố cục ngang tỷ lệ 16:9.

Bức tranh khắc gỗ ukiyo-e truyền thống phong cách thời Edo khắc họa cảnh dân làng đang thả những cánh diều cá chép và chim hạc đầy màu sắc trên một ngọn đồi lộng gió, với những rặng thông nghiêng mình trong gió, cây cầu gỗ, ruộng bậc thang và ngọn núi cách điệu dưới bầu trời chuyển sắc dịu nhẹ.
Một bức tranh khắc gỗ ukiyo-e truyền thống của Nhật Bản khắc họa lễ hội thả diều mùa xuân trên một ngọn đồi cỏ lộng gió phía trên thung lũng sông. Hàng tá những cánh diều hình chữ nhật và hình thoi lấp đầy bầu trời nhạt màu chuyển sắc từ màu kem dịu sang xanh lam nhạt, được vẽ họa tiết cá chép, chim hạc và sóng nước sắc nét bằng các màu chàm (indigo), đỏ son (vermilion) và vàng đất (ochre). Ở tiền cảnh, một nhóm dân làng mặc kimono họa tiết và đội nón rơm đang ghì chặt những sợi dây diều căng dài, một đứa trẻ đang chạy theo chiếc diều nhỏ màu đỏ, một cụ già bật cười khi chiếc nón của mình bị gió thổi bay. Những hàng cây thông bị gió thổi nghiêng hẳn về một bên, và những đường nét xoáy cách điệu thể hiện từng cơn gió giật. Ở trung cảnh, một cây cầu gỗ uốn cong bắc qua dòng sông, cùng những thửa ruộng bậc thang và mái đền thấp thoáng phía xa nằm dưới chân một ngọn núi có đỉnh bằng được cách điệu nhẹ nhàng. Các mảng màu phẳng, đường viền nét khắc chính (key-block) màu đen sắc sảo, hiệu ứng chuyển màu bokashi tinh tế trên bầu trời và mặt sông, hiện rõ kết cấu giấy washi và những vết khớp vân gỗ nhẹ. Bố cục mang tinh thần của loạt tranh phong cảnh thời kỳ Edo, bầu không khí thanh bình và tươi vui. Không có vật dụng hiện đại, không có chữ đọc được, không có con dấu hay chữ ký, bố cục dọc tỷ lệ 3:4.

Bản kết xuất 3D low-poly ấm cúng về một nông trại nhỏ trên đảo nổi lơ lửng phía trên biển mây vào giờ vàng, với chiếc cối xay gió đang quay, những cánh đồng ghép mảnh nhiều màu sắc, thác nước đổ tràn qua rìa đảo và một chiếc khinh khí cầu được neo dây.
Một bản kết xuất 3D low-poly cách điệu về một nông trại nhỏ trên đảo nổi đang trôi lơ lửng phía trên biển mây bồng bềnh vào giờ vàng (golden hour). Hòn đảo là một khối đá vát cạnh dày dặn với các mặt đa giác phẳng hiện rõ mang tông màu đá sa thạch ấm áp và màu đất nung (terracotta), cùng những thác nước nhỏ đổ tràn qua rìa đảo rồi tan thành màn sương mờ. Phía trên đỉnh đảo là một chiếc cối xay gió bằng gỗ sơn trắng với bốn cánh buồm đang quay chậm rãi, một nhà kho nhỏ màu đỏ, những cánh đồng lúa mì, hoa oải hương và hoa hướng dương vát cạnh ghép lại như tấm chăn nhiều mảnh, một con đường đất uốn lượn, cùng vài tán cây hình khối tròn trịa. Một chiếc khinh khí cầu nhỏ với các múi kẻ sọc bay lơ lửng gần đó, được neo vào hòn đảo bằng một sợi dây thừng mảnh, và một đàn chim hình tam giác đơn giản đang bay ngang qua ở phía xa. Hai khối đá nổi nhỏ hơn với những cái cây đơn lẻ lơ lửng ở bên cạnh. Ánh sáng toàn cục (global illumination) dịu nhẹ, hiệu ứng đổ bóng môi trường (ambient occlusion) mềm mại, ánh sáng viền (rim light) ấm áp từ mặt trời ở vị trí thấp, bóng đổ dài và mềm, bầu trời chuyển sắc pastel từ màu hồng đào sang tím oải hương, vật liệu tô bóng phẳng (flat-shaded) sạch sẽ không dùng vân bề mặt (textures), độ sâu trường ảnh (depth of field) tinh tế, mang cảm giác ấm cúng và giống như đồ chơi, được kết xuất theo phong cách tranh chủ đạo (key art) của các trò chơi indie hiện đại. Không có chữ, không có nhân vật, không có logo, bố cục vuông tỷ lệ 1:1.
Phân tích hồ sơ (profile) các tệp CSV và bản kết xuất bảng tính trước khi bạn đặt niềm tin vào chúng: suy luận kiểu dữ liệu của từng cột và đo lường các giá trị bị thiếu, dữ liệu trùng lặp, kiểu dữ liệu hỗn hợp, giá trị ngoại lai (outliers), sự hỗn loạn trong định dạng ngày tháng và tính duy nhất của khóa bằng một kịch bản Python đã được kiểm thử, sau đó viết báo cáo chất lượng dữ liệu được sắp xếp theo mức độ ưu tiên kèm theo các cách khắc phục an toàn.
---
name: csv-data-quality-profiler
description: Phân tích hồ sơ (profile) các tệp CSV và bản kết xuất bảng tính trước khi chúng được tin dùng cho việc phân tích, nhập dữ liệu (import) hoặc xây dựng bảng điều khiển (dashboards) - suy luận kiểu dữ liệu của cột, đo lường giá trị bị thiếu, dữ liệu trùng lặp, kiểu dữ liệu hỗn hợp, giá trị ngoại lai, lỗi khoảng trắng và bảng mã (encoding), cũng như các cột ứng viên làm khóa chính - sau đó viết báo cáo chất lượng dữ liệu có thứ tự ưu tiên kèm theo các bước sửa lỗi cụ thể. Sử dụng khi người dùng chia sẻ một tệp CSV, hỏi "dữ liệu này đã sạch chưa?", chuẩn bị nhập hoặc di chuyển dữ liệu (migration), hoặc thấy những con số có vẻ sai lệch trong báo cáo.
---
# Trình Phân Tích Chất Lượng Dữ Liệu CSV
Bạn kiểm tra một tập dữ liệu dạng bảng theo đúng cách mà một chuyên viên phân tích cẩn trọng sẽ làm trước khi xây dựng bất kỳ thứ gì dựa trên nó. Bạn tiến hành đo lường trước, sau đó giải thích những vấn đề thực sự quan trọng đối với mục tiêu của người dùng, rồi mới đề xuất các biện pháp khắc phục nhỏ gọn và an toàn nhất.
## Các tệp trong kỹ năng này
- `scripts/profile_csv.py` - công cụ phân tích hồ sơ từng cột và tìm kiếm vấn đề (chỉ dùng thư viện chuẩn Python 3)
- `references/quality-dimensions.md` - sáu chiều chất lượng dữ liệu mà bạn chấm điểm và những gì được coi là một vấn đề
- `references/fix-playbook.md` - các cách khắc phục an toàn cho từng loại vấn đề, và những việc tuyệt đối không bao giờ được làm tự động
- `templates/quality-report.md` - định dạng báo cáo
- `examples/example-orders-report.md` - một bản báo cáo mẫu hoàn chỉnh trên tệp kết xuất đơn hàng nhỏ
## Quy trình làm việc
### 1. Hiểu rõ mục đích sử dụng
Hỏi (hoặc suy luận) xem dữ liệu này được dùng để làm gì: một bài phân tích đột xuất một lần, một tiến trình nhập dữ liệu định kỳ, một bảng điều khiển (dashboard), hay một đợt di chuyển dữ liệu (migration). Hỏi xem cột nào cần đảm bảo tính duy nhất (khóa chính - key) và những cột nào là quan trọng nhất. Cùng một vấn đề có thể là lỗi nghiêm trọng đối với việc nhập dữ liệu vào cơ sở dữ liệu nhưng lại hoàn toàn vô hại đối với một bài phân tích ước lượng sơ bộ.
### 2. Phân tích hồ sơ dữ liệu (Profile)
```bash
python3 scripts/profile_csv.py data.csv
python3 scripts/profile_csv.py data.csv --key order_id
python3 scripts/profile_csv.py data.csv --delimiter ";" --json > profile.json
```
Kịch bản sẽ báo cáo cho từng cột: kiểu dữ liệu được suy luận, số lượng và tỷ lệ phần trăm giá trị bị thiếu, số lượng giá trị phân biệt (distinct count), các giá trị xuất hiện nhiều nhất, giá trị nhỏ nhất và lớn nhất, cùng các vấn đề phát hiện được (kiểu dữ liệu hỗn hợp, khoảng trắng thừa ở đầu hoặc cuối, giá trị ngoại lai theo quy tắc IQR, định dạng ngày tháng không nhất quán, viết hoa/thường không đồng nhất). Kịch bản cũng báo cáo các dòng bị trùng lặp, các dòng bị lệch số cột (ragged rows), và liệu cột khóa có đảm bảo tính duy nhất hay không. Mã thoát (exit code) là 1 khi phát hiện bất kỳ vấn đề nào ở mức HIGH.
Nếu người dùng không thể chạy kịch bản, hãy tự đọc 200 dòng đầu tiên và áp dụng thủ công các bước kiểm tra tương tự, đồng thời nêu rõ rằng kết quả này được tính trên một mẫu dữ liệu.
### 3. Diễn giải kết quả
Đối với mỗi phát hiện, hãy sử dụng `references/quality-dimensions.md` để xác định:
1. Nó ảnh hưởng đến chiều chất lượng nào (tính đầy đủ - completeness, tính hợp lệ - validity, tính duy nhất - uniqueness, tính nhất quán - consistency, tín hiệu về độ chính xác - accuracy signals, cấu trúc - structure).
2. Mức độ nghiêm trọng đối với mục đích sử dụng hiện tại: HIGH (gây sai lệch kết quả hoặc làm thất bại quá trình nhập dữ liệu), MEDIUM (gây hiểu lầm ở một số góc nhìn), LOW (chỉ ảnh hưởng mặt hình thức).
3. Liệu đó có phải là một vấn đề thực sự hay là điều bình thường đúng như kỳ vọng (ví dụ: một cột tùy chọn như "coupon_code" được phép để trống ở phần lớn các dòng).
### 4. Khuyến nghị cách khắc phục
Sử dụng `references/fix-playbook.md`. Ưu tiên việc sửa lỗi ngay tại hệ thống nguồn hơn là làm sạch dữ liệu ở các khâu phía sau. Đưa ra mỗi cách khắc phục dưới dạng một bước thực hiện cụ thể (một công thức bảng tính, một đoạn mã pandas hoặc SQL, hoặc một thay đổi trên hệ thống nguồn) và nêu rõ nó sẽ làm thay đổi điều gì cũng như tác động đến bao nhiêu dòng dữ liệu.
### 5. Viết báo cáo
Điền vào mẫu `templates/quality-report.md` theo đúng cách mà `examples/example-orders-report.md` thực hiện: nêu kết luận tổng thể (verdict) trước, tiếp đến là các vấn đề hàng đầu, rồi mới đến bảng chi tiết từng cột.
## Các mức kết luận (Verdicts)
- **READY** - không có vấn đề mức HIGH nào đối với mục đích đã nêu.
- **READY WITH CAVEATS** - có thể sử dụng được nếu chấp nhận các lưu ý đã liệt kê.
- **NOT READY** - có ít nhất một vấn đề mức HIGH sẽ dẫn đến số liệu sai lệch hoặc làm thất bại quá trình nhập dữ liệu.
## Quy tắc
- Tuyệt đối không âm thầm xóa bỏ, tự điền giá trị khuyết thiếu (impute), hoặc khử trùng lặp các dòng dữ liệu; luôn phải nêu rõ quy tắc xử lý cùng số lượng dòng bị ảnh hưởng, đồng thời giữ nguyên tệp gốc.
- Không phỏng đoán ý nghĩa của một mã hiệu hay từ viết tắt; hãy hỏi lại người dùng hoặc đánh dấu đó là một giả định.
- Xử lý dữ liệu cá nhân một cách cẩn trọng: chỉ hiển thị tối đa một vài giá trị làm ví dụ, và che (mask) địa chỉ email, số điện thoại cũng như các mã định danh cá nhân (ID) trong báo cáo.
- Các giá trị ngoại lai (outliers) là những manh mối cần tìm hiểu, chứ không mặc nhiên là lỗi. Hãy hỏi ý kiến trước khi loại bỏ chúng.
FILE:references/quality-dimensions.md
# Các Chiều Chất Lượng Dữ Liệu
Chấm điểm từng chiều chất lượng theo các mức OK (Ổn), WATCH (Cần theo dõi), hoặc PROBLEM (Có vấn đề) dựa trên mục đích sử dụng của người dùng.
## 1. Tính đầy đủ (Completeness)
Các giá trị bắt buộc có mặt đầy đủ hay không?
- Các ký hiệu đánh dấu dữ liệu khuyết thiếu cần được coi là rỗng: "", "NA", "N/A", "null", "NULL", "None", "-", "?" (không phân biệt chữ hoa chữ thường, sau khi đã cắt bỏ khoảng trắng hai đầu).
- PROBLEM: một cột bắt buộc (khóa chính, số tiền, ngày tháng) có bất kỳ giá trị bị thiếu nào khi dùng để nhập dữ liệu (import), hoặc thiếu nhiều hơn 5 phần trăm khi dùng để phân tích.
- WATCH: một cột tùy chọn bị bỏ trống nhiều hơn 50 phần trăm (liệu cột này có còn được sử dụng nữa hay không?).
## 2. Tính hợp lệ (Validity)
Các giá trị có khớp với kiểu dữ liệu kỳ vọng và phạm vi cho phép hay không?
- Kiểu dữ liệu hỗn hợp trong cùng một cột (vừa có số vừa có chữ như "TBD").
- Số được lưu kèm dấu phân cách hàng nghìn hoặc ký hiệu tiền tệ ("1,200", "$45").
- Ngày tháng không thể phân tích cú pháp, hoặc các giá trị phi thực tế (tháng 13, số lượng âm, tuổi 250).
- Đánh giá là PROBLEM khi cột đó được đưa vào phép tính toán hoặc nạp vào một cột có kiểu dữ liệu cố định trong cơ sở dữ liệu.
## 3. Tính duy nhất (Uniqueness)
- Các dòng bị trùng lặp hoàn toàn trên mọi cột: thường gây ra bởi việc xuất dữ liệu hai lần hoặc các tác vụ chạy lặp lại.
- Khóa chính bị trùng lặp: hai dòng cùng mang một mã ID nhưng lại chứa dữ liệu khác nhau. Luôn đánh giá là PROBLEM đối với tác vụ nhập dữ liệu.
- Gần như trùng lặp (có cùng giá trị sau khi cắt khoảng trắng và chuyển về chữ thường) được xếp vào mức WATCH.
## 4. Tính nhất quán (Consistency)
- Nhiều định dạng ngày tháng cùng xuất hiện trong một cột (2026-03-01, 03/01/2026, 1 Mar 2026).
- Cùng một danh mục nhưng lại được viết theo nhiều kiểu khác nhau ("Paid", "paid", "PAID ").
- Trộn lẫn các đơn vị đo lường trong cùng một cột (kg và lb, xu và đô la).
- Khoảng trắng thừa ở đầu hoặc cuối chuỗi làm hỏng các phép kết nối bảng (joins) và bộ lọc (filters).
## 5. Tín hiệu về độ chính xác (Accuracy signals)
Công cụ phân tích hồ sơ không thể chứng minh độ chính xác thực tế, nhưng có thể dựng cờ cảnh báo:
- Các giá trị ngoại lai nằm ngoài khoảng 1.5 x IQR tính từ các tứ phân vị (quartiles).
- Các hằng số đáng ngờ (mọi dòng đều mang cùng một giá trị duy nhất).
- Các giá trị trông giống như giá trị mặc định giả định (1970-01-01, 0, 999999, "test").
- Tổng số liệu không khớp với một con số đã biết từ phía người dùng.
## 6. Cấu trúc (Structure)
- Các dòng bị lệch số cột (ragged rows - có số lượng trường khác với dòng tiêu đề): thường do ký tự phân cách nằm bên trong chuỗi văn bản nhưng không được bọc trong dấu ngoặc kép.
- Tên cột tiêu đề bị bỏ trống hoặc bị trùng nhau.
- Lỗi bảng mã ký tự (mojibake): các chữ cái có dấu bị hiển thị thành hai ký tự lạ, ví dụ chữ "e" có dấu sắc trong từ "café" bị biến thành chữ "A" có dấu ngã đi kèm một ký hiệu lạ (do tệp mã hóa UTF-8 nhưng lại bị đọc theo bảng mã Latin-1).
- Có ký tự đánh dấu thứ tự byte (BOM) nằm ở ngay đầu tên cột tiêu đề đầu tiên.
## Mức độ nghiêm trọng theo mục đích sử dụng
| Phát hiện | Phân tích | Nhập dữ liệu định kỳ | Bảng điều khiển (Dashboard) |
|---|---|---|---|
| Khóa chính bị trùng lặp | MEDIUM | HIGH | HIGH |
| Kiểu dữ liệu hỗn hợp trong cột số | HIGH | HIGH | HIGH |
| Nhiều định dạng ngày tháng | MEDIUM | HIGH | MEDIUM |
| Khoảng trắng thừa ở cuối tên danh mục | LOW | MEDIUM | MEDIUM |
| Giá trị ngoại lai (Outliers) | MEDIUM | LOW | MEDIUM |
| Dòng bị lệch số cột (Ragged rows) | HIGH | HIGH | HIGH |
FILE:references/fix-playbook.md
# Cẩm Nang Khắc Phục Lỗi Dữ Liệu
Nguyên tắc bất di bất dịch: luôn giữ lại tệp gốc, viết các bước sửa lỗi dưới dạng một kịch bản có thể chạy lặp lại hoặc tài liệu hướng dẫn từng bước rõ ràng, và báo cáo chính xác số lượng dòng mà mỗi bước sửa lỗi tác động tới.
## Giá trị bị thiếu (Missing values)
- Trường bắt buộc: sửa ngay tại hệ thống nguồn, hoặc cách ly (quarantine) các dòng bị lỗi sang một tệp riêng biệt để rà soát.
- Trường tùy chọn: giữ nguyên trạng thái rỗng; chuẩn hóa mọi ký hiệu đánh dấu dữ liệu khuyết thiếu về cùng một giá trị rỗng duy nhất.
- Tuyệt đối không tự ý điền số tiền hoặc ngày tháng bị thiếu bằng số 0 hay ngày hôm nay chỉ để vượt qua bước kiểm tra khi nhập dữ liệu.
## Kiểu dữ liệu hỗn hợp (Mixed types)
- Tìm các giá trị không khớp kiểu dữ liệu trước tiên: `df[pd.to_numeric(df.col, errors="coerce").isna() & df.col.notna()]`.
- Đưa ra quyết định cho từng loại giá trị: một giá trị thực tế nhưng được viết theo định dạng khác ("1,200" chuyển thành 1200), một chuỗi giữ chỗ tạm thời ("TBD" chuyển thành rỗng), hoặc một lỗi dữ liệu thực sự (gửi trả lại cho người phụ trách dữ liệu).
## Dữ liệu trùng lặp (Duplicates)
- Các dòng trùng lặp hoàn toàn: an toàn để loại bỏ sau khi đã xác nhận chúng sinh ra do việc xuất dữ liệu hai lần; giữ lại dòng xuất hiện đầu tiên.
- Khóa chính trùng lặp nhưng chứa dữ liệu khác nhau: không tự động chọn bừa một dòng. Hãy liệt kê cả hai dòng ra và hỏi xem hệ thống nào mới là nguồn dữ liệu chuẩn (source of truth), hoặc giữ lại bản ghi mới nhất dựa theo cột `updated_at` nếu người dùng đồng ý.
## Ngày tháng không nhất quán (Inconsistent dates)
- Phân tích cú pháp bằng một định dạng tường minh cho từng mẫu (pattern), tuyệt đối không dùng bộ phân tích tự đoán mò trên toàn bộ cột.
- Các giá trị ngày/tháng gây nhập nhằng (như 03/04/2026) cần có quy tắc xác nhận từ người dùng; hãy kiểm tra xem trong cột có giá trị nào mà phần ngày lớn hơn 12 hay không để suy luận ra định dạng chuẩn.
- Lưu trữ kết quả đầu ra theo chuẩn ISO 8601 (YYYY-MM-DD).
## Danh mục không nhất quán và khoảng trắng thừa
- Cắt bỏ khoảng trắng hai đầu ở mọi cột văn bản được dùng để kết nối bảng (joins) hoặc gom nhóm (grouping).
- Ánh xạ các biến thể chính tả bằng một bảng ánh xạ tường minh đã được người dùng phê duyệt, thay vì dùng kỹ thuật khớp mờ (fuzzy matching) tự động.
## Giá trị ngoại lai (Outliers)
- Kiểm tra lại các giá trị này với người sở hữu dữ liệu. Những nguyên nhân thực tế thường gặp: đơn đặt hàng số lượng lớn (bán sỉ), các khoản hoàn tiền được lưu dưới dạng số âm, hoặc các giao dịch thử nghiệm.
- Nếu loại trừ chúng khỏi bài phân tích, hãy nêu rõ điều đó trong phần kết quả và trình bày song song cả hai bộ số liệu khi có và không có các giá trị ngoại lai đó.
## Cấu trúc (Structure)
- Các dòng bị lệch số cột (Ragged rows): xuất lại dữ liệu từ nguồn với cấu hình đóng mở ngoặc kép chuẩn xác, hoặc phân tích cú pháp bằng đúng ký tự phân cách và ký tự bao chuỗi.
- Bảng mã (Encoding): đọc lại tệp theo chuẩn UTF-8; nếu hiện tượng lỗi phông chữ (mojibake) vẫn còn, tệp đó đã bị mã hóa chồng hai lần ngay từ hệ thống nguồn.
- Ký tự BOM: đọc tệp với tham số bảng mã là "utf-8-sig".
## Những việc tuyệt đối không bao giờ làm tự động
- Xóa hàng loạt các dòng có chứa giá trị bị thiếu.
- Tự suy diễn và điền giá trị thay thế (impute) vào các cột khóa chính, số tiền hoặc ngày tháng.
- Tự động gộp các bản ghi khách hàng hoặc sản phẩm trông gần giống nhau.
- Tự ý xóa bỏ các giá trị ngoại lai.
FILE:templates/quality-report.md
# Báo Cáo Chất Lượng Dữ Liệu: {{dataset_name}}
**Mục đích:** {{analysis | recurring import | dashboard | migration}}
**Tệp:** {{file_name}} ({{rows}} dòng x {{columns}} cột, ký tự phân cách "{{delimiter}}")
**Cột khóa chính:** {{key_column or "none given"}}
**Kết luận:** {{READY | READY WITH CAVEATS | NOT READY}}
## Tóm tắt
{{Two or three sentences: is the data fit for the purpose, and what must happen first.}}
## Các vấn đề hàng đầu (nghiêm trọng nhất xếp trước)
| # | Mức độ | Chiều chất lượng | Cột | Phát hiện | Số dòng ảnh hưởng | Cách khắc phục khuyến nghị |
|---|---|---|---|---|---|---|
| 1 | {{HIGH}} | {{Uniqueness}} | {{col}} | {{finding}} | {{n}} | {{fix}} |
## Điểm số theo từng chiều chất lượng
| Chiều chất lượng | Điểm số | Ghi chú |
|---|---|---|
| Completeness (Tính đầy đủ) | {{OK / WATCH / PROBLEM}} | |
| Validity (Tính hợp lệ) | | |
| Uniqueness (Tính duy nhất) | | |
| Consistency (Tính nhất quán) | | |
| Accuracy signals (Tín hiệu độ chính xác) | | |
| Structure (Cấu trúc) | | |
## Hồ sơ chi tiết từng cột
| Cột | Kiểu dữ liệu | Tỷ lệ thiếu % | Số giá trị phân biệt | Phạm vi hoặc giá trị phổ biến nhất | Vấn đề |
|---|---|---|---|---|---|
## Câu hỏi dành cho người sở hữu dữ liệu
- {{question}}
## Các giả định
- {{assumption}}
## Các bước tiếp theo
1. {{step}}
FILE:examples/example-orders-report.md
# Báo Cáo Chất Lượng Dữ Liệu: Bản kết xuất đơn hàng trực tuyến (Tháng 9)
**Mục đích:** nhập dữ liệu định kỳ vào cơ sở dữ liệu tài chính
**Tệp:** orders_sept.csv (8 dòng x 6 cột, ký tự phân cách ",")
**Cột khóa chính:** order_id
**Kết luận:** NOT READY
## Tóm tắt
Bản kết xuất này không thể nhập trực tiếp như hiện trạng: một mã đơn hàng xuất hiện hai lần với hai số tiền khác nhau, và cột số tiền trộn lẫn cả giá trị số với chuỗi giữ chỗ "TBD". Ngày tháng đang sử dụng hai định dạng khác nhau. Sau khi thực hiện ba bước khắc phục dưới đây, tệp dữ liệu sẽ sẵn sàng để nhập.
## Các vấn đề hàng đầu (nghiêm trọng nhất xếp trước)
| # | Mức độ | Chiều chất lượng | Cột | Phát hiện | Số dòng ảnh hưởng | Cách khắc phục khuyến nghị |
|---|---|---|---|---|---|---|
| 1 | HIGH | Uniqueness | order_id | Mã khóa A-1003 xuất hiện hai lần (số tiền 45.00 và 54.00) | 2 | Hỏi bộ phận tài chính xem dòng nào mới đúng; không tự động chọn |
| 2 | HIGH | Validity | amount | Kiểu dữ liệu hỗn hợp: 1 giá trị không phải số ("TBD") | 1 | Thay thế bằng số tiền thực tế từ hệ thống cửa hàng, hoặc cách ly dòng này |
| 3 | MEDIUM | Consistency | order_date | Hai định dạng ngày tháng (YYYY-MM-DD và DD/MM/YYYY) | 2 | Phân tích cú pháp tường minh cho từng mẫu, lưu trữ theo chuẩn ISO 8601 |
| 4 | MEDIUM | Consistency | status | Khoảng trắng thừa ở đầu hoặc cuối ("paid ") | 1 | Cắt bỏ khoảng trắng ở tất cả các cột danh mục trước khi nhập dữ liệu |
| 5 | LOW | Accuracy signals | amount | Giá trị ngoại lai 1250.00 (theo quy tắc IQR) | 1 | Xác nhận lại với nhóm vận hành cửa hàng; nhiều khả năng là đơn hàng mua sỉ |
## Điểm số theo từng chiều chất lượng
| Chiều chất lượng | Điểm số | Ghi chú |
|---|---|---|
| Completeness (Tính đầy đủ) | WATCH | cột coupon trống 87.5 phần trăm, đúng như kỳ vọng đối với một trường tùy chọn |
| Validity (Tính hợp lệ) | PROBLEM | có giá trị "TBD" trong cột amount |
| Uniqueness (Tính duy nhất) | PROBLEM | trùng mã khóa A-1003 |
| Consistency (Tính nhất quán) | WATCH | định dạng ngày tháng, khoảng trắng thừa ở cuối trong cột status |
| Accuracy signals (Tín hiệu độ chính xác) | WATCH | có một đơn hàng giá trị lớn |
| Structure (Cấu trúc) | OK | không có dòng lệch số cột, dòng tiêu đề sạch sẽ |
## Hồ sơ chi tiết từng cột
| Cột | Kiểu dữ liệu | Tỷ lệ thiếu % | Số giá trị phân biệt | Phạm vi hoặc giá trị phổ biến nhất | Vấn đề |
|---|---|---|---|---|---|
| order_id | string | 0.0 | 7 | A-1003 (2), A-1001 (1), A-1002 (1) | trùng khóa chính |
| order_date | date | 0.0 | 7 | 2026-09-01 đến 2026-09-28 | 2 định dạng |
| customer_email | string | 0.0 | 6 | (đã ẩn) | không có |
| amount | float | 0.0 | 8 | 18.5 đến 1250.0 | kiểu hỗn hợp, giá trị ngoại lai |
| status | string | 0.0 | 2 | paid (7), refunded (1) | khoảng trắng bao quanh |
| coupon | string | 87.5 | 1 | FALL10 (1) | phần lớn để trống (đúng kỳ vọng) |
## Câu hỏi dành cho người sở hữu dữ liệu
- Dòng A-1003 nào mới là dòng chính xác, và tại sao đơn hàng này lại bị xuất ra hai lần?
- Số tiền thực tế của đơn hàng A-1006 là bao nhiêu?
## Các giả định
- Định dạng DD/MM/YYYY được sử dụng cho các ngày tháng có dấu gạch chéo (vì có một giá trị mang ngày 28, nên không thể là định dạng MM/DD).
## Các bước tiếp theo
1. Giải quyết dứt điểm hai đơn hàng A-1003 và A-1006 với bộ phận tài chính.
2. Bổ sung bước cắt khoảng trắng và chuẩn hóa ngày tháng vào tác vụ xuất dữ liệu.
3. Chạy lại lệnh `python3 scripts/profile_csv.py orders_sept.csv --key order_id` và tiến hành nhập dữ liệu khi lệnh trả về mã thoát 0.
FILE:scripts/profile_csv.py
#!/usr/bin/env python3
"""Profile a CSV file for data quality problems (Python 3 standard library only).
Usage:
python3 profile_csv.py FILE.csv [--key COLUMN] [--delimiter ","] [--json]
python3 profile_csv.py - < FILE.csv (read from stdin)
Reports per column: inferred type (int, float, numtext = numbers stored with
separators or currency symbols, date, bool, string), missing values, distinct count, top values,
min/max, and issues (mixed types, surrounding spaces, IQR outliers, several
date formats, inconsistent casing). Also reports ragged rows, duplicate rows,
blank or duplicate headers, and key uniqueness when --key is given.
Exit code: 0 = no HIGH issues, 1 = at least one HIGH issue, 2 = usage error.
"""
import argparse
import csv
import io
import json
import re
import statistics
import sys
from collections import Counter
MISSING = {"", "na", "n/a", "null", "none", "nan", "-", "?"}
DATE_PATTERNS = [
("YYYY-MM-DD", re.compile(r"^\d{4}-\d{2}-\d{2}$")),
("YYYY-MM-DD HH:MM", re.compile(r"^\d{4}-\d{2}-\d{2}[ T]\d{2}:\d{2}(:\d{2})?$")),
("DD/MM/YYYY or MM/DD/YYYY", re.compile(r"^\d{1,2}/\d{1,2}/\d{4}$")),
("DD.MM.YYYY", re.compile(r"^\d{1,2}\.\d{1,2}\.\d{4}$")),
("D Mon YYYY", re.compile(r"^\d{1,2} [A-Za-z]{3,9} \d{4}$")),
]
INT_RE = re.compile(r"^[+-]?\d+$")
FLOAT_RE = re.compile(r"^[+-]?(\d+\.\d*|\.\d+|\d+)([eE][+-]?\d+)?$")
FORMATTED_NUM_RE = re.compile(r"^[+-]?[$\u20ac\u00a3]?\d{1,3}(,\d{3})+(\.\d+)?$|^[$\u20ac\u00a3]\d+(\.\d+)?$")
BOOL_VALUES = {"true", "false", "yes", "no", "y", "n"}
def classify(value):
v = value.strip()
if INT_RE.match(v):
return "int"
if FLOAT_RE.match(v):
return "float"
if v.lower() in BOOL_VALUES:
return "bool"
for name, rx in DATE_PATTERNS:
if rx.match(v):
return "date:" + name
if FORMATTED_NUM_RE.match(v):
return "formatted_number"
return "string"
def quartiles(nums):
q = statistics.quantiles(nums, n=4, method="inclusive")
return q[0], q[2]
def profile(rows, header, key=None):
issues = [] # (severity, column, code, message)
ncols = len(header)
if header and header[0].startswith("\ufeff"):
header[0] = header[0].lstrip("\ufeff")
issues.append(("LOW", header[0], "bom", "File starts with a byte order mark; read with encoding utf-8-sig"))
names = Counter(h.strip() for h in header)
for h in header:
if not h.strip():
issues.append(("MEDIUM", "(header)", "blank-header", "A header cell is blank"))
for h, c in names.items():
if h and c > 1:
issues.append(("HIGH", h, "duplicate-header", f"Header '{h}' appears {c} times"))
ragged = [i + 2 for i, r in enumerate(rows) if len(r) != ncols]
if ragged:
issues.append(("HIGH", "(rows)", "ragged-rows",
f"{len(ragged)} row(s) have a field count different from the header ({ncols}); first at line(s) {ragged[:5]}"))
good = [r for r in rows if len(r) == ncols]
dup_counter = Counter(tuple(c.strip() for c in r) for r in good)
dup_rows = sum(c - 1 for c in dup_counter.values() if c > 1)
if dup_rows:
issues.append(("MEDIUM", "(rows)", "duplicate-rows", f"{dup_rows} fully duplicated row(s)"))
columns = []
for idx, name in enumerate(header):
raw = [r[idx] for r in good]
present = [v for v in raw if v.strip().lower() not in MISSING]
missing = len(raw) - len(present)
kinds = Counter(classify(v) for v in present)
base = Counter()
for k, c in kinds.items():
base["date" if k.startswith("date:") else k] += c
if base:
top_kind, top_n = base.most_common(1)[0]
else:
top_kind, top_n = "empty", 0
if set(base) <= {"int", "float"} and base:
ctype = "float" if "float" in base else "int"
elif set(base) <= {"int", "float", "formatted_number"} and base:
ctype = "numtext"
else:
ctype = top_kind
col = {
"name": name, "type": ctype, "rows": len(raw), "missing": missing,
"missing_pct": round(100.0 * missing / len(raw), 1) if raw else 0.0,
"distinct": len(set(v.strip() for v in present)),
"top_values": Counter(v.strip() for v in present).most_common(3),
"issues": [],
}
def add(sev, code, msg):
col["issues"].append(code)
issues.append((sev, name, code, msg))
numeric_like = base.get("int", 0) + base.get("float", 0)
others = {k: c for k, c in kinds.items() if k not in ("int", "float")}
if numeric_like and others and numeric_like >= sum(others.values()):
bad = [v.strip() for v in present if classify(v) not in ("int", "float")]
add("HIGH", "mixed-types",
f"Mostly numeric but {len(bad)} non-numeric value(s), e.g. {bad[:3]}")
elif base.get("formatted_number") and ctype in ("numtext", "formatted_number"):
add("MEDIUM", "formatted-numbers",
f"{base['formatted_number']} value(s) use thousands separators or currency symbols")
date_formats = {k[5:] for k in kinds if k.startswith("date:")}
if len(date_formats) > 1:
add("MEDIUM", "date-formats", f"Several date formats: {sorted(date_formats)}")
if ctype == "date" and "DD/MM/YYYY or MM/DD/YYYY" in date_formats:
add("LOW", "ambiguous-dates", "Slash dates are ambiguous (day/month order); confirm the format")
spaced = [v for v in present if v != v.strip()]
if spaced:
add("MEDIUM", "surrounding-spaces", f"{len(spaced)} value(s) have leading or trailing spaces")
if ctype == "string" and present:
groups = {}
for v in present:
groups.setdefault(v.strip().lower(), set()).add(v.strip())
variants = [sorted(s) for s in groups.values() if len(s) > 1]
if variants:
add("LOW", "case-variants", f"Same value with different casing: {variants[:3]}")
if col["missing_pct"] > 50:
add("LOW", "mostly-empty", f"{col['missing_pct']}% missing (fine if optional)")
elif missing:
add("LOW", "missing", f"{missing} missing value(s)")
if ctype in ("int", "float"):
nums = [float(v) for v in present if classify(v) in ("int", "float")]
if nums:
fmt = int if ctype == "int" else float
col["min"], col["max"] = fmt(min(nums)), fmt(max(nums))
if len(nums) >= 4:
q1, q3 = quartiles(nums)
iqr = q3 - q1
lo, hi = q1 - 1.5 * iqr, q3 + 1.5 * iqr
outliers = [n for n in nums if n < lo or n > hi]
if outliers:
add("LOW", "outliers", f"{len(outliers)} outlier(s) outside [{lo:g}, {hi:g}]: {outliers[:3]}")
elif ctype == "date" and present:
iso = sorted(v.strip()[:10] for v in present if classify(v).startswith("date:YYYY"))
if iso:
col["min"], col["max"] = iso[0], iso[-1]
if len(present) > 1 and col["distinct"] == 1:
add("LOW", "constant", "Every non-missing value is the same")
columns.append(col)
key_report = None
if key:
names_clean = [h.strip() for h in header]
if key not in names_clean:
issues.append(("HIGH", key, "key-missing", f"Key column '{key}' not found in header"))
else:
k = names_clean.index(key)
vals = [r[k].strip() for r in good]
empty = sum(1 for v in vals if v.lower() in MISSING)
dups = {v: c for v, c in Counter(vals).items() if c > 1 and v.lower() not in MISSING}
key_report = {"column": key, "empty": empty, "duplicate_keys": dups}
if empty:
issues.append(("HIGH", key, "key-empty", f"{empty} row(s) have an empty key"))
if dups:
issues.append(("HIGH", key, "key-duplicate", f"{len(dups)} key value(s) repeat: {dict(list(dups.items())[:5])}"))
return {"rows": len(rows), "columns": len(header), "column_profiles": columns,
"key": key_report, "issues": [dict(zip(("severity", "column", "code", "message"), i)) for i in issues]}
def render(report):
out = [f"Rows: {report['rows']} Columns: {report['columns']}", "",
"Column Type Missing% Distinct Range / top values"]
for c in report["column_profiles"]:
rng = f"{c['min']} .. {c['max']}" if "min" in c else ", ".join(f"{v} ({n})" for v, n in c["top_values"])
out.append(f"{c['name'][:17]:<17} {c['type'][:8]:<8} {c['missing_pct']:>8} {c['distinct']:>8} {rng[:60]}")
out.append("")
order = {"HIGH": 0, "MEDIUM": 1, "LOW": 2}
for i in sorted(report["issues"], key=lambda x: order[x["severity"]]):
out.append(f"[{i['severity']}] {i['column']}: {i['code']}: {i['message']}")
counts = Counter(i["severity"] for i in report["issues"])
out.append("")
out.append(f"{counts.get('HIGH', 0)} HIGH, {counts.get('MEDIUM', 0)} MEDIUM, {counts.get('LOW', 0)} LOW")
out.append("Heuristic profile: confirm findings with references/quality-dimensions.md.")
return "\n".join(out)
def main(argv=None):
ap = argparse.ArgumentParser(description="Profile a CSV file for data quality problems.")
ap.add_argument("file", help="CSV path, or - for stdin")
ap.add_argument("--key", help="column that should be unique and non-empty")
ap.add_argument("--delimiter", default=None, help="field delimiter (default: sniffed)")
ap.add_argument("--json", action="store_true", help="print JSON instead of text")
a = ap.parse_args(argv)
try:
text = sys.stdin.read() if a.file == "-" else open(a.file, encoding="utf-8", newline="").read()
except (OSError, UnicodeDecodeError) as e:
print(f"error: cannot read {a.file}: {e}", file=sys.stderr)
return 2
if not text.strip():
print("error: file is empty", file=sys.stderr)
return 2
delim = a.delimiter
if delim is None:
try:
delim = csv.Sniffer().sniff(text[:4096], delimiters=",;\t|").delimiter
except csv.Error:
delim = ","
all_rows = [r for r in csv.reader(io.StringIO(text), delimiter=delim) if any(c.strip() for c in r)]
header, rows = all_rows[0], all_rows[1:]
report = profile(rows, header, a.key)
report["delimiter"] = delim
print(json.dumps(report, indent=2) if a.json else render(report))
return 1 if any(i["severity"] == "HIGH" for i in report["issues"]) else 0
if __name__ == "__main__":
sys.exit(main())Giải thích bất kỳ biểu thức cron nào bằng ngôn ngữ tự nhiên dễ hiểu, liệt kê các thời điểm chạy tiếp theo, và cảnh báo những cạm bẫy phổ biến như quy tắc ngày-trong-tháng HOẶC thứ-trong-tuần, các ngày không bao giờ xảy ra, khoảng trống giờ mùa hè (DST), sự nhầm lẫn giữa giờ UTC và giờ địa phương, cùng các tác vụ bị chạy chồng chéo. Bao gồm công cụ kiểm tra tệp crontab viết bằng thư viện chuẩn Python đã được kiểm thử.
---
name: cron-schedule-explainer
description: Giải thích, xác thực và viết các lịch trình cron - dịch một biểu thức cron sang ngôn ngữ tự nhiên dễ hiểu, liệt kê các thời điểm chạy tiếp theo, đồng thời cảnh báo những cạm bẫy như quy tắc ngày-trong-tháng HOẶC thứ-trong-tuần, các ngày không bao giờ xảy ra, khoảng trống giờ mùa hè (DST), sự nhầm lẫn về múi giờ, và các tác vụ chạy chồng chéo hoặc quá dày đặc. Sử dụng khi người dùng dán một dòng crontab, Kubernetes CronJob, lịch chạy GitHub Actions, hoặc hỏi "khi nào tác vụ này sẽ chạy?" hay "hãy viết một biểu thức cron chạy vào thứ Ba tuần thứ hai của mỗi tháng".
---
# Trình Giải Thích Lịch Trình Cron
Bạn giúp cho các tác vụ được lập lịch trở nên dễ đoán định. Đối với mỗi lịch trình, bạn đưa ra ý nghĩa bằng ngôn ngữ tự nhiên dễ hiểu, các thời điểm chạy cụ thể tiếp theo, và những rủi ro có thể khiến ai đó bị bất ngờ lúc 2 giờ sáng.
## Các tệp trong kỹ năng này
- `scripts/cron_explain.py` - trình phân tích cú pháp, giải thích, tính toán lần chạy tiếp theo và kiểm tra cạm bẫy (chỉ dùng thư viện chuẩn Python 3)
- `references/cron-syntax.md` - phạm vi các trường, ký tự đặc biệt, các macro và sự khác biệt giữa các nền tảng
- `references/scheduling-pitfalls.md` - những sai lầm phổ biến và cách phòng tránh chúng
- `templates/schedule-review.md` - định dạng báo cáo rà soát
- `examples/example-backup-review.md` - bài rà soát mẫu hoàn chỉnh cho ba dòng crontab
## Quy trình làm việc
### 1. Xác định nền tảng
Chuẩn cron 5 trường (Vixie cron, cronie, Kubernetes CronJob, GitHub Actions) là mặc định. Hãy hỏi hoặc kiểm tra xem liệu người dùng có đang nói đến Quartz (6 hoặc 7 trường có thêm giây và ký tự `?`), AWS EventBridge (6 trường có thêm năm), hay systemd timers hay không; xem chi tiết trong `references/cron-syntax.md`. Lưu ý về múi giờ: GitHub Actions luôn sử dụng múi giờ UTC; Kubernetes sử dụng múi giờ của trình điều khiển (controller) trừ khi trường `timeZone` được thiết lập.
### 2. Chạy công cụ kiểm tra
```bash
python3 scripts/cron_explain.py "30 2 * * 1-5"
python3 scripts/cron_explain.py "0 9 1 * MON" --count 8 --from "2026-10-08 10:00"
python3 scripts/cron_explain.py --file crontab.txt
```
Kịch bản sẽ in ra lời giải thích bằng ngôn ngữ dễ hiểu, N thời điểm chạy tiếp theo (theo giờ địa phương không gắn múi giờ của máy chủ), và các cảnh báo. Mã thoát (exit code) là 1 khi một biểu thức không hợp lệ hoặc không bao giờ chạy.
Nếu bạn không thể chạy kịch bản, hãy tự áp dụng đúng các quy tắc tương tự một cách thủ công và nói rõ điều đó.
### 3. Giải thích
Đối với mỗi lịch trình, hãy cung cấp:
1. Ý nghĩa bằng một câu ngôn ngữ tự nhiên dễ hiểu.
2. Từ 3 đến 5 lần chạy tiếp theo kèm theo múi giờ được nêu rõ.
3. Các cảnh báo từ kịch bản và từ tệp `references/scheduling-pitfalls.md` có liên quan (chạy chồng chéo với các tác vụ kéo dài, giờ mùa hè DST, giờ UTC so với giờ địa phương, các lần chạy bị bỏ lỡ khi máy tắt).
### 4. Viết hoặc sửa lịch trình
Khi người dùng mô tả một lịch trình bằng lời, hãy viết biểu thức cron tương ứng, sau đó đưa nó qua công cụ kiểm tra để xác nhận rằng các lần chạy tiếp theo khớp đúng với ý định của họ. Đối với những lịch trình mà cron không thể diễn đạt trực tiếp (chẳng hạn như thứ Ba tuần thứ hai của tháng, hoặc ngày làm việc cuối cùng trong tháng), hãy đưa ra một biểu thức cron kết hợp với một điều kiện bảo vệ (guard) trong câu lệnh, ví dụ như `[ "$(date +\%d)" -le 07 ] && run-job`, và giải thích rõ lý do tại sao.
### 5. Báo cáo
Sử dụng mẫu `templates/schedule-review.md`, giống như cách làm trong `examples/example-backup-review.md`.
## Quy tắc
- Luôn nêu rõ múi giờ mà bạn đang giả định.
- Hãy nhớ rằng ký tự `%` bắt buộc phải được thoát (escape) thành `\%` bên trong phần câu lệnh của tệp crontab.
- Tuyệt đối không trực tiếp chỉnh sửa tệp crontab đang chạy của người dùng; hãy hiển thị dòng cần thêm vào và hướng dẫn bước chạy `crontab -e`.
- Luôn khuyến nghị sử dụng cơ chế khóa (ví dụ: `flock -n /tmp/job.lock cmd`) bất cứ khi nào một tác vụ có khả năng chạy lâu hơn khoảng thời gian lặp lại của nó.
FILE:references/cron-syntax.md
# Tài Liệu Tham Chiếu Cú Pháp Cron (Chuẩn 5 trường)
```
+------------- phút (0-59)
| +----------- giờ (0-23)
| | +--------- ngày trong tháng (1-31)
| | | +------- tháng (1-12 hoặc JAN-DEC)
| | | | +----- thứ trong tuần (0-7 hoặc SUN-SAT; cả 0 và 7 đều là Chủ nhật)
| | | | |
* * * * * command
```
## Các ký tự đặc biệt
| Ký hiệu | Ý nghĩa | Ví dụ |
|---|---|---|
| `*` | mọi giá trị | `* * * * *` mỗi phút |
| `,` | danh sách | `0 8,12,18 * * *` vào lúc 08:00, 12:00, 18:00 |
| `-` | khoảng giá trị | `0 9 * * 1-5` 09:00 từ thứ Hai đến thứ Sáu |
| `/` | bước nhảy | `*/15 * * * *` mỗi 15 phút; `10-50/20` = 10, 30, 50 |
Tên tháng và thứ không phân biệt chữ hoa chữ thường. Khoảng tên viết tắt (`MON-FRI`) hoạt động trên hầu hết các bản triển khai cron, còn danh sách các tên viết tắt thì hoạt động ở mọi nơi.
## Các macro
| Macro | Biểu thức tương đương |
|---|---|
| `@yearly` / `@annually` | `0 0 1 1 *` |
| `@monthly` | `0 0 1 * *` |
| `@weekly` | `0 0 * * 0` |
| `@daily` / `@midnight` | `0 0 * * *` |
| `@hourly` | `0 * * * *` |
| `@reboot` | chạy một lần khi khởi động máy (không dựa theo thời gian) |
## Quy tắc kết hợp trường ngày
Nếu cả trường ngày trong tháng và trường thứ trong tuần đều bị giới hạn (tức là không trường nào mang dấu `*`), tác vụ sẽ chạy khi MỘT TRONG HAI điều kiện khớp. Biểu thức `0 9 1 * MON` sẽ chạy vào ngày mùng 1 hàng tháng VÀ vào mọi ngày thứ Hai.
## Sự khác biệt giữa các nền tảng
| Nền tảng | Số trường | Múi giờ | Ghi chú |
|---|---|---|---|
| Linux cron (cronie, Vixie) | 5 | giờ địa phương của hệ thống | biến `CRON_TZ=` được cronie hỗ trợ |
| Kubernetes CronJob | 5 | múi giờ của controller, hoặc `spec.timeZone` | sử dụng `concurrencyPolicy: Forbid` để ngăn chạy chồng chéo |
| GitHub Actions `schedule` | 5 | luôn là UTC | các lần chạy có thể bị trễ khi hệ thống quá tải; khoảng cách tối thiểu là 5 phút |
| Quartz (Java) | 6-7 (giây đứng đầu, năm là tùy chọn) | có thể cấu hình | dùng `?` cho "không chỉ định giá trị cụ thể", hỗ trợ `L`, `W`, `#` |
| AWS EventBridge | 6 (có trường năm) | UTC trừ khi múi giờ của bộ lập lịch được thiết lập | một trong hai trường ngày-trong-tháng hoặc thứ-trong-tuần bắt buộc phải là `?` |
Kịch bản trong kỹ năng này hỗ trợ chuẩn 5 trường cùng các macro nêu trên (ngoại trừ `@reboot`, được kịch bản báo cáo là không dựa theo thời gian).
FILE:references/scheduling-pitfalls.md
# Những Cạm Bẫy Khi Lập Lịch
## 1. Ngày trong tháng HOẶC thứ trong tuần
Biểu thức `0 0 13 * 5` KHÔNG PHẢI là "thứ Sáu ngày 13". Nó sẽ chạy vào mọi ngày 13 hàng tháng và vào mọi ngày thứ Sáu. Hãy sử dụng `0 0 13 * *` kết hợp với một điều kiện bảo vệ: `[ "$(date +\%u)" = 5 ] && cmd`.
## 2. những ngày không bao giờ hoặc hiếm khi xảy ra
- `0 0 30 2 *` không bao giờ chạy (tháng Hai không có ngày 30).
- `0 0 31 * *` chỉ chạy trong 7 tháng có 31 ngày của năm.
- `0 0 29 2 *` chỉ chạy vào các năm nhuận.
Đối với nhu cầu chạy vào "ngày cuối cùng của tháng", hãy dùng `0 0 28-31 * *` kèm điều kiện bảo vệ: `[ "$(date -d tomorrow +\%d)" = 01 ] && cmd`.
## 3. Giờ tiết kiệm ánh sáng ban ngày / Giờ mùa hè (DST)
Ở các múi giờ địa phương có áp dụng DST, các mốc thời gian trong khoảng từ 01:00 đến 03:00 có thể bị bỏ qua (khi vặn đồng hồ tiến lên vào mùa xuân) hoặc bị chạy hai lần (khi vặn đồng hồ lùi lại vào mùa thu), tùy thuộc vào bản triển khai cron. Hãy xếp lịch cho các tác vụ quan trọng nằm ngoài khung giờ đó, hoặc cho cron chạy theo múi giờ UTC.
## 4. Giờ UTC so với giờ địa phương
GitHub Actions và nhiều bộ lập lịch trên đám mây sử dụng giờ UTC. Lịch chạy "Mỗi ngày vào lúc 09:00" đối với một nhóm làm việc tại Istanbul (UTC+3) sẽ là `0 6 * * *` theo giờ UTC. Hãy luôn ghi chú rõ múi giờ ngay bên cạnh biểu thức cron trong tài liệu và trong bình luận mã nguồn (code comments).
## 5. Chạy quá dày đặc hoặc chạy chồng chéo
- `* * * * *` chạy tới 1440 lần mỗi ngày. Hãy đảm bảo đó thực sự là điều bạn muốn.
- Trường phút để là `*` đi kèm với một giờ cố định (`* 3 * * *`) sẽ chạy 60 lần liên tục từ 03:00 đến 03:59; thông thường ý định thực sự của người viết là `0 3 * * *`.
- Nếu một tác vụ có thể mất nhiều thời gian thực thi hơn khoảng cách giữa các lần chạy của nó, hãy sử dụng cơ chế khóa (`flock -n`) hoặc `concurrencyPolicy: Forbid`.
## 6. Giá trị bước nhảy (step) không chia chẵn vòng lặp
Biểu thức `*/7` ở trường phút sẽ chạy vào các phút 0, 7, ..., 56, rồi chạy lại ngay ở phút 0 của giờ tiếp theo (chỉ cách nhau 4 phút). Biểu thức `*/25` chạy vào các phút 0, 25, 50. Các bước nhảy luôn bắt đầu đếm lại từ đầu sau mỗi giờ, mỗi ngày hoặc mỗi tháng.
## 7. Hiệu ứng bầy đàn (Thundering herd)
Rất nhiều nhóm cùng chọn `0 0 * * *` hoặc `0 * * * *`. Hãy dịch chuyển các tác vụ sang một số phút lẻ (ví dụ: `17 2 * * *`) để tránh gây đột biến tải trên các hệ thống dùng chung và các API có giới hạn tần suất gọi (rate-limited).
## 8. Biến môi trường và kết quả đầu ra
Cron chạy với biến môi trường PATH tối thiểu và không có login shell. Hãy sử dụng các đường dẫn tuyệt đối, khai báo các biến cần thiết ngay trong tệp crontab, và chuyển hướng kết quả đầu ra (`>> /var/log/job.log 2>&1`) để có thể nhìn thấy lỗi khi tác vụ thất bại. Hãy thoát ký tự `%` thành `\%`.
## 9. Các lần chạy bị bỏ lỡ
Cron thuần túy không tự động chạy bù các lần thực thi bị bỏ lỡ trong lúc máy tính đang tắt. Hãy sử dụng anacron, systemd timers với thiết lập `Persistent=true`, hoặc `startingDeadlineSeconds` của Kubernetes khi việc bỏ lỡ một lần chạy gây ảnh hưởng quan trọng.
FILE:templates/schedule-review.md
# Báo Cáo Rà Soát Lịch Trình: {{system_or_repo}}
**Nền tảng:** {{Linux cron | Kubernetes CronJob | GitHub Actions | other}}
**Múi giờ giả định:** {{time_zone}}
**Ngày rà soát:** {{date}}
## Tóm tắt
{{One or two sentences: are the schedules doing what the team expects, and what must change.}}
## Các lịch trình
### {{n}}. `{{expression}}` - {{job name}}
- **Ý nghĩa:** {{plain-English explanation}}
- **Các lần chạy tiếp theo:** {{run 1}}, {{run 2}}, {{run 3}}
- **Kết luận:** {{OK | FIX | CLARIFY}}
- **Cảnh báo:**
- {{warning}}
- **Dòng cấu hình đề xuất:**
```
{{corrected crontab line}}
```
## Câu hỏi
- {{question for the team}}
FILE:examples/example-backup-review.md
# Báo Cáo Rà Soát Lịch Trình: crontab của máy chủ vận hành (ops server)
**Nền tảng:** Linux cron (cronie)
**Múi giờ giả định:** Europe/Berlin (giờ địa phương của máy chủ, có áp dụng DST)
**Ngày rà soát:** 2026-10-08
## Tóm tắt
Hai trong số ba dòng cấu hình không hoạt động đúng như nội dung ghi chú. Tác vụ sao lưu chạy bên trong khung giờ chuyển đổi giờ mùa hè (DST), còn báo cáo "thứ Sáu ngày 13" thực chất lại chạy vào mọi ngày thứ Sáu và mọi ngày 13 hàng tháng.
## Các lịch trình
### 1. `30 2 * * *` - sao lưu cơ sở dữ liệu hàng đêm
- **Ý nghĩa:** Vào lúc 02:30 mỗi ngày.
- **Các lần chạy tiếp theo:** 2026-10-09 02:30, 2026-10-10 02:30, 2026-10-11 02:30
- **Kết luận:** FIX
- **Cảnh báo:**
- Mốc 02:30 nằm trong khung giờ thay đổi DST; vào đêm chỉnh đồng hồ tiến lên trong mùa xuân, tác vụ có thể bị bỏ qua, còn vào mùa thu nó có thể bị chạy hai lần.
- Quá trình sao lưu có thể mất hơn một giờ vào dịp cuối tháng; hiện chưa có cơ chế khóa (lock).
- **Dòng cấu hình đề xuất:**
```
17 4 * * * flock -n /tmp/db-backup.lock /opt/scripts/db-backup.sh >> /var/log/db-backup.log 2>&1
```
### 2. `0 9 13 * FRI` - báo cáo vui "thứ Sáu ngày 13"
- **Ý nghĩa:** Vào lúc 09:00 ngày 13 của tháng HOẶC vào mọi ngày thứ Sáu (theo quy tắc kết hợp ngày của cron).
- **Các lần chạy tiếp theo:** 2026-10-09 09:00 (Thứ Sáu), 2026-10-13 09:00 (Thứ Ba, ngày 13), 2026-10-16 09:00 (Thứ Sáu)
- **Kết luận:** FIX
- **Cảnh báo:**
- Cả hai trường ngày đều bị giới hạn, vì vậy cron áp dụng phép toán HOẶC (OR) chứ không phải VÀ (AND).
- **Dòng cấu hình đề xuất:**
```
0 9 13 * * [ "$(date +\%u)" = 5 ] && /opt/scripts/fun-report.sh
```
### 3. `*/20 8-18 * * 1-5` - đồng bộ phiếu yêu cầu (tickets) từ hệ thống hỗ trợ
- **Ý nghĩa:** Mỗi 20 phút một lần (vào các phút :00, :20, :40) từ 08:00 đến 18:59, từ thứ Hai đến thứ Sáu.
- **Các lần chạy tiếp theo:** 2026-10-08 10:20, 2026-10-08 10:40, 2026-10-08 11:00
- **Kết luận:** CLARIFY
- **Cảnh báo:**
- Lần chạy cuối cùng trong ngày là lúc 18:40 chứ không phải 18:00. Hãy sử dụng `8-17` kết hợp với một dòng `0 18 * * 1-5` riêng biệt nếu quá trình đồng bộ cần dừng lại đúng vào lúc 18:00.
## Câu hỏi
- Máy chủ có nên chạy cron theo múi giờ UTC để tránh hoàn toàn các rắc rối liên quan đến giờ mùa hè (DST) hay không?
FILE:scripts/cron_explain.py
#!/usr/bin/env python3
"""Explain, validate, and preview standard 5-field cron expressions (stdlib only).
Usage:
python3 cron_explain.py "EXPR" [--count N] [--from "YYYY-MM-DD HH:MM"]
python3 cron_explain.py --file crontab.txt [--count N] [--from ...]
For each expression: a plain-English explanation, the next N run times
(naive server-local time), and pitfall warnings. In --file mode, crontab
lines are read; comments, blank lines and VAR=value lines are skipped and the
first five fields (or a leading @macro) are taken as the schedule.
Exit code: 0 = all valid, 1 = an expression is invalid or never runs, 2 = usage.
"""
import argparse
import calendar
import datetime as dt
import sys
MONTHS = {m.lower(): i for i, m in enumerate(calendar.month_abbr) if m}
DAYS = {"sun": 0, "mon": 1, "tue": 2, "wed": 3, "thu": 4, "fri": 5, "sat": 6}
MACROS = {
"@yearly": "0 0 1 1 *", "@annually": "0 0 1 1 *", "@monthly": "0 0 1 * *",
"@weekly": "0 0 * * 0", "@daily": "0 0 * * *", "@midnight": "0 0 * * *",
"@hourly": "0 * * * *",
}
FIELDS = [("minute", 0, 59, {}), ("hour", 0, 23, {}), ("day of month", 1, 31, {}),
("month", 1, 12, MONTHS), ("day of week", 0, 7, DAYS)]
DAY_NAMES = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"]
def compress(values, fmt=str):
"""[1,2,3,5] -> '1-3, 5' using fmt for each number."""
vals, out, i = sorted(values), [], 0
while i < len(vals):
j = i
while j + 1 < len(vals) and vals[j + 1] == vals[j] + 1:
j += 1
out.append(fmt(vals[i]) if j - i < 2 else f"{fmt(vals[i])}-{fmt(vals[j])}")
if 0 < j - i < 2:
out.append(fmt(vals[j]))
i = j + 1
return ", ".join(out)
class CronError(ValueError):
pass
def _num(token, lo, hi, names, field):
t = token.lower()
if t in names:
return names[t]
if not t.isdigit():
raise CronError(f"{field}: '{token}' is not a number or known name")
v = int(t)
if not lo <= v <= hi:
raise CronError(f"{field}: {v} is outside {lo}-{hi}")
return v
def parse_field(text, lo, hi, names, field):
values = set()
for part in text.split(","):
if not part:
raise CronError(f"{field}: empty list item in '{text}'")
step = 1
if "/" in part:
part, step_s = part.split("/", 1)
if not step_s.isdigit() or int(step_s) == 0:
raise CronError(f"{field}: bad step '/{step_s}'")
step = int(step_s)
if part == "*":
start, end = lo, hi
elif "-" in part:
a, b = part.split("-", 1)
start, end = _num(a, lo, hi, names, field), _num(b, lo, hi, names, field)
if start > end:
raise CronError(f"{field}: range {a}-{b} is reversed")
else:
start = _num(part, lo, hi, names, field)
end = hi if step > 1 else start
values.update(range(start, end + 1, step))
return values
def parse(expr):
expr = expr.strip()
if expr.lower() == "@reboot":
raise CronError("@reboot runs once at startup and is not time based")
expr = MACROS.get(expr.lower(), expr)
parts = expr.split()
if len(parts) != 5:
hint = " (6-7 fields look like Quartz or EventBridge; see references/cron-syntax.md)" if len(parts) in (6, 7) else ""
raise CronError(f"expected 5 fields, got {len(parts)}{hint}")
for p in parts:
bare = p.lower()
for n in list(MONTHS) + list(DAYS):
bare = bare.replace(n, "")
if any(c in bare for c in "?lw#"):
raise CronError(f"'{p}': '?', 'L', 'W' and '#' are Quartz extensions, not standard cron")
sets = [parse_field(p, lo, hi, names, name) for p, (name, lo, hi, names) in zip(parts, FIELDS)]
if 7 in sets[4]:
sets[4].discard(7)
sets[4].add(0)
return parts, sets
def describe_set(values, lo, hi, field, raw):
vals = sorted(values)
if raw == "*":
return None
if field == "day of week":
names = [DAY_NAMES[v] for v in vals]
if vals == [1, 2, 3, 4, 5]:
return "Monday to Friday"
if vals == [0, 6]:
return "on weekends"
return ", ".join(names)
if field == "month":
return ", ".join(calendar.month_name[v] for v in vals)
return compress(vals)
def explain(parts, sets):
minute, hour, dom, month, dow = sets
rm, rh, rdom, rmon, rdow = parts
if rm == "*" and rh == "*":
time_txt = "every minute"
elif rm.startswith("*/") and rh == "*":
time_txt = f"every {rm[2:]} minutes"
elif rm.startswith("*/"):
time_txt = (f"every {rm[2:]} minutes (at minute " + ", ".join(str(m) for m in sorted(minute)) +
") during hour(s) " + compress(hour, lambda h: f"{h:02d}"))
elif rh == "*":
time_txt = "at minute " + ", ".join(str(m) for m in sorted(minute)) + " of every hour"
elif rm == "*":
time_txt = "every minute during hour(s) " + compress(hour, lambda h: f"{h:02d}")
elif len(minute) * len(hour) <= 6:
time_txt = "at " + ", ".join(f"{h:02d}:{m:02d}" for h in sorted(hour) for m in sorted(minute))
else:
time_txt = ("at minute(s) " + ", ".join(str(m) for m in sorted(minute)) +
" past hour(s) " + compress(hour, lambda h: f"{h:02d}"))
day_txt = []
d_dom = describe_set(dom, 1, 31, "day of month", rdom)
d_dow = describe_set(dow, 0, 6, "day of week", rdow)
if d_dom and d_dow:
day_txt.append(f"on day(s) {d_dom} of the month OR on {d_dow}")
elif d_dom:
day_txt.append(f"on day(s) {d_dom} of the month")
elif d_dow:
day_txt.append(d_dow if d_dow.startswith("on ") else f"on {d_dow}")
else:
day_txt.append("every day")
d_mon = describe_set(month, 1, 12, "month", rmon)
if d_mon:
day_txt.append(f"in {d_mon}")
text = f"{time_txt}, {' '.join(day_txt)}"
return text[0].upper() + text[1:] + "."
def day_matches(d, parts, sets):
_, _, dom, month, dow = sets
if d.month not in month:
return False
cron_dow = (d.weekday() + 1) % 7
dom_r, dow_r = parts[2] != "*", parts[4] != "*"
if dom_r and dow_r:
return d.day in dom or cron_dow in dow
if dom_r:
return d.day in dom
if dow_r:
return cron_dow in dow
return True
def next_runs(parts, sets, start, count, max_days=366 * 8):
minute, hour = sorted(sets[0]), sorted(sets[1])
runs = []
day = start.date()
for _ in range(max_days):
if day_matches(day, parts, sets):
for h in hour:
for m in minute:
t = dt.datetime(day.year, day.month, day.day, h, m)
if t > start:
runs.append(t)
if len(runs) >= count:
return runs
day += dt.timedelta(days=1)
return runs
def warnings(parts, sets, runs):
minute, hour, dom, month, dow = sets
out = []
if parts[2] != "*" and parts[4] != "*":
out.append("Day of month AND day of week are both set: cron runs when EITHER matches (OR, not AND).")
if parts[2] != "*" and parts[4] == "*":
max_days = {m: (29 if m == 2 else calendar.monthrange(2026, m)[1]) for m in month}
if not any(d <= max_days[m] for m in month for d in dom):
out.append("Never runs: the chosen day(s) of month do not exist in the chosen month(s).")
elif any(d > 28 for d in dom):
out.append("Some chosen days (29-31) do not exist in every month, so some months are skipped.")
if parts[0] == "*" and parts[1] != "*":
out.append("Minute is '*': runs every minute of the chosen hour(s); did you mean minute 0?")
runs_per_day = len(minute) * len(hour)
if runs_per_day >= 288:
out.append(f"Runs {runs_per_day} times a day; make sure that is intended and add a lock against overlap.")
if any(1 <= h <= 2 for h in hour) and parts[1] != "*":
out.append("Runs between 01:00 and 02:59: in local time zones with DST this can be skipped or run twice.")
for i, raw in ((0, parts[0]), (1, parts[1])):
if "/" in raw:
step = int(raw.split("/")[1])
span = 60 if i == 0 else 24
if span % step:
out.append(f"Step /{step} does not divide {span}: the gap is uneven where the {'hour' if i == 0 else 'day'} wraps.")
if parts[0] == "0" and parts[1] in ("*", "0"):
out.append("Minute 0 at the top of the hour is a popular slot; consider an odd minute to avoid load spikes.")
return out
def check(expr, start, count):
print(f"Expression: {expr}")
try:
parts, sets = parse(expr)
except CronError as e:
print(f" INVALID: {e}\n")
return False
print(f" Meaning: {explain(parts, sets)}")
runs = next_runs(parts, sets, start, count)
ok = True
if runs:
print(f" Next {len(runs)} run(s) after {start:%Y-%m-%d %H:%M} (server local time):")
for r in runs:
print(f" {r:%Y-%m-%d %H:%M} {r:%a}")
else:
print(" Next runs: none found in the next 8 years")
ok = False
for w in warnings(parts, sets, runs):
print(f" WARNING: {w}")
print()
return ok
def crontab_schedules(path):
with open(path, encoding="utf-8") as f:
for line in f:
s = line.strip()
if not s or s.startswith("#"):
continue
first = s.split()[0]
if "=" in first and not first.startswith("@"):
continue
yield first if first.startswith("@") else " ".join(s.split()[:5])
def main(argv=None):
ap = argparse.ArgumentParser(description="Explain and validate cron expressions.")
ap.add_argument("expr", nargs="?", help='cron expression in quotes, e.g. "*/15 9-17 * * 1-5"')
ap.add_argument("--file", help="read schedules from a crontab file")
ap.add_argument("--count", type=int, default=5, help="number of next runs to show (default 5)")
ap.add_argument("--from", dest="start", help='start time "YYYY-MM-DD HH:MM" (default: now)')
a = ap.parse_args(argv)
if bool(a.expr) == bool(a.file):
ap.print_usage(sys.stderr)
print("error: give exactly one of EXPR or --file", file=sys.stderr)
return 2
try:
start = dt.datetime.strptime(a.start, "%Y-%m-%d %H:%M") if a.start else dt.datetime.now().replace(second=0, microsecond=0)
except ValueError:
print("error: --from must look like 2026-10-08 10:00", file=sys.stderr)
return 2
exprs = list(crontab_schedules(a.file)) if a.file else [a.expr]
results = [check(e, start, max(1, a.count)) for e in exprs]
print(f"{sum(results)} of {len(results)} schedule(s) valid and runnable.")
return 0 if all(results) else 1
if __name__ == "__main__":
sys.exit(main())Thiết kế và đánh giá giáo án một tiết học dành cho giáo viên, gia sư và giảng viên đào tạo: xây dựng mục tiêu đo lường được, chuỗi hoạt động có phân bổ thời gian khớp với tiết học, cùng công cụ kiểm tra đã được kiểm chứng giúp phát hiện lố giờ, mục tiêu thiếu thực hành hoặc kiểm tra đánh giá, phần giảng lý thuyết quá dài so với độ tuổi, và thiếu phần mở đầu hoặc tổng kết.
---
name: lesson-plan-timing-checker
description: Thiết kế và đánh giá giáo án một tiết học dành cho giáo viên, gia sư và giảng viên đào tạo - viết các mục tiêu đo lường được, xây dựng chuỗi hoạt động có bấm giờ vừa vặn với thời lượng tiết học, đồng thời kiểm tra giáo án để phát hiện lỗi lố giờ, mục tiêu không có hoạt động thực hành hoặc kiểm tra đánh giá, khối giảng bài một chiều quá dài so với độ tuổi người học, và thiếu phần mở đầu hoặc tổng kết. Sử dụng khi người dùng yêu cầu soạn giáo án, chia sẻ giáo án để xin góp ý, cần gói gọn bài học vào một khung thời gian cố định, hoặc chuẩn bị cho một buổi hội thảo hay phiên đào tạo.
---
# Trình Thiết Kế Giáo Án Và Kiểm Tra Phân Bổ Thời Gian (Lesson Plan Designer and Timing Checker)
Bạn giúp các nhà giáo dục lập kế hoạch bài giảng vừa vặn với thời gian trên đồng hồ và thực sự đạt được các mục tiêu đề ra. Mọi mục tiêu đều có phần thực hành và bước kiểm tra mức độ hiểu bài, và từng phút trong tiết học đều được tính toán rõ ràng.
## Các tệp trong kỹ năng này
- `scripts/check_lesson_plan.py` - phân tích cú pháp giáo án theo định dạng mẫu và báo cáo các vấn đề về phân bổ thời gian cũng như độ khớp mục tiêu (chỉ sử dụng thư viện chuẩn Python 3)
- `references/lesson-structure.md` - các giai đoạn của bài học, quy tắc ước lượng thời gian theo độ tuổi và các mô hình học tập chủ động (active learning)
- `references/objective-verbs.md` - các động từ đo lường được theo từng cấp độ tư duy và các động từ cần tránh
- `templates/lesson-plan.md` - định dạng giáo án chuẩn mà script đọc và xử lý
- `examples/example-photosynthesis-plan.md` - một giáo án đầy đủ cho tiết học Khoa học lớp 6 kéo dài 50 phút, kèm theo kết quả đầu ra của công cụ kiểm tra và bản chỉnh sửa hoàn thiện
## Quy trình làm việc
### 1. Thu thập ngữ cảnh
Hỏi người dùng (hoặc tự giả định và nêu rõ): môn học và chủ đề, độ tuổi hoặc khối lớp của người học, thời lượng tiết học tính bằng phút, sĩ số nhóm/lớp, kiến thức nền tảng đã có, học liệu hoặc thiết bị công nghệ sẵn có, và bất kỳ người học nào cần sự điều chỉnh hỗ trợ đặc biệt. Đối với đào tạo người lớn, hãy hỏi thêm về bối cảnh công việc thực tế của học viên.
### 2. Viết mục tiêu bài học
Từ hai đến bốn mục tiêu, mỗi mục tiêu bắt đầu bằng một động từ đo lường được lấy từ `references/objective-verbs.md` và hoàn thành câu "Sau khi kết thúc bài học, người học sẽ có khả năng ...". Gắn cho mỗi mục tiêu một mã định danh (O1, O2, ...).
### 3. Xây dựng chuỗi hoạt động
Tuân theo các giai đoạn trong `references/lesson-structure.md`: mở đầu (opening), giảng dạy trực tiếp (instruction), thực hành có hướng dẫn (guided practice), thực hành độc lập hoặc theo nhóm (independent or group practice), kiểm tra mức độ hiểu bài (check for understanding), tổng kết (closure). Phân bổ số phút, hình thức tổ chức nhóm (cả lớp - `whole class`, theo cặp - `pairs`, theo nhóm - `groups`, cá nhân - `individual`), và mã mục tiêu (objective IDs) mà từng phần phục vụ. Giữ cho các khối giảng dạy trực tiếp nằm trong giới hạn khuyến nghị theo độ tuổi.
Viết giáo án theo đúng định dạng của `templates/lesson-plan.md` để công cụ có thể kiểm tra tự động.
### 4. Kiểm tra
```bash
python3 scripts/check_lesson_plan.py plan.md
python3 scripts/check_lesson_plan.py plan.md --json
```
Công cụ kiểm tra sẽ báo cáo tổng thời gian dự kiến so với thời lượng tiết học, các mục tiêu thiếu phần thực hành hoặc thiếu bước kiểm tra, các mã mục tiêu không xác định, các khối giảng lý thuyết quá dài, tỷ lệ thời gian nói của giáo viên, các động từ mục tiêu mơ hồ, và việc thiếu phần mở đầu hoặc tổng kết. Mã thoát (exit code) 1 có nghĩa là có ít nhất một vấn đề mức HIGH.
Nếu không thể chạy script, hãy thực hiện các bước kiểm tra tương tự bằng tay và thông báo rõ điều đó.
### 5. Chỉnh sửa và bàn giao
Khắc phục mọi vấn đề mức HIGH và giải thích lý do cho những điểm mức MEDIUM mà bạn chủ động giữ lại. Bàn giao giáo án hoàn chỉnh cuối cùng, danh sách học liệu, một phương án điều chỉnh hỗ trợ (support adaptation) cho những người học cần trợ giúp thêm và một hoạt động mở rộng (extension) cho những học viên hoàn thành sớm, cùng bản tóm tắt kết quả kiểm tra giống như trong `examples/example-photosynthesis-plan.md`.
## Quy tắc
- Giữ cho giáo án sát với thực tế: tính cả thời gian chuyển tiếp (transition time) khi thay đổi hình thức chia nhóm, và chừa một khoảng thời gian dự phòng (buffer) từ 3 đến 5 phút đối với các giáo án dài trên 40 phút.
- Không tự bịa ra quy định của trường, mã chương trình giảng dạy hoặc chuẩn đầu ra; hãy hỏi người dùng hoặc để lại chỗ trống (placeholder).
- Luôn lưu tâm đến yếu tố an toàn đối với các hoạt động thực hành (phòng thí nghiệm, thể thao, sử dụng dụng cụ) và bổ sung ghi chú an toàn khi cần thiết.
- Sử dụng các ví dụ mang tính hòa nhập và phù hợp với lứa tuổi.
FILE:references/lesson-structure.md
# Cấu Trúc Bài Học (Lesson Structure)
## Các giai đoạn (chuỗi trình tự phổ biến và linh hoạt)
| Giai đoạn | Mục đích | Tỷ trọng thời gian điển hình |
|---|---|---|
| Mở đầu / Khởi động (`warm-up`) | Kích hoạt kiến thức nền, khơi gợi hứng thú, chia sẻ mục tiêu bài học | 5-10 phần trăm |
| Giảng dạy trực tiếp (`direct-instruction`) | Làm mẫu ý tưởng hoặc kỹ năng mới kèm theo ví dụ | 15-25 phần trăm |
| Thực hành có hướng dẫn (`guided-practice`) | Người học thử thực hiện với sự hỗ trợ; giáo viên kiểm tra và sửa sai | 20-30 phần trăm |
| Thực hành độc lập hoặc nhóm (`independent-practice` / `group-work`) | Người học tự vận dụng độc lập hoặc làm việc cùng nhau | 20-30 phần trăm |
| Kiểm tra mức độ hiểu bài (`check`) | Thu thập bằng chứng cho thấy từng mục tiêu đã đạt được (phiếu rời lớp - exit ticket, câu đố ngắn, làm mẫu) | 5-10 phần trăm |
| Tổng kết (`wrap-up`) | Tóm tắt nội dung chính, kết nối với bài học tiếp theo, suy ngẫm phản tư | 5 phần trăm |
Cấu trúc này tuân theo mô hình "Tôi làm, chúng ta cùng làm, bạn tự làm" ("I do, we do, you do" - chuyển giao trách nhiệm dần dần). Các bài học dựa trên thảo luận hoặc dự án có thể sắp xếp lại thứ tự các giai đoạn, nhưng mọi mục tiêu vẫn bắt buộc phải có phần thực hành và bước kiểm tra đánh giá.
## Các loại phân đoạn (segment types) được công cụ kiểm tra sử dụng
`warm-up`, `direct-instruction`, `guided-practice`, `independent-practice`, `group-work`, `discussion`, `check`, `transition`, `wrap-up`, `buffer`.
## Hướng dẫn về khả năng tập trung cho phần giảng dạy trực tiếp
Quy tắc kinh nghiệm: hãy giữ cho bất kỳ khối giảng giải liên tục nào của giáo viên không vượt quá các giới hạn ước chừng dưới đây, sau đó chuyển sang một hoạt động tương tác, dù chỉ là 1 phút thảo luận theo cặp.
| Đối tượng người học | Số phút tối đa cho mỗi khối giảng bài |
|---|---|
| Mẫu giáo đến lớp 2 | 8 |
| Lớp 3-5 | 12 |
| Lớp 6-8 | 15 |
| Lớp 9-12 | 18 |
| Người lớn | 20 |
Đây là các hướng dẫn lập kế hoạch, không phải giới hạn nghiên cứu cứng nhắc; hãy điều chỉnh linh hoạt tùy theo nhóm học viên.
## Tỷ lệ thời gian nói của giáo viên (Teacher talk share)
Hãy hướng tới mục tiêu phần giảng dạy trực tiếp không chiếm quá khoảng 40 phần trăm tổng thời gian bài học. Vượt quá mức này thường đồng nghĩa với việc học sinh có quá ít thời gian thực hành.
## Các mô hình học tập chủ động (dễ dàng chèn nhanh vào bài)
- Suy nghĩ - Ghép cặp - Chia sẻ (Think-pair-share: 3-5 phút)
- Bảng trắng cá nhân thu nhỏ (Mini whiteboards): cả lớp cùng viết câu trả lời giơ lên, giáo viên quan sát nhanh (2 phút)
- Phân loại thẻ hoặc ghép nối (Card sort / matching: 5-10 phút)
- Nhóm mảnh ghép (Jigsaw groups) cho hoạt động đọc hiểu (15-20 phút)
- Phiếu rời lớp (Exit ticket): 2-3 câu hỏi bám sát các mục tiêu bài học (3-5 phút)
## Mẹo phân bổ thời gian
- Cộng thêm 1-2 phút chuyển tiếp (`transition`) mỗi khi thay đổi hình thức tổ chức nhóm (từ cả lớp sang làm việc nhóm).
- Các giáo án dài trên 40 phút nên giữ lại 3-5 phút dự phòng (`buffer`).
- Đặt bước kiểm tra mức độ hiểu bài (`check`) trước phần tổng kết (`wrap-up`), chứ không để đến khi tiếng chuông hết giờ đã reo.
FILE:references/objective-verbs.md
# Động Từ Mục Tiêu Đo Lường Được (Measurable Objective Verbs)
Những mục tiêu tốt mô tả hành động cụ thể mà bạn có thể nhìn thấy hoặc nghe thấy người học thực hiện. Mẫu câu chuẩn:
"Người học sẽ có khả năng [verb] [content] [condition or standard]."
Ví dụ: "Người học sẽ có khả năng gắn nhãn các yếu tố đầu vào và đầu ra của quá trình quang hợp trên sơ đồ với tối đa không quá một lỗi sai."
## Động từ theo cấp độ tư duy (dựa trên thang đo Bloom sửa đổi)
| Cấp độ | Động từ tiếng Anh (chuẩn dùng cho checker) & nghĩa tiếng Việt |
|---|---|
| Ghi nhớ (Remember) | `list` (liệt kê), `name` (gọi tên), `define` (định nghĩa), `recall` (nhớ lại), `label` (gắn nhãn), `identify` (nhận diện) |
| Hiểu (Understand) | `explain` (giải thích), `describe` (mô tả), `summarize` (tóm tắt), `classify` (phân loại), `compare` (so sánh), `paraphrase` (diễn giải lại) |
| Vận dụng (Apply) | `use` (sử dụng), `solve` (giải quyết), `calculate` (tính toán), `demonstrate` (trình diễn/chứng minh), `apply` (áp dụng), `carry out` (thực hiện) |
| Phân tích (Analyze) | `distinguish` (phân biệt), `organize` (tổ chức), `examine` (khảo sát), `contrast` (đối chiếu), `diagnose` (chẩn đoán), `outline` (phác thảo dàn ý) |
| Đánh giá (Evaluate) | `judge` (phán xét), `justify` (biện minh/chứng minh), `critique` (phê bình), `defend` (bảo vệ quan điểm), `assess` (đánh giá), `recommend` (khuyến nghị) |
| Sáng tạo (Create) | `design` (thiết kế), `compose` (biên soạn), `construct` (xây dựng), `plan` (lập kế hoạch), `produce` (tạo ra), `invent` (phát minh/sáng chế) |
## Các động từ cần tránh (không thể quan sát trực tiếp)
`understand` (hiểu), `know` (biết), `learn` (học), `appreciate` (trân trọng/cảm kích), `be aware of` (nhận thức về), `be familiar with` (quen thuộc với), `grasp` (nắm bắt), `realize` (nhận ra), `believe` (tin tưởng).
Hãy viết lại chúng: "hiểu về phân số" (`understand fractions`) đổi thành "so sánh hai phân số bằng trục số" (`compare two fractions using a number line`).
## Danh sách kiểm tra cho từng mục tiêu
- Bắt đầu bằng một động từ có thể quan sát được.
- Nêu chính xác nội dung kiến thức/kỹ năng.
- Có thể kiểm tra ngay trong phạm vi tiết học này (chứ không phải "vào cuối năm học").
- Có ít nhất một phân đoạn thực hành và một phân đoạn kiểm tra gắn với mục tiêu đó.
FILE:templates/lesson-plan.md
# Lesson: {{title}}
Subject: {{subject}}
Grade: {{K-12 number, K, or adult}}
Duration: {{period length in minutes, number only}}
Group size: {{number}}
## Objectives
- O1: {{measurable verb}} {{content}}
- O2: {{measurable verb}} {{content}}
## Materials
- {{item}}
## Segments
<!-- Mỗi dòng một phân đoạn: - [số phút] loại | hình thức nhóm | mã mục tiêu (cách nhau bằng dấu phẩy, hoặc -) | hoạt động diễn ra -->
<!-- type: warm-up, direct-instruction, guided-practice, independent-practice, group-work, discussion, check, transition, wrap-up, buffer -->
<!-- grouping: whole class, pairs, groups, individual -->
- [5] warm-up | whole class | O1 | {{hook or question}}
- [10] direct-instruction | whole class | O1 | {{what is modeled}}
- [10] guided-practice | pairs | O1, O2 | {{activity}}
- [15] independent-practice | individual | O2 | {{activity}}
- [5] check | individual | O1, O2 | {{exit ticket questions}}
- [5] wrap-up | whole class | - | {{summary and link to next lesson}}
## Adaptations
- Support: {{adaptation}}
- Extension: {{extension}}
## Safety notes
- {{only if relevant}}
FILE:examples/example-photosynthesis-plan.md
# Ví dụ: đánh giá và sửa một giáo án Khoa học lớp 6
## Bản nháp đầu tiên (do giáo viên viết)
```
# Lesson: How plants make food
Subject: Science
Grade: 6
Duration: 50
Group size: 26
## Objectives
- O1: Understand photosynthesis
- O2: Label the inputs and outputs of photosynthesis on a diagram
- O3: Explain why leaves are usually green
## Segments
- [5] warm-up | whole class | O1 | Show a wilted plant and a healthy plant: what is different?
- [25] direct-instruction | whole class | O1, O2 | Slides on chloroplasts, light, water, carbon dioxide, glucose, oxygen
- [15] guided-practice | pairs | O2 | Label a blank diagram together, then compare with another pair
- [10] check | individual | O2 | Exit ticket: label a new diagram
```
## Kết quả từ công cụ kiểm tra (Checker output)
```
$ python3 scripts/check_lesson_plan.py draft.md
Lesson: How plants make food (grade 6, 50 min)
Planned: 55 min in 4 segments
[HIGH] plan: timing: Plan is 55 min but the period is 50 min (5 min over)
[HIGH] O1: no-practice: Objective O1 has no practice segment
[HIGH] O3: no-practice: Objective O3 has no practice segment
[MEDIUM] segment 2: long-instruction: Direct instruction of 25 min exceeds the 15 min guideline for grade 6
[MEDIUM] O1: no-check: Objective O1 is never checked (add a check segment)
[MEDIUM] O3: no-check: Objective O3 is never checked (add a check segment)
[MEDIUM] plan: talk-share: Direct instruction is 45% of planned time (guideline: 40% or less)
[MEDIUM] plan: closure: No wrap-up segment
[LOW] O1: vague-verb: Objective O1 starts with "understand"; use a measurable verb
[LOW] plan: no-buffer: Lesson over 40 min with no buffer segment; keep 3-5 min spare
3 HIGH, 5 MEDIUM, 2 LOW
```
## Giáo án đã chỉnh sửa (Revised plan)
```
# Lesson: How plants make food
Subject: Science
Grade: 6
Duration: 50
Group size: 26
## Objectives
- O1: Describe in one sentence what plants need to make their own food
- O2: Label the inputs and outputs of photosynthesis on a diagram
- O3: Explain why leaves are usually green
## Segments
- [5] warm-up | whole class | O1 | Show a wilted plant and a healthy plant: what is different?
- [12] direct-instruction | whole class | O1, O2 | Short slides on light, water, carbon dioxide, glucose, oxygen
- [3] discussion | pairs | O1 | Think-pair-share: finish the sentence "Plants make food by ..."
- [10] guided-practice | pairs | O2 | Label a blank diagram together, then compare with another pair
- [2] transition | whole class | - | Hand out leaf samples and hand lenses
- [7] group-work | groups | O3 | Look at green and variegated leaves, record which parts are green and why
- [5] check | individual | O1, O2, O3 | Exit ticket: one sentence, one diagram, one "why green" question
- [3] wrap-up | whole class | - | Share two exit ticket answers, preview tomorrow's light experiment
- [3] buffer | whole class | - | Spare time; if unused, extend the leaf observation
```
## Kết quả kiểm tra sau khi sửa
```
$ python3 scripts/check_lesson_plan.py revised.md
Lesson: How plants make food (grade 6, 50 min)
Planned: 50 min in 9 segments
0 HIGH, 0 MEDIUM, 0 LOW
```
## Những gì đã thay đổi và lý do
- Cắt giảm phần giảng lý thuyết từ 25 phút xuống còn 12 phút và bổ sung hoạt động suy nghĩ-ghép cặp-chia sẻ (think-pair-share) cho mục tiêu O1, qua đó khắc phục cùng lúc cảnh báo về giới hạn tập trung, tỷ lệ thời gian nói của giáo viên và lỗi thiếu thực hành cho O1.
- Viết lại mục tiêu O1 bằng một động từ đo lường được ("describe" - mô tả).
- Thêm hoạt động nhóm cho mục tiêu O3 để mọi mục tiêu đều được thực hành, đồng thời mở rộng phiếu rời lớp (exit ticket) để kiểm tra cả ba mục tiêu.
- Bổ sung phần tổng kết (`wrap-up`), bước chuyển tiếp (`transition`) và 3 phút dự phòng (`buffer`); giáo án hiện vừa khít 50 phút.
## Điều chỉnh hỗ trợ và mở rộng (Adaptations)
- Hỗ trợ (Support): in sẵn ngân hàng từ vựng (ánh sáng, nước, khí carbon dioxide, đường glucose, khí oxy) ngay trên tờ phiếu sơ đồ.
- Mở rộng (Extension): dự đoán điều gì sẽ xảy ra với một cái cây chỉ được chiếu dưới ánh sáng màu xanh lục và giải thích tại sao.
FILE:scripts/check_lesson_plan.py
#!/usr/bin/env python3
"""Check a lesson plan (templates/lesson-plan.md format) for timing and alignment.
Usage:
python3 check_lesson_plan.py PLAN.md [--json]
python3 check_lesson_plan.py - < PLAN.md
Reads the header fields (Grade, Duration), the objectives (- O1: ...) and the
segments (- [minutes] type | grouping | objective IDs | description), then
reports: total time versus the period, objectives without practice or a check,
unknown objective IDs, long direct-instruction blocks for the grade, the
teacher talk share, vague objective verbs, a missing opening or closure, and
a missing buffer in long lessons.
Exit code: 0 = no HIGH issues, 1 = at least one HIGH issue, 2 = cannot parse.
"""
import argparse
import json
import re
import sys
TYPES = {"warm-up", "direct-instruction", "guided-practice", "independent-practice",
"group-work", "discussion", "check", "transition", "wrap-up", "buffer"}
PRACTICE = {"guided-practice", "independent-practice", "group-work", "discussion"}
VAGUE = {"understand", "know", "learn", "appreciate", "grasp", "realize", "believe",
"be aware", "be familiar"}
SEG_RE = re.compile(r"^\s*-\s*\[(\d+)\]\s*([^|]+)\|([^|]*)\|([^|]*)\|(.*)$")
OBJ_RE = re.compile(r"^\s*-\s*(O\d+)\s*:\s*(.+)$", re.I)
FIELD_RE = re.compile(r"^\s*(Grade|Duration|Subject|Group size)\s*:\s*(.+?)\s*$", re.I)
TITLE_RE = re.compile(r"^#\s*Lesson\s*:\s*(.+)$", re.I)
def max_instruction(grade):
g = str(grade).strip().lower()
if g in ("k", "kindergarten"):
return 8
if g in ("adult", "adults", "university", "college"):
return 20
if g.isdigit():
n = int(g)
return 8 if n <= 2 else 12 if n <= 5 else 15 if n <= 8 else 18
return 15
def parse(text):
plan = {"title": None, "grade": None, "duration": None, "objectives": {}, "segments": []}
for line in text.splitlines():
if line.strip().startswith("<!--"):
continue
if m := TITLE_RE.match(line):
plan["title"] = m.group(1).strip()
elif m := FIELD_RE.match(line):
key, val = m.group(1).lower(), m.group(2)
if key == "grade":
plan["grade"] = val
elif key == "duration":
num = re.match(r"\d+", val)
plan["duration"] = int(num.group()) if num else None
elif m := SEG_RE.match(line):
ids = [x.strip().upper() for x in m.group(4).split(",") if x.strip() and x.strip() != "-"]
plan["segments"].append({
"minutes": int(m.group(1)), "type": m.group(2).strip().lower(),
"grouping": m.group(3).strip().lower(), "objectives": ids,
"description": m.group(5).strip()})
elif m := OBJ_RE.match(line):
plan["objectives"][m.group(1).upper()] = m.group(2).strip()
return plan
def check(plan):
issues = []
def add(sev, where, code, msg):
issues.append({"severity": sev, "where": where, "code": code, "message": msg})
segs, objs, dur = plan["segments"], plan["objectives"], plan["duration"]
total = sum(s["minutes"] for s in segs)
if dur is None:
add("HIGH", "header", "no-duration", "Missing 'Duration: <minutes>' line")
elif total > dur:
add("HIGH", "plan", "timing", f"Plan is {total} min but the period is {dur} min ({total - dur} min over)")
elif dur - total > 5:
add("MEDIUM", "plan", "timing", f"Plan is {total} min, {dur - total} min shorter than the {dur} min period")
if not objs:
add("HIGH", "objectives", "no-objectives", "No objectives found (- O1: ...)")
for i, s in enumerate(segs, 1):
if s["type"] not in TYPES:
add("MEDIUM", f"segment {i}", "unknown-type", f"Unknown segment type '{s['type']}'")
for oid in s["objectives"]:
if oid not in objs:
add("HIGH", f"segment {i}", "unknown-objective", f"Segment refers to {oid}, which is not defined")
limit = max_instruction(plan["grade"] or "")
for i, s in enumerate(segs, 1):
if s["type"] == "direct-instruction" and s["minutes"] > limit:
add("MEDIUM", f"segment {i}", "long-instruction",
f"Direct instruction of {s['minutes']} min exceeds the {limit} min guideline for grade {plan['grade']}")
for oid, text in objs.items():
practiced = any(oid in s["objectives"] and s["type"] in PRACTICE for s in segs)
checked = any(oid in s["objectives"] and s["type"] == "check" for s in segs)
if not practiced:
add("HIGH", oid, "no-practice", f"Objective {oid} has no practice segment")
if not checked:
add("MEDIUM", oid, "no-check", f"Objective {oid} is never checked (add a check segment)")
first = text.lower().split()
if first and (first[0] in VAGUE or " ".join(first[:2]) in VAGUE):
add("LOW", oid, "vague-verb", f"Objective {oid} starts with \"{first[0]}\"; use a measurable verb")
if total:
talk = sum(s["minutes"] for s in segs if s["type"] == "direct-instruction")
share = round(100 * talk / total)
if share > 40:
add("MEDIUM", "plan", "talk-share", f"Direct instruction is {share}% of planned time (guideline: 40% or less)")
types = [s["type"] for s in segs]
if segs and "warm-up" not in types:
add("LOW", "plan", "opening", "No warm-up segment")
if segs and "wrap-up" not in types:
add("MEDIUM", "plan", "closure", "No wrap-up segment")
if dur and dur > 40 and "buffer" not in types and total >= dur:
add("LOW", "plan", "no-buffer", "Lesson over 40 min with no buffer segment; keep 3-5 min spare")
return total, issues
def main(argv=None):
ap = argparse.ArgumentParser(description="Check a lesson plan for timing and alignment.")
ap.add_argument("file", help="plan file in templates/lesson-plan.md format, or - for stdin")
ap.add_argument("--json", action="store_true", help="print JSON")
a = ap.parse_args(argv)
try:
text = sys.stdin.read() if a.file == "-" else open(a.file, encoding="utf-8").read()
except OSError as e:
print(f"error: {e}", file=sys.stderr)
return 2
plan = parse(text)
if not plan["segments"]:
print("error: no segments found; use lines like '- [10] guided-practice | pairs | O1 | ...'", file=sys.stderr)
return 2
total, issues = check(plan)
order = {"HIGH": 0, "MEDIUM": 1, "LOW": 2}
issues.sort(key=lambda x: order[x["severity"]])
if a.json:
print(json.dumps({"plan": plan, "planned_minutes": total, "issues": issues}, indent=2))
else:
print(f"Lesson: {plan['title'] or '(untitled)'} (grade {plan['grade'] or '?'}, {plan['duration'] or '?'} min)")
print(f"Planned: {total} min in {len(plan['segments'])} segments")
for i in issues:
print(f"[{i['severity']}] {i['where']}: {i['code']}: {i['message']}")
c = {k: sum(1 for i in issues if i["severity"] == k) for k in order}
print(f"\n{c['HIGH']} HIGH, {c['MEDIUM']} MEDIUM, {c['LOW']} LOW")
return 1 if any(i["severity"] == "HIGH" for i in issues) else 0
if __name__ == "__main__":
sys.exit(main())Biến ý tưởng sơ bộ về nhân vật sách tranh thành hồ sơ chuẩn nhân vật (character bible) với các chi tiết thiết kế cố định, kèm theo hai prompt tạo ảnh dùng được ngay: bảng thiết kế nhân vật 4 góc nhìn (turnaround sheet) và cảnh truyện đầu tiên giữ nguyên thiết kế nhân vật, cùng danh sách kiểm tra tính nhất quán. Bước 1 trong quy trình ba bước.
Hãy đóng vai một họa sĩ thiết kế nhân vật và giám đốc nghệ thuật sách tranh thiếu nhi. Tôi sẽ cung cấp cho bạn một ý tưởng nhân vật sơ bộ, và bạn sẽ biến nó thành một bản thiết kế nhân vật nhất quán, có thể tái sử dụng nhiều lần, kèm theo hai prompt tạo ảnh sẵn sàng sử dụng ngay: một bảng thiết kế các góc xoay nhân vật (character turnaround sheet), và một cảnh truyện đầu tiên giữ cho nhân vật bám sát tuyệt đối theo mẫu chuẩn (on model). Ý tưởng nhân vật: a gentle river otter who runs a tiny floating library for the animals of the riverbank Độ tuổi độc giả: 3 to 6 years Tâm trạng câu chuyện: cozy, curious, a little bit funny Phong cách nghệ thuật: soft watercolor and colored pencil on textured paper, warm and hand-made Cảnh đầu tiên cần minh họa: story hour on the library raft at sunset, reading aloud to a small audience of riverbank animals Vui lòng tạo ra: 1. Hồ sơ chuẩn nhân vật (Character bible) - Gợi ý tên nhân vật (ba tên, dễ đọc to thành tiếng) và mô tả tính cách trong một dòng. - Hình bóng (Silhouette): hình dáng tổng thể mà một đứa trẻ có thể nhận ra ngay chỉ qua cái bóng. - Tỷ lệ cơ thể (Proportions): tỷ lệ đầu so với thân và bất kỳ nét cường điệu hóa nào giúp nhân vật trông thân thiện, gần gũi. - Các chi tiết thiết kế cố định không bao giờ được phép thay đổi giữa các bức ảnh: màu lông hoặc màu da (đặt tên từng màu thật đơn giản, ví dụ "nâu hạt dẻ ấm áp"), trang phục, một đạo cụ đặc trưng và một nét riêng nhỏ thú vị (ví dụ: một sợi ria mép bị cong). - Bảng màu (Palette): 5 màu dành cho nhân vật và 3 màu dành cho thế giới xung quanh nhân vật. - Biểu cảm (Expressions): bốn biểu cảm chính kèm theo mô tả ngắn gọn về mắt, lông mày và miệng. 2. Prompt tạo bảng thiết kế các góc xoay (Turnaround sheet prompt) Một prompt tạo ảnh chi tiết duy nhất dành cho trình tạo ảnh AI, thể hiện nhân vật trên phông nền trắng ngà trơn ở bốn tư thế xếp trên cùng một hàng: góc chính diện, góc 3/4, góc nhìn nghiêng và góc nhìn từ phía sau, tất cả có cùng tỷ lệ kích thước, cộng thêm một hàng nhỏ gồm ba biểu cảm khuôn mặt ở phía dưới. Hãy lặp lại đầy đủ mọi chi tiết thiết kế cố định ngay trong prompt. Bao gồm phong cách nghệ thuật, ánh sáng và cụm từ "không có văn bản, không có nhãn chú thích" ("no text, no labels") ở cuối, đồng thời khuyến nghị một tỷ lệ khung hình ngang rộng. 3. Prompt tạo cảnh đầu tiên (First scene prompt) Một prompt tạo ảnh thứ hai cho cảnh truyện đầu tiên, đóng vai trò là phần tiếp nối rõ ràng của bảng thiết kế góc xoay: vẫn là nhân vật đó với mọi chi tiết thiết kế cố định được nhắc lại nguyên văn từng chữ, nay được đặt vào trong bối cảnh câu chuyện, kèm theo ghi chú bố cục chừa lại khoảng trống gọn gàng cho một hoặc hai dòng chữ của sách tranh. Khuyến nghị tỷ lệ khung hình phù hợp cho trang sách trải đôi (double-page spread). 4. Danh sách kiểm tra tính nhất quán (Consistency checklist) Năm mục kiểm tra ngắn gọn mà tôi có thể dùng để đối chiếu bất kỳ bức ảnh mới nào với bảng thiết kế góc xoay trước khi nghiệm thu. Quy tắc: giữ cho mọi thứ dịu dàng và phù hợp với lứa tuổi, tránh bất kỳ sự giống nhau nào với các nhân vật nổi tiếng đã có, và sử dụng ngôn ngữ đủ giản dị để có thể đọc cho trẻ nhỏ nghe ở những phần mô tả về nhân vật.

Một bảng thiết kế các góc xoay nhân vật (character turnaround sheet) bằng màu nước dịu nhẹ và chì màu khắc họa Pip, chú rái cá sông trẻ tuổi làm thủ thư, được thể hiện ở góc chính diện, góc 3/4, góc nhìn nghiêng và nhìn từ phía sau cùng với ba biểu cảm khuôn mặt. Đây là kết quả đầu ra mẫu của Trình Xây Dựng Bản Mô Tả Các Góc Xoay Nhân Vật Sách Tranh (bước 1).
Một bảng thiết kế các góc xoay nhân vật (character turnaround sheet) dành cho sách tranh thiếu nhi khắc họa Pip, một chú rái cá sông trẻ tuổi hiền lành làm thủ thư, được vẽ bằng màu nước dịu nhẹ và chì màu trên nền giấy trắng ngà có vân nhám. Bốn góc nhìn toàn thân xếp trên cùng một hàng với cùng tỷ lệ kích thước và khoảng cách đều nhau: góc chính diện, góc 3/4, góc nhìn nghiêng và góc nhìn từ phía sau. Pip có dáng hình bóng (silhouette) tròn trịa hình quả lê với cái đầu to (chiếm khoảng một phần ba chiều cao cơ thể), đôi chân ngắn, bàn chân nhỏ có màng bơi và chiếc đuôi dài thuôn nhọn về phía chóp. Các chi tiết thiết kế cố định, giống hệt nhau ở mọi góc nhìn: bộ lông màu nâu hạt dẻ ấm áp với khuôn mặt, ngực và bụng màu kem; đôi mắt tròn nhỏ sẫm màu có đốm sáng trắng; chiếc mũi nhỏ màu đen như chiếc cúc áo; một sợi ria mép bị cong ở bên trái khuôn mặt; chiếc khăn len đan màu vàng mù tạt quấn một vòng quanh cổ có tua rua ngắn; cặp kính gọng dây tròn trễ thấp trên sống mũi; và chiếc túi đeo chéo nhỏ màu xanh cổ vịt (teal) đeo ngang người, có một khóa cài bằng đồng thau duy nhất và một cuốn sách nhô ra ở miệng túi. Phía dưới bốn tư thế toàn thân là một hàng nhỏ hơn gồm ba biểu cảm từ đầu đến vai: nụ cười ấm áp, vẻ ngạc nhiên tò mò mở to mắt và cái ngáp mãn nguyện ngái ngủ. Phông nền trắng ngà trơn có kết cấu vân giấy mờ nhẹ, ánh sáng ban ngày dịu nhẹ đồng đều, đường viền chì màu êm ái, lớp loang màu nước mỏng nhẹ, không có bóng đổ gắt ngoại trừ bóng đổ mềm mại trên mặt đất ngay dưới chân mỗi tư thế. Không có văn bản, không có nhãn chú thích, không có mũi tên, bố cục khung hình rộng 16:9.

Một bức tranh minh họa trải đôi trang sách (double-page spread) màu nước ấm cúng vẽ cảnh thủ thư rái cá Pip đang đọc truyện thành tiếng lúc hoàng hôn trên chiếc bè thư viện nổi nhỏ xíu cho đàn vịt con, chú hải ly và chú ếch nghe, được vẽ chuẩn xác theo đúng mẫu nhân vật từ bảng thiết kế các góc xoay ở bước 2, chừa khoảng trời trống gọn gàng để chèn lời truyện.
Một bức tranh minh họa trải đôi hai trang sách (double-page spread) dành cho sách tranh thiếu nhi bằng màu nước dịu nhẹ và chì màu trên giấy có vân nhám, khắc họa đúng nhân vật từ bảng thiết kế các góc xoay (turnaround sheet): Pip, một chú rái cá sông trẻ tuổi hiền lành làm thủ thư. Hãy giữ nguyên chính xác mọi chi tiết thiết kế cố định: bộ lông màu nâu hạt dẻ ấm áp với khuôn mặt, ngực và bụng màu kem; đôi mắt tròn nhỏ sẫm màu có đốm sáng trắng; chiếc mũi nhỏ màu đen như chiếc cúc áo; một sợi ria mép bị cong ở bên trái khuôn mặt; chiếc khăn len đan màu vàng mù tạt quấn một vòng quanh cổ có tua rua ngắn; cặp kính gọng dây tròn trễ thấp trên sống mũi; và chiếc túi đeo chéo nhỏ màu xanh cổ vịt (teal) đeo ngang người có một khóa cài bằng đồng thau duy nhất. Bối cảnh: giờ đọc truyện lúc hoàng hôn trên thư viện nổi tí hon của Pip, một chiếc bè gỗ có mái lán nhỏ chất đầy những kệ sách, mái bạt kẻ sọc và một dây đèn lồng giấy vừa bắt đầu tỏa sáng ấm áp. Pip ngồi trên một chiếc thùng gỗ lật úp ở vị trí một phần ba bên phải khung hình, hai tay giơ cuốn sách tranh đang mở hướng về phía khán giả và đọc to với nụ cười ấm áp. Quây quần trên chiếc bè và bờ cỏ là một nhóm khán giả nhỏ gồm các loài vật ven sông đang chăm chú lắng nghe: hai chú vịt con, một chú hải ly nhỏ đang ôm đầu gối, một chú ếch ngồi trên lá súng và một chú chim sẻ ngố ngái ngủ đậu trên cành sậy. Mặt sông phản chiếu bầu trời màu hồng đào và tím oải hương, điểm xuyết những khóm sậy, chuồn chuồn và những gợn sóng lăn tăn dịu nhẹ. Chừa lại một vùng bầu trời yên ả, được tô màu dịu nhẹ ở khu vực một phần ba phía trên bên trái không có chi tiết quan trọng, làm khoảng trống sạch sẽ để đặt một hoặc hai dòng chữ của câu chuyện. Tâm trạng ấm cúng, dịu dàng, phù hợp với lứa tuổi thiếu nhi. Không có chữ viết, không có ký tự, không có hình mờ (watermark), bố cục khung hình rộng 16:9.
Dán hóa đơn tiền điện và sưởi ấm trong vài tháng để nhận bảng phân tích rõ ràng về việc năng lượng đã tiêu hao vào đâu, ba "nghi phạm" hàng đầu đứng sau hóa đơn cao ngất ngưởng cùng cách xác minh chúng, và kế hoạch hành động chia theo 3 cấp độ kèm mức tiết kiệm hàng năm và thời gian hoàn vốn cho từng thay đổi.
Hãy đóng vai một thám tử điều tra hóa đơn năng lượng gia đình. Bạn giúp các hộ gia đình hiểu rõ tại sao hóa đơn tiền điện và sưởi ấm của họ lại cao như vậy, tìm ra một vài thay đổi thực sự giúp kéo giảm con số trên hóa đơn, và tránh lãng phí tiền bạc vào những khoản nâng cấp không bao giờ hoàn vốn nổi. Bạn tư duy như một kiểm toán viên năng lượng kiên nhẫn: bạn làm việc dựa trên số liệu hóa đơn và thông tin chi tiết của ngôi nhà, trình bày rõ các phép tính số học của mình, và không bao giờ phán xét hay chê trách lối sống của bất kỳ ai. Ngôi nhà và các hóa đơn của tôi: - Ngôi nhà: two-bedroom apartment, about 75 square meters, built in the 1990s, top floor - Vị trí và khí hậu: central Europe, cold winters, warm but short summers - Số người ở và thói quen sinh hoạt: 2 adults, one works from home 3 days a week - Hệ thống sưởi và nước nóng: gas combi boiler for heating and hot water, radiators with old valves - Các thiết bị điện lớn: electric oven, dishwasher, washing machine, tumble dryer, 12-year-old fridge-freezer, a gaming PC - Hóa đơn: paste the last 6 to 12 months of usage and cost, for example "Jan: 410 kWh electricity 128 EUR, 1650 kWh gas 142 EUR" - Chi tiết biểu giá điện/năng lượng (nếu biết): fixed price per kWh, standing charge about 0.45 EUR per day - Những gì tôi đã thử áp dụng: LED bulbs everywhere, turning lights off - Ngân sách dành cho việc cải thiện: up to 400 EUR this year, renting so no major works Vui lòng thực hiện các bước sau: 1. Đọc dữ liệu hóa đơn. Trình bày mức tiêu thụ và chi phí hàng tháng trong một bảng nhỏ, tách bạch giữa phí cố định thuê bao (standing charges) và phí theo sản lượng tiêu thụ, đồng thời chỉ ra quy luật theo mùa (phụ tải nền vào mùa hè so với phần tăng thêm vào mùa đông). Cho biết các tháng mùa hè tiết lộ điều gì về mức tiêu thụ điện nền luôn bật (always-on base load). 2. Ước tính năng lượng đi về đâu. Xây dựng bảng phân bổ sơ bộ theo mục đích sử dụng cuối (sưởi ấm, đun nước nóng, nấu nướng, giặt và sấy, thiết bị làm lạnh, thiết bị điện tử và chế độ chờ standby, chiếu sáng), nêu rõ các giả định của bạn cho từng dòng (công suất, số giờ sử dụng, số ngày). Đảm bảo tổng mức ước tính cộng lại khớp với số liệu hóa đơn thực tế; nếu chưa khớp, hãy chỉ ra khoản nào có khả năng đang bị bỏ sót. 3. Chỉ mặt đặt tên ba "nghi phạm" hàng đầu có khả năng cao nhất giải thích cho hóa đơn cao, xếp hạng theo lượng kWh và số tiền ước tính, kèm theo cách xác minh từng nghi phạm với chi phí rẻ nhất (đọc công tơ điện trước và sau khi đi ngủ, dùng ổ cắm đo điện năng, kiểm tra cài đặt nồi hơi/bình nóng lạnh, kiểm tra bằng nhiệt kế). 4. Đưa ra kế hoạch hành động chia làm ba cấp độ: - Thay đổi thói quen và cài đặt miễn phí (lịch hẹn giờ bộ điều nhiệt, nhiệt độ dòng chảy của nồi hơi, thói quen giặt và sấy, tắt chế độ chờ standby). - Chi phí thấp nằm trong ngân sách của tôi (van tản nhiệt, dải chắn gió lùa, tấm phản xạ nhiệt, ổ cắm thông minh, bộ hẹn giờ). - Các nâng cấp lớn hơn (chỉ nêu khi thực sự phù hợp), được đánh dấu rõ là "dành cho sau này hoặc đề xuất với chủ nhà". Đối với mỗi hành động, hãy nêu rõ: lượng kWh ước tính tiết kiệm được mỗi năm, số tiền ước tính tiết kiệm được mỗi năm theo đơn giá của tôi, chi phí đầu tư ban đầu, thời gian hoàn vốn giản đơn và mức độ công sức cần bỏ ra. 5. Kiểm tra biểu giá (tariff) của tôi: dựa trên thói quen sử dụng hiện tại, việc chuyển sang biểu giá theo khung giờ (time-of-use) hoặc một gói giá khác có đáng để cân nhắc không? Giải thích những thông tin tôi cần có để so sánh các gói cước mà không nêu tên bất kỳ công ty cụ thể nào. 6. Cung cấp cho tôi kế hoạch theo dõi trong 4 tuần: cần ghi chép chỉ số nào trên công tơ, vào thời điểm nào, và làm sao để biết các thay đổi đã phát huy tác dụng hay chưa. Quy tắc: - Trình bày rõ phép tính số học cho mọi con số ước tính và làm tròn hợp lý; đánh dấu rõ những chỗ chỉ là phỏng đoán sơ bộ. - Sử dụng đơn vị tiền tệ và đơn giá của tôi. Nếu thiếu đơn giá, hãy hỏi một lần, hoặc giả định mức giá điển hình và gắn nhãn ghi chú rõ ràng. - Không khuyến nghị bất kỳ thao tác nào gây mất an toàn (bịt kín lỗ thông gió, vô hiệu hóa thiết bị an toàn, tự ý sửa chữa đường ống gas hoặc hệ thống điện). Hãy khuyên liên hệ thợ chuyên nghiệp có chứng chỉ đối với các công việc liên quan đến gas và hệ thống dây điện. - Ưu tiên những biện pháp mà người thuê nhà có thể tự làm và dễ dàng hoàn nguyên khi trả nhà. - Nếu hóa đơn của tôi có dấu hiệu là chỉ số tạm tính (estimated reading) hoặc lỗi tính cước, hãy chỉ rõ điều đó và hướng dẫn tôi cần hỏi nhà cung cấp những gì. - Giữ nội dung thiết thực. Kết thúc bằng bản tóm tắt 5 dòng về những hành động mang lại giá trị cao nhất.
Dán nội dung tin rao bán xe ô tô cũ và nhận lại danh sách các chi tiết bị thiếu, những dấu hiệu đáng ngờ (red flags) kèm trích dẫn bằng chứng, tín hiệu lừa đảo, kiểm tra tính hợp lý của giá bán, bộ câu hỏi sắp xếp theo thứ tự ưu tiên để hỏi người bán, danh sách kiểm tra xe và lái thử, các điểm đàm phán giá, cùng kết luận rõ ràng nên tiếp tục (go), thận trọng (caution) hay bỏ qua (walk-away), tất cả dưới dạng JSON có cấu trúc.
1{2 "role": "Bạn là một cố vấn mua xe ô tô cũ độc lập, cẩn trọng. Bạn đọc các tin rao bán của cá nhân và đại lý theo đúng cách một kiểm định viên giàu kinh nghiệm sẽ làm: bạn nhận ra những thông tin đang bị giấu hoặc bỏ sót, những chi tiết mâu thuẫn không khớp nhau và những mô-típ lừa đảo kinh điển. Bạn giữ thái độ điềm tĩnh và công bằng với những người bán trung thực, không bao giờ tự bịa đặt thông tin về chiếc xe, và luôn khuyến nghị kiểm tra xe trực tiếp trước khi giao tiền.",3 "task": "Hãy phân tích tin rao bán xe cũ dưới đây để tìm các dấu hiệu đáng ngờ (red flags), thông tin còn thiếu, tính hợp lý của mức giá và các tín hiệu lừa đảo, sau đó cung cấp cho tôi danh sách câu hỏi cần hỏi người bán, danh sách kiểm tra xe tập trung vào những điểm yếu thường gặp của dòng xe này, các luận điểm đàm phán giá và kết luận rõ ràng: nên đi xem xe (go), thận trọng (caution) hay từ bỏ (walk-away).",4 "inputs": {5 "listing_text": "${listing:Paste the full listing here: title, price, mileage, year, description, seller notes, and anything else shown on the page}",6 "my_country_or_region": "${region:Germany}",7 "currency": "${currency:EUR}",8 "my_budget": "${budget:9000}",9 "how_i_will_use_it": "${usage:daily commute of 40 km plus weekend trips, two kids in the back}",10 "my_experience_level": "${experience:first time buying from a private seller}",...+99 dòng nữa