prompts.chatprompts.chatprompts.chat
BotPromptKỹ năngQuy trìnhDanh mụcThẻCao thủ PromptPhong cách
SáchCho trẻ emNhà phát triển
Đăng nhập
CC0 2026 prompts.chat
DeepWikiHướng dẫn...Tài liệuAPIQuyền riêng tưĐiều khoảnHỗ trợGiới thiệuGitHub
F

Fatih Kadir Akın

@f

446prompt
0lượt bình chọn nhận được
0đóng góp
Tham gia about 17 hours ago
446 đóng góp trong năm qua
Tháng 11
Tháng 12
Tháng 1
Tháng 2
Tháng 3
Tháng 4
Tháng 5
Tháng 6
Tháng 7
Tháng 8
Tháng 9
Tháng 10
M
W
F
Ít
Nhiều
Kế Hoạch An Toàn Và Thời Điểm Quay Đầu Khi Đi Bộ Đường Dài Trong Ngày (JSON)
Văn bản

Mô tả cung đường mòn, nhóm của bạn và điều kiện thực tế để nhận một kế hoạch đi bộ đường dài trong ngày bằng định dạng JSON nghiêm ngặt: quyết định nên đi (go), thận trọng (caution) hay hủy chuyến (no-go), ước tính tốc độ di chuyển kèm phép tính số học, thời điểm bắt buộc phải quay đầu trước lúc mặt trời lặn, lịch trình theo khung giờ, các điểm ra quyết định, lượng nước và thức ăn, danh sách kiểm tra trang bị theo mùa, lộ trình rút lui sớm, kế hoạch chuyến đi gửi lại cho người ở nhà và hướng dẫn xử lý khẩn cấp.

1{
2 "role": "Người lập kế hoạch đi bộ đường dài trong ngày cẩn thận, giàu kinh nghiệm, chuyên giúp những người đi bộ đường dài không chuyên và trình độ trung bình biến một cung đường đã chọn thành một kế hoạch thực tế, ưu tiên an toàn trên hết cho chuyến đi một ngày, với cách tính thời gian trung thực, thời điểm quay đầu kiên quyết và các quy tắc rõ ràng về thời điểm cần thay đổi kế hoạch",
3 "task": "Chuyển đổi thông tin về cung đường mòn, nhóm và điều kiện của tôi thành một kế hoạch đi bộ đường dài trong ngày dưới dạng JSON nghiêm ngặt duy nhất: đánh giá nên đi hay không (go hoặc no-go), lịch trình có gắn mốc thời gian kèm theo thời điểm bắt buộc phải quay đầu, ước tính tốc độ có tính đến khoảng cách và độ cao, lượng nước và thức ăn, danh sách trang bị phù hợp với mùa, kiểm tra thời tiết và ánh sáng ban ngày, các điểm ra quyết định dọc theo lộ trình, kế hoạch rút lui sớm (bail-out plan), bản kế hoạch chuyến đi để gửi lại cho người ở nhà, và hướng dẫn khẩn cấp ngắn gọn. Tuyệt đối không tự bịa ra thông tin thực tế về đường mòn, khoảng cách, nguồn nước hoặc các quy định mà tôi không cung cấp.",
4 "context": {
5 "trail": "${trail:a loop to a mountain lake and a viewpoint, 14 km round trip, about 750 m of total climb, rocky in the upper section}",
6 "date_and_season": "${date:a Saturday in late October}",
7 "start_location_and_elevation": "${start:trailhead car park at 900 m}",
8 "highest_point": "${high_point:viewpoint at 1600 m}",
9 "group": "${group:three adults, one is a beginner, everyone reasonably fit}",
10 "experience": "${experience:a few easy 8 km hikes, no navigation course}",
...+143 dòng nữa
TravelPlanningSafety+3
F@f
0
Trình Xây Dựng Lịch Trình Nấu Bữa Tối Dịp Lễ (YAML)
Văn bản

Chuyển đổi thực đơn ngày lễ, số lượng khách và thiết lập nhà bếp thành kế hoạch nấu ăn định dạng YAML được tính ngược từ giờ dọn món: kiểm tra thực đơn và chế độ ăn uống, danh sách mua sắm được điều chỉnh theo khẩu phần, rã đông an toàn, các công việc chuẩn bị trước, kế hoạch phân bổ công suất lò nướng và bếp nấu, lịch trình chi tiết từng phút trong ngày diễn ra bữa tiệc, các mốc kiểm tra an toàn thực phẩm, phân công việc cho người phụ bếp, cách phục vụ, xử lý đồ ăn thừa và phương án dự phòng khi bị trễ giờ.

1role: Người lập kế hoạch nấu ăn tại nhà điềm tĩnh, có tổ chức, chuyên biến một bữa tối dịp lễ lớn hoặc tiệc kỷ niệm thành một lịch trình nấu nướng thực tế, từng bước một để mọi món ăn đều sẵn sàng, nóng hổi và an toàn vào cùng một thời điểm mà chủ nhà vẫn có thời gian ngồi xuống dùng bữa
2task: >
3 Chuyển đổi thực đơn, danh sách khách mời, thiết bị nhà bếp và thời gian dọn món của tôi thành một kế hoạch nấu ăn hoàn chỉnh dưới dạng YAML:
4 kiểm tra thực đơn, danh sách mua sắm được điều chỉnh theo số lượng khách, lịch trình chuẩn bị trước cho những ngày trước đó, lịch trình theo từng giờ
5 và sau đó là từng phút cho chính ngày hôm đó được tính ngược từ thời gian dọn món, kế hoạch phân bổ công suất lò nướng
6 và bếp nấu để không có món nào tranh giành cùng một không gian, các điểm kiểm tra an toàn thực phẩm, nhiệm vụ cho người phụ giúp,
7 kế hoạch phục vụ và bảo quản thức ăn thừa, cùng kế hoạch dự phòng nếu có món bị trễ.
8
9inputs:
10 occasion: ${occasion:a family holiday dinner}
...+98 dòng nữa
PlanningYAMLChecklist+2
F@f
0
Tranh Màu Nước Phóng Khoáng Vẽ Chim Bói Cá Lao Mình Xuống Suối Rừng
Hình ảnh
Tranh Màu Nước Phóng Khoáng Vẽ Chim Bói Cá Lao Mình Xuống Suối Rừng

Tranh màu nước phóng khoáng trên giấy có vân: một chú chim bói cá thông thường màu xanh cô-ban và cam đang lao xuống gần như thẳng đứng, chiếc mỏ vừa chạm mặt nước tạo nên một chùm tia nước trắng xóa và những gợn sóng đồng tâm, một cành cây phủ rêu với những chiếc lá liễu rủ xuống ở góc trên bên trái, những mảng màu loang xanh lá và xanh mòng két dịu nhẹ ở bờ suối bên phải, hiệu ứng loang màu ướt trên ướt và những khoảng sáng từ nền giấy trắng.

Một bức tranh màu nước biểu cảm, phóng khoáng vẽ một chú chim bói cá thông thường đang lao đầu xuống dòng suối rừng tĩnh lặng, trong bố cục ngang rộng 16:9 trên nền giấy vẽ màu nước màu trắng có vân. Chú chim nằm ở trung tâm khung hình, lao xuống gần như thẳng đứng với đôi cánh khép chặt dọc theo cơ thể: phần lưng và cánh màu xanh cô-ban và xanh ngọc lam rực rỡ điểm xuyết những đốm xanh nhạt hơn, phần ngực và bụng màu cam gỉ sắt đậm đà, một mảng trắng nhỏ ở cổ, và chiếc mỏ dài sẫm màu sắc như dao găm có phần chóp vừa chạm vào mặt nước. Tại nơi chiếc mỏ tiếp xúc với bề mặt nước, một chùm nước bắn tung tóe màu trắng và những giọt nước văng lên không trung, bao quanh bởi những gợn sóng đồng tâm màu xanh xám lan tỏa khắp mặt nước. Ở góc trên bên trái, một cành cây phủ rêu màu xanh ô-liu vươn vào khung hình từ rìa tranh, với những chiếc lá liễu dài mảnh mai rủ xuống phía bên trái hướng về mặt nước, chính là cành cây mà chú chim vừa rời đi. Ở bên phải, một bờ suối mờ ảo, mềm mại được vẽ bằng những lớp màu loang xanh lá cây, xanh mòng két và vàng lục phản chiếu mờ nhạt dưới dòng suối. Hiệu ứng loang màu ướt trên ướt (wet-on-wet), hạt màu kết tủa, những giọt màu chảy và vẩy phóng khoáng, các chi tiết sắc nét chỉ tập trung ở chú chim và chùm nước bắn, còn phần màu trắng của giấy được chừa trống không tô để làm vùng sáng (highlight) và tạo hiệu ứng mờ viền (vignette) dịu nhẹ quanh các cạnh. Tâm trạng tươi mới, sống động và tự nhiên. Không có văn bản, không có chữ ký, không có khung tranh.
VisualArtCreative+1
F@f
0
Cáo Bắc Cực Chân Thực Như Ảnh Chụp Cuộn Tròn Trong Tuyết Vào Giờ Xanh
Hình ảnh
Cáo Bắc Cực Chân Thực Như Ảnh Chụp Cuộn Tròn Trong Tuyết Vào Giờ Xanh

Ảnh chụp động vật hoang dã bằng ống kính tele góc thấp chân thực về một chú cáo Bắc Cực trắng muốt cuộn tròn như quả bóng trên lớp tuyết bột mới rơi, chiếc đuôi xù quấn quanh cơ thể, hai mắt lim dim ngái ngủ, những bông tuyết đậu trên bộ lông, phía sau là vùng lãnh nguyên tuyết phủ mờ ảo, ánh sáng dịu nhẹ của giờ xanh và một vệt sáng hồng mỏng manh nơi đường chân trời.

Một bức ảnh chụp động vật hoang dã chân thực về một chú cáo Bắc Cực trong bộ lông mùa đông dày trắng muốt, cuộn tròn như một quả bóng trên lớp tuyết bột mới rơi ở chính giữa khung hình ngang rộng 16:9. Chiếc đuôi trắng xù của nó quấn quanh phía trước cơ thể và che kín các bàn chân, với chiếc mũi đen nằm ngay phía trên đuôi. Cả hai mắt mở hờ, sẫm màu và ngái ngủ, nhìn một cách bình thản về phía máy quay, đôi tai nhỏ bo tròn dựng đứng, và một vài bông tuyết mịn màng đậu trên bộ lông dày rậm. Chú cáo nằm trong một hốc tuyết mềm mại, được chụp từ góc thấp ngang tầm mắt sát mặt đất. Phía sau nó, một vùng lãnh nguyên tuyết phủ rộng lớn mờ dần thành một dải mờ mịn màng của những cồn tuyết thoai thoải dưới ánh sáng dịu nhẹ của giờ xanh (blue hour), với một dải ánh sáng hồng mỏng manh dọc theo đường chân trời xa xôi và bầu trời màu tím oải hương nhạt phía trên. Ống kính tele, độ sâu trường ảnh nông, lấy nét sắc sảo vào khuôn mặt và kết cấu bộ lông, tông màu xanh lam và trắng mát mẻ điểm xuyết chút sắc hồng, màu sắc tự nhiên, tâm trạng tĩnh lặng và yên bình. Không có văn bản, không có hình mờ (watermark), không có người.
PhotographyVisualRealism+2
F@f
0
Những Chú Robot Đồ Chơi Bằng Thiếc Lên Dây Cót Cổ Điển Diễu Hành Trên Kệ Cửa Hàng Đồ Chơi
Hình ảnh
Những Chú Robot Đồ Chơi Bằng Thiếc Lên Dây Cót Cổ Điển Diễu Hành Trên Kệ Cửa Hàng Đồ Chơi

Ảnh chụp macro ấm áp gồm năm chú robot bằng thiếc lên dây cót thập niên 1950 hình hộp có màu đỏ, xanh mòng két, kem, vàng mù tạt và đỏ xếp thành hàng trên chiếc kệ gỗ cũ kỹ, mỗi chú đều có mặt đồng hồ trước ngực, ăng-ten và chìa khóa lên dây cót bằng đồng thau, chú cuối cùng cầm một lá cờ hiệu tam giác màu đỏ, phía sau là những kệ đồ chơi cũ làm mờ và ánh sáng cửa sổ màu vàng rực chiếu từ bên trái.

Một bức ảnh chụp sản phẩm cận cảnh (macro) gồm năm chú robot đồ chơi bằng thiếc lên dây cót cổ điển thập niên 1950 đứng cạnh nhau thành một hàng diễu hành trên chiếc kệ gỗ cũ kỹ, trong khung hình ngang rộng 16:9. Những chú robot có dáng hình hộp và mang phong cách hoài cổ (retro), mỗi chú đều có cái đầu vuông, đôi mắt tròn hoặc kính che mắt, một chiếc ăng-ten nhỏ có gắn quả cầu trên đỉnh, một mặt đồng hồ đo hình tròn trên ngực, những chiếc đinh tán in nhỏ xíu, các nút bấm và đèn, đôi tay ngắn cứng đơ, cùng một chìa khóa lên dây cót bằng đồng thau ở sau lưng. Màu sơn thiếc của chúng từ trái sang phải là red, teal, cream, mustard yellow, and red, với lớp sơn hơi sờn, những vết xước nhẹ và một chút rỉ sét ở các cạnh. Chú robot ở ngoài cùng bên phải cầm một cây cột kim loại mảnh với một lá cờ hiệu hình tam giác nhỏ màu đỏ không có chữ, như thể đang dẫn đầu đoàn diễu hành. Phía sau chúng, nằm ngoài vùng lấy nét, là những chiếc kệ gỗ tối màu bày đồ chơi cũ: một chú gấu bông bên cửa sổ, một quả địa cầu nhỏ, một quả bóng màu đỏ, một chiếc xe tải đồ chơi và một ngôi nhà búp bê nhỏ. Ánh sáng cửa sổ màu vàng ấm áp chiếu từ bên trái, tạo nên những vùng sáng dịu nhẹ trên lớp thiếc và bóng đổ mềm mại trên chiếc kệ gỗ trầy xước. Độ sâu trường ảnh nông, tông màu nâu ấm áp đậm đà, tâm trạng hoài niệm và vui tươi. Không có chữ đọc được, không có logo, không có người.
PhotographyVisualCreative+3
F@f
0
Trình Xây Dựng Bản Tóm Tắt Ý Tưởng Cảnh Nhà Thờ Bên Bãi Biển Cát Đen Iceland
Văn bản

Xây dựng bản tóm tắt sẵn sàng tạo ảnh và tạo chuyển động cho một cảnh quay chủ đạo về ngôi nhà thờ trắng mái đỏ trên mũi đất phủ cỏ phía trên bãi biển cát đen Iceland với các cột đá bazan trên biển vào giờ vàng: nhà thờ, bãi biển và sóng vỗ, các cột đá biển, ánh sáng, các chi tiết cố định, bản đồ chuyển động và hai prompt hạt giống: một ảnh tĩnh 16:9 và một hoạt họa từ ảnh sang video.

Hãy đóng vai một nhà thiết kế phong cảnh điện ảnh chuyên viết các bản tóm tắt cảnh quay sẵn sàng tạo ảnh và tạo chuyển động. Hãy giúp tôi thiết kế một cảnh quay chủ đạo (hero shot) về một ngôi nhà thờ gỗ nhỏ màu trắng có mái đỏ nằm trên một mũi đất phủ cỏ phía trên bãi biển cát đen Iceland, với những cột đá bazan tối màu nhô lên từ đại dương vào giờ vàng. Cảnh quay này trước tiên sẽ được tạo dưới dạng một bức ảnh tĩnh và sau đó được hoạt họa thành một đoạn video ngắn sử dụng bức ảnh đó làm khung hình đầu tiên. Bản tóm tắt của bạn phải làm cho bức ảnh tĩnh phong phú về các yếu tố có thể chuyển động tự nhiên (những dải sóng cuộn vào và tràn lên bãi cát đen, bọt nước rút xuống, bọt sóng biển đập tung tóe vào chân các cột đá, mây trôi chậm rãi, ánh sáng ấm áp chuyển dịch đôi chút, cỏ lay động trong gió) trong khi vẫn giữ bố cục tĩnh lặng, dễ nhìn và dễ hoạt họa.

Các lựa chọn ban đầu của tôi:
- Nhà thờ: a small white wooden church with a red gabled roof and a short red steeple
- Mũi đất: a rounded grassy green headland that drops to the beach
- Bãi biển: a long curve of fine black volcanic sand
- Cột đá biển: a few dark basalt sea stacks offshore, one tall and jagged, the others smaller spires and a low rounded rock
- Thời gian và ánh sáng: golden hour with a low sun near the horizon and scattered clouds lit orange, pink, and purple
- Phong cách hình ảnh: photoreal landscape photography, natural colors
- Từ khóa tâm trạng: remote, peaceful, dramatic, warm

Trước tiên hãy hỏi tôi, trong một tin nhắn ngắn gọn, về bất kỳ thông tin nào còn thiếu: vị trí đặt máy quay mà tôi muốn (thấp trên bãi biển ngay mép nước, hơi nâng cao từ các cồn cát, hoặc từ lối mòn trên vách đá), nhà thờ và các cột đá biển nên nằm ở bên nào của khung hình, mặt trời nên ở đâu, mặt biển nên động đến mức nào, và bất kỳ thứ gì cần tránh (người, xe hơi, thiết bị bay không người lái trên bầu trời, chim chóc đòi hỏi phải bay sao cho thuyết phục, văn bản, logo, hoặc sao chép một bức ảnh chụp thực tế cụ thể).

Sau đó hãy tạo bản tóm tắt với các phần sau:

1. **Tổng quan Cảnh quay (Scene Overview)**: hai hoặc ba câu mô tả địa điểm, thời gian, ánh sáng và cảm xúc của khoảnh khắc đó. Hãy mô tả một khung cảnh bờ biển phía nam Iceland tưởng tượng mang đúng tinh thần của vùng đất này, chứ không phải bản sao của một bức ảnh cụ thể hay một nhà thờ có tên tuổi.

2. **Nhà thờ và Mũi đất (Church and Headland)**: vị trí và kích thước của nhà thờ trong khung hình, những bức tường trắng, mái đỏ và tháp chuông, cửa sổ và cửa ra vào, cách mặt trời thấp chiếu sáng ngôi nhà thờ, cũng như hình dáng của mũi đất phủ cỏ bên dưới và nơi nó tiếp giáp với bãi biển.

3. **Bãi biển và Sóng vỗ (Beach and Surf)**: đường cong của bãi cát đen từ tiền cảnh, sự phản chiếu trên lớp cát ướt, có bao nhiêu dải sóng vỗ, hướng sóng cuộn vào, và bọt nước tràn lên xa đến đâu.

4. **Các Cột đá biển và Chiều sâu (Sea Stacks and Depth)**: có bao nhiêu cột đá, hình dáng và chiều cao của chúng, vị trí của chúng từ trái sang phải, nơi bọt nước đập vào chúng, và các lớp chiều sâu từ lớp cát ở tiền cảnh cho đến đường chân trời.

5. **Ánh sáng và Bầu trời (Light and Sky)**: vị trí và độ cao của mặt trời, dải chuyển màu của bầu trời, hình dáng đám mây và cách chúng được chiếu sáng, những bóng đổ dài trên mũi đất và bãi cát, cùng bất kỳ làn sương mù nào trên mặt biển.

6. **Các Chi tiết Hình ảnh Cố định (Fixed Visual Details)**: danh sách đánh số từ 8 đến 10 chi tiết phải giữ nguyên hệt nhau trong cả bức ảnh tĩnh lẫn trong video (vị trí và màu sắc của nhà thờ, hình dáng tháp chuông, đường viền mũi đất, đường cong bãi biển, số lượng và thứ tự của các cột đá biển, vị trí mặt trời, đường chân trời, bố cục đám mây, vị trí máy quay, khung hình).

7. **Bản đồ Chuyển động (Motion Map)**: một bảng liệt kê mọi yếu tố có thể chuyển động trong video, với các cột: yếu tố, loại chuyển động, hướng, tốc độ (rất chậm, chậm, trung bình), và mức độ chuyển động mạnh hay nhẹ. Bao gồm các dải sóng vỗ, bọt nước trên cát, bọt nước tung tóe ở các cột đá, những đám mây, thảm cỏ, ánh sáng và chuyển động của máy quay. Giữ tổng thể chuyển động tinh tế và hợp lý về mặt vật lý, với một hướng sóng và một hướng gió duy nhất.

8. **Prompt Hạt giống A - Ảnh Tĩnh Chủ đạo (Prompt Seed A - Hero Still Image)**: một prompt tạo ảnh sẵn sàng để dán dưới 170 từ cho khung hình ngang rộng 16:9, với nhà thờ trên mũi đất, bãi biển cát đen và các cột đá biển ngay trong câu đầu tiên, kết thúc bằng: không có người, không có văn bản, không có logo.

9. **Prompt Hạt giống B - Hoạt họa Từ Ảnh Sang Video (Prompt Seed B - Image-to-Video Animation)**: một prompt tạo video sẵn sàng để dán dưới 120 từ bắt đầu từ bức ảnh tĩnh chủ đạo làm khung hình đầu tiên, mô tả sóng cuộn vào và tràn lên bãi cát, bọt nước tung tóe ở các cột đá, mây trôi, một chuyển động máy quay rất chậm trong khoảng 6 giây, và yêu cầu giữ nguyên nhà thờ, mũi đất, các cột đá cũng như khung hình y hệt như trong ảnh mà không thêm vật thể mới nào.

Quy tắc: không có người, động vật hoặc phương tiện giao thông làm chủ thể; không có văn bản, biển báo hoặc logo; không có chuyển động máy quay nhanh hoặc cắt cảnh; và không có chuyển động nào vi phạm quy luật vật lý (sóng luôn đến từ biển và vỗ vào bờ, bọt nước trượt ngược xuống dốc, đá và nhà thờ không bao giờ di chuyển hay thay đổi hình dạng, mặt trời không nhảy vị trí). Sử dụng các đoạn văn ngắn và danh sách.
TravelPlanningImage To Video+2
F@f
0
Nhà Thờ Trắng Mái Đỏ Phía Trên Bãi Biển Cát Đen Và Các Cột Đá Bazan Trên Biển Vào Giờ Vàng
Hình ảnh
Nhà Thờ Trắng Mái Đỏ Phía Trên Bãi Biển Cát Đen Và Các Cột Đá Bazan Trên Biển Vào Giờ Vàng

Phong cảnh giờ vàng chân thực như ảnh chụp: một nhà thờ nhỏ màu trắng với mái đỏ và tháp chuông trên mũi đất phủ cỏ ở một phần ba bên trái, một đường cong cát đen bóng loáng, sóng trắng cuộn vào từ bên phải, bốn tảng đá bazan tối màu và các cột đá biển ngoài khơi, mặt trời thấp ở ngoài cùng bên trái cùng những đám mây màu cam, hồng và tím. Bước 2 trong quy trình Nhà Thờ Bên Bãi Biển Cát Đen Iceland.

Một nhà thờ bằng gỗ nhỏ màu trắng với mái đầu hồi màu đỏ và tháp chuông nhọn màu đỏ nằm trên một mũi đất xanh phủ cỏ bo tròn phía trên bãi biển cát đen Iceland vào giờ vàng, với những cột đá bazan tối màu nhô lên từ đại dương ở bên phải. Ngôi nhà thờ nằm ở một phần ba bên trái của khung hình, gần đỉnh mũi đất, những bức tường trắng và cửa sổ nhỏ được chiếu sáng bằng sắc vàng ấm áp bởi mặt trời xuống thấp. Sườn cỏ của mũi đất thoai thoải đổ xuống bãi biển, và một đường cong rộng của lớp cát núi lửa màu đen mịn màng trải dài từ góc dưới bên trái ngang qua tiền cảnh, ướt át và bóng loáng gần mép nước. Những con sóng bọt trắng xóa cuộn vào từ bên phải thành nhiều dải dài và tràn lên bãi cát đen. Trên mặt biển ở nửa bên phải là bốn tảng đá bazan tối màu: một tảng đá thấp bo tròn gần cuối mũi đất, một cột đá biển lởm chởm cao vút ở phía trung tâm bên phải, và hai chóp đá nhọn nhỏ hơn ở xa hơn về bên phải. Mặt trời thấp nằm gần đường chân trời ở ngoài cùng bên trái phía sau mũi đất, tỏa ánh vàng cam rực rỡ, và bầu trời tràn ngập những đám mây rải rác được thắp sáng sắc cam, hồng và tím trên nền xanh dịu. Nhiếp ảnh phong cảnh chân thực, góc rộng, màu sắc tự nhiên, tâm trạng tĩnh lặng và đầy kịch tính. Khung cảnh ngang rộng 16:9. Không có người, không có văn bản, không có logo.
TravelWorkflowPhotography+3
F@f
0
Video
Nhà Thờ Bên Bãi Biển Cát Đen Iceland Vào Giờ Vàng (Hoạt Họa Từ Ảnh Sang Video)

Hoạt họa bức ảnh nhà thờ bên bãi biển cát đen ở bước 2 làm khung hình đầu tiên thành một cảnh quay tĩnh lặng dài 6 giây: sóng vỗ từ bên phải tràn lên lớp cát đen, bọt nước tung tóe vào cột đá biển cao vút, mây trôi trong khi ánh sáng vàng rực rỡ vẫn giữ nguyên, cỏ khẽ lay động và máy quay đẩy nhẹ vào trong. Bước 3 trong quy trình Nhà Thờ Bên Bãi Biển Cát Đen Iceland.

Bắt đầu từ hình ảnh ở bước 2 "Nhà Thờ Trắng Mái Đỏ Phía Trên Bãi Biển Cát Đen Và Các Cột Đá Bazan Trên Biển Vào Giờ Vàng" làm khung hình đầu tiên chính xác, và hoạt họa nó thành một cảnh quay điện ảnh tĩnh lặng dài 6 giây. Giữ nguyên ngôi nhà thờ trắng với mái đỏ và tháp chuông trên mũi đất phủ cỏ ở một phần ba bên trái, đường cong của bãi cát đen, bốn tảng đá bazan và các cột đá biển ở bên phải, mặt trời thấp ở phía ngoài cùng bên trái, đường chân trời và bố cục khung hình y hệt như trong ảnh. Những dải sóng trắng dài cuộn vào từ bên phải hướng về phía bãi biển, tràn lên lớp cát đen thành những lớp bọt mỏng rồi trượt ngược xuống, để lại vẻ bóng ướt lấp lánh. Khoảng giữa cảnh quay, một đợt bọt sóng trắng xóa đập tung tóe vào chân cột đá biển lởm chởm cao vút rồi rơi trở lại mặt nước. Những đám mây màu cam, hồng và tím trôi chậm rãi ngang bầu trời trong khi ánh sáng vàng ấm áp vẫn giữ nguyên ổn định, và lớp cỏ trên mũi đất khẽ lay động nhẹ nhàng trong gió. Máy quay gần như đứng yên, chỉ đẩy vào rất nhẹ và chậm rãi. Chuyển động tự nhiên, chân thực, không cắt cảnh, không có người, không có vật thể mới, không có văn bản. Âm thanh của tiếng sóng cuộn, tiếng gió và tiếng sóng vỗ từ xa.
TravelVideo GenerationImage To Video+2
F@f
0
Trình Rà Soát Bảo Mật Cấu Hình Nginx
Kỹ năng

Rà soát tệp nginx.conf và cấu hình các trang web để phát hiện các vấn đề bảo mật như TLS 1.0/1.1, bộ mã hóa (cipher) yếu, thiếu HSTS và các tiêu đề bảo mật (security headers), bẫy kế thừa add_header, lỗi duyệt đường dẫn (path traversal) do lệch dấu gạch chéo trong alias, autoindex, kích thước phần thân yêu cầu (body size) không giới hạn, biến $uri trong chuyển hướng, HTTP không chuyển hướng sang HTTPS, các đường dẫn quản trị bỏ ngỏ và trang đăng nhập không giới hạn tần suất (rate limit). Cung cấp bản cấu hình đã sửa lỗi. Bao gồm công cụ kiểm tra viết bằng thư viện chuẩn Python có hỗ trợ --self-test.

---
name: nginx-config-security-reviewer
description: Rà soát tệp nginx.conf và cấu hình các trang web để phát hiện các vấn đề bảo mật như TLS 1.0/1.1, bộ mã hóa (cipher) yếu, thiếu HSTS và các tiêu đề bảo mật (security headers), bẫy kế thừa add_header, lỗi duyệt đường dẫn (path traversal) do lệch dấu gạch chéo trong alias, autoindex, kích thước phần thân yêu cầu (body size) không giới hạn, biến $uri trong chuyển hướng, HTTP không chuyển hướng sang HTTPS, các đường dẫn quản trị bỏ ngỏ và trang đăng nhập không giới hạn tần suất (rate limit). Cung cấp bản cấu hình đã sửa lỗi. Bao gồm công cụ kiểm tra viết bằng thư viện chuẩn Python có hỗ trợ --self-test.
---

# Trình Rà Soát Bảo Mật Cấu Hình Nginx

Bạn rà soát các cấu hình nginx theo đúng cách mà một kỹ sư vận hành (ops engineer) cẩn trọng sẽ làm trước một đợt kiểm toán bảo mật: tìm ra các thiết lập làm lộ dữ liệu hoặc làm suy yếu TLS, giải thích từng vấn đề bằng ngôn từ dễ hiểu, và bàn giao lại một bản cấu hình đã được sửa lỗi nhưng vẫn phục vụ nguyên vẹn các tuyến (routes) như cũ. Bạn tuyệt đối không phỏng đoán về những tệp mà mình chưa được xem, và không bao giờ bảo người dùng nạp lại (reload) cấu hình mà không chạy `nginx -t` trước.

## Các tệp trong kỹ năng này

- `scripts/nginx_review.py` - phân tích cú pháp các tệp cấu hình nginx thành từng khối (blocks) và in ra các phát hiện kèm theo mức độ nghiêm trọng, số dòng và cách khắc phục (chỉ dùng thư viện chuẩn Python 3)
- `references/hardening-checklist.md` - mọi mục kiểm tra mà kịch bản thực thi, lý do tại sao nó quan trọng và cách khắc phục, cộng thêm các mục cần kiểm tra thủ công
- `references/tls-and-headers.md` - thiết lập giao thức TLS và bộ mã hóa (ciphers), lộ trình triển khai HSTS, các tiêu đề bảo mật và cơ chế hoạt động của tính kế thừa `add_header`
- `templates/review-report.md` - bản báo cáo bạn bàn giao lại: tóm tắt, bảng phát hiện lỗi, phần so sánh khác biệt (diff) của cấu hình, các câu lệnh kiểm tra
- `examples/shop-site.conf` - một cấu hình cửa hàng nhỏ từ năm 2019 với 18 phát hiện lỗi (3 lỗi mức HIGH)
- `examples/shop-site-fixed.conf` - cùng trang web đó sau khi đã áp dụng các bản sửa lỗi (không còn phát hiện lỗi nào ngoại trừ ghi chú về lệnh include)

## Quy trình làm việc

### 1. Thu thập toàn bộ cấu hình
Yêu cầu kết quả đầu ra của lệnh `sudo nginx -T` (lệnh này in ra tệp cấu hình chính và mọi tệp được đính kèm qua lệnh include), hoặc yêu cầu tệp nginx.conf cộng với các tệp liên quan trong thư mục sites-enabled. Hỏi xem trang web đó làm gì (phục vụ tệp tĩnh, reverse proxy, PHP), những đường dẫn nào bắt buộc phải giữ công khai, phiên bản nginx (`nginx -v`) và liệu có CDN hay bộ cân bằng tải (load balancer) nào đứng trước xử lý chấm dứt TLS (terminate TLS) hay không. Nhắc người dùng xóa các khóa bí mật (private keys) hoặc thông tin nhạy cảm (secrets) trước khi dán nội dung vào; giữ nguyên đường dẫn chứng chỉ thì không sao.

### 2. Chạy công cụ kiểm tra
```
python3 scripts/nginx_review.py examples/shop-site.conf
python3 scripts/nginx_review.py /etc/nginx/nginx.conf /etc/nginx/sites-enabled/shop --json
python3 scripts/nginx_review.py --self-test
```
Mã thoát (exit code) 1 có nghĩa là có các phát hiện mức HIGH, 2 có nghĩa là không thể đọc hoặc phân tích cú pháp tệp (thông báo lỗi sẽ chỉ rõ dòng gặp sự cố).

### 3. Xác nhận và bổ sung các bước kiểm tra thủ công
Đọc từng phát hiện lỗi trong mối tương quan với bối cảnh của người dùng: việc có một CDN đứng trước sẽ làm thay đổi các phát hiện về TLS, một tuyến quản trị chỉ dùng nội bộ có thể đã được bảo vệ bởi tường lửa, một câu lệnh `return 404` có thể đã ẩn đường dẫn đó đi rồi. Sau đó, rà soát qua các mục kiểm tra thủ công trong `references/hardening-checklist.md` (các tệp được include, xác minh TLS ở phía upstream, các khối `if`, các bộ xử lý PHP, quyền truy cập tệp). Loại bỏ các cảnh báo dương tính giả (false positives) và giải thích rõ lý do.

### 4. Bàn giao kết quả
Điền vào `templates/review-report.md`: ưu tiên các mục mức HIGH lên đầu, đưa ra bản so sánh khác biệt (diff) tối giản của cấu hình, cùng các bước kiểm thử. Luôn giải thích quy tắc kế thừa của `add_header` bất cứ khi nào lỗi ADD_HEADER_INHERITANCE xuất hiện, bởi vì quy tắc này gây bất ngờ cho hầu hết mọi người. Khi mới bắt đầu áp dụng HSTS cho một trang web chưa từng dùng nó trước đây, hãy đề xuất mức `max-age` ngắn trước.

## Kết quả ví dụ
Đối với `examples/shop-site.conf`: 3 lỗi mức HIGH (bật TLS 1.0/1.1, dùng các bộ mã hóa RC4 và 3DES, khối `location /images` dùng `alias .../images/` cho phép tấn công duyệt đường dẫn qua `/images../`), 6 lỗi mức MEDIUM (kích thước phần thân yêu cầu không giới hạn, cổng 80 trực tiếp trả về nội dung thay vì chuyển hướng, bật autoindex trên `/downloads/`, đường dẫn `/api/` làm mất HSTS và nosniff do tự đặt `add_header` riêng, đường dẫn `/admin/` không được bảo vệ, dùng biến `$uri` trong lệnh chuyển hướng), 8 lỗi mức LOW (server_tokens, các tệp ẩn bắt đầu bằng dấu chấm - dotfiles, thiếu CSP/Referrer-Policy/X-Frame-Options, các tiêu đề proxy, không giới hạn tần suất truy cập trên `/api/auth/login`) và 1 mục mức INFO. Tệp `examples/shop-site-fixed.conf` thể hiện các bản sửa lỗi và vượt qua bước kiểm tra `nginx -t`.

## Quy tắc

- Trích dẫn chính xác dòng bị lỗi và nội dung thay thế chính xác; giữ cho các thay đổi ở mức tối thiểu và bảo toàn nguyên vẹn chức năng định tuyến.
- Luôn kết thúc bằng câu lệnh `sudo nginx -t && sudo systemctl reload nginx` cùng một lệnh kiểm tra bằng curl; tuyệt đối không đề xuất khởi động lại (restart) làm đứt kết nối khi chỉ cần nạp lại (reload) là đủ.
- Không tự bịa ra các chỉ thị (directives): hãy đối chiếu tên chỉ thị với tài liệu chính thức của nginx tương ứng với phiên bản của người dùng (ví dụ: `http2 on;` yêu cầu phiên bản 1.25.1 trở lên, các phiên bản cũ hơn sử dụng `listen 443 ssl http2;`).
- Tuyệt đối không yêu cầu hoặc lặp lại các khóa bí mật (private keys), mật khẩu hoặc mã băm htpasswd.
- Hãy coi việc áp dụng các tiêu đề bảo mật như CSP là một lộ trình triển khai từng bước: đề xuất dùng `Content-Security-Policy-Report-Only` trước đối với các ứng dụng phức tạp.
FILE:references/hardening-checklist.md
# Danh Sách Kiểm Tra Gia Cố Bảo Mật Nginx

Mọi mã lỗi mà kịch bản in ra, lý do tại sao nó quan trọng và cách khắc phục. Mức độ nghiêm trọng dưới đây là mức mặc định; hãy nâng lên hoặc hạ xuống tùy theo bối cảnh thực tế.

## HIGH (Cao)

### OLD_TLS_PROTOCOL
Chỉ thị `ssl_protocols` có liệt kê SSLv3, TLSv1 hoặc TLSv1.1. Các giao thức này đã bị loại bỏ (RFC 8996), không đạt chuẩn PCI DSS và cho phép thực hiện các cuộc tấn công hạ cấp (downgrade attacks). Cách khắc phục: đặt `ssl_protocols TLSv1.2 TLSv1.3;` (đây là giá trị mặc định kể từ phiên bản nginx 1.23.4, vì vậy việc xóa dòng này đi cũng có tác dụng tương tự trên các phiên bản mới). Chỉ những trình khách (clients) quá cũ (như Android 4.3, IE trên Windows 7 chưa cập nhật) mới bị mất quyền truy cập.

### WEAK_CIPHERS
Chỉ thị `ssl_ciphers` cho phép các bộ mã hóa RC4, DES/3DES (lỗ hổng SWEET32), MD5, NULL, EXPORT hoặc ẩn danh (aNULL/ADH). Các mục bắt đầu bằng dấu `!` hoặc `-` là các quy tắc loại trừ nên hoàn toàn hợp lệ. Cách khắc phục: sử dụng danh sách "intermediate" (trung cấp) của Mozilla từ trang ssl-config.mozilla.org hoặc xóa bỏ dòng `ssl_ciphers` để sử dụng cấu hình mặc định của OpenSSL, đồng thời đặt `ssl_prefer_server_ciphers off;` khi chỉ còn lại các bộ mã hóa AEAD hiện đại.

### NO_CERTIFICATE
Một khối server lắng nghe với tham số `ssl` nhưng lại không thiết lập `ssl_certificate` bên trong nó hoặc trong khối http. Khi đó nginx sẽ từ chối khởi động hoặc làm thất bại quá trình bắt tay (handshake). Hãy thêm `ssl_certificate` (chuỗi chứng chỉ đầy đủ - full chain) và `ssl_certificate_key`.

### ALIAS_TRAVERSAL (lệch dấu gạch chéo - off-by-slash)
`location /images { alias /var/www/media/images/; }`: một yêu cầu truy cập tới `/images../secret.txt` sẽ được ánh xạ thành `/var/www/media/images/../secret.txt`, khiến cho các tệp nằm ở thư mục cha ngay phía trên đường dẫn alias có thể bị đọc trái phép (một phát hiện rất phổ biến trong nhiều cuộc kiểm toán bảo mật). Cách khắc phục: đảm bảo cả đường dẫn trong location và đường dẫn trong alias đều kết thúc bằng dấu gạch chéo, tức là `location /images/ { alias /var/www/media/images/; }`, hoặc sử dụng chỉ thị `root` khi đường dẫn URL trùng khớp với tên thư mục trên đĩa.

## MEDIUM (Trung bình)

### HTTP_NO_REDIRECT
Một khối server ở cổng 80 có cùng `server_name` với một khối server HTTPS nhưng lại trực tiếp phục vụ các trang web thay vì chuyển hướng người dùng. Khi đó người dùng và dữ liệu cookie vẫn nằm trên kết nối HTTP thuần không mã hóa. Cách khắc phục:
```
server {
    listen 80;
    server_name shop.example.com;
    location /.well-known/acme-challenge/ { root /var/www/certbot; }
    location / { return 301 https://$host$request_uri; }
}
```

### NO_HSTS
Không có tiêu đề `Strict-Transport-Security` trên máy chủ HTTPS, dẫn đến việc yêu cầu đầu tiên của một phiên truy cập có thể bị tấn công hạ cấp xuống HTTP. Xem tệp `tls-and-headers.md` để biết lộ trình triển khai an toàn.

### ADD_HEADER_INHERITANCE
nginx chỉ kế thừa các chỉ thị `add_header` từ cấp cha khi cấp hiện tại hoàn toàn không có bất kỳ chỉ thị `add_header` nào. Chỉ cần một dòng `add_header Cache-Control no-store;` bên trong khối `location /api/` cũng sẽ làm mất sạch mọi tiêu đề bảo mật được khai báo ở khối `server` đối với location đó. Cách khắc phục: lặp lại đầy đủ các tiêu đề bảo mật bên trong location đó, hoặc tách chúng ra một tệp đoạn mã riêng (`include snippets/security-headers.conf;`) và include tệp này vào mọi khối có sử dụng `add_header` riêng. Trên phiên bản nginx 1.29.3 trở lên, việc đặt `add_header_inherit merge;` trong khối http sẽ giúp các khối con cộng gộp thêm vào các tiêu đề của khối cha thay vì ghi đè toàn bộ. Lưu ý rằng `proxy_set_header`, `fastcgi_param` và `proxy_hide_header` cũng tuân theo đúng quy tắc kế thừa này.

### AUTOINDEX_ON
Tính năng liệt kê thư mục làm hiển thị tên của mọi tệp tin, bao gồm cả các bản sao lưu (backups) và các tệp kết xuất dữ liệu (exports) mà không ai chèn liên kết tới. Hãy tắt tính năng này đi hoặc thiết lập cơ chế bảo vệ cho location đó.

### ADMIN_OPEN
Các đường dẫn như /admin, /phpmyadmin, /actuator, /server-status, /nginx_status hoặc /metrics không được thiết lập quy tắc allow/deny, auth_basic hoặc auth_request. Cách khắc phục: thêm `allow 10.20.0.0/16; deny all;` (dải mạng văn phòng hoặc VPN), hoặc dùng `auth_request` trỏ tới dịch vụ đăng nhập một lần (SSO). Chỉ sử dụng xác thực cơ bản (Basic auth) qua kết nối HTTPS.

### UNLIMITED_BODY
Thiết lập `client_max_body_size 0` sẽ vô hiệu hóa giới hạn kích thước yêu cầu gửi lên, khiến kẻ tấn công dễ dàng làm cạn kiệt dung lượng đĩa và bộ nhớ. Hãy giữ mức mặc định 1m hoặc đặt một mức giới hạn thực tế trong khối http, và chỉ nâng giới hạn này lên ở riêng location dùng để tải tệp lên (upload).

### URI_IN_REDIRECT
Biến `$uri` đã được chuẩn hóa và giải mã URL (URL-decoded), vì vậy chuỗi `%0d%0a` sẽ biến thành ký tự xuống dòng thực sự và kẻ tấn công có thể chèn các tiêu đề (`Set-Cookie`) vào phản hồi chuyển hướng (HTTP response splitting / header injection). Hãy sử dụng biến `$request_uri` (dòng yêu cầu thô, vẫn còn nguyên mã hóa URL) trong các lệnh `return`, đích đến của `rewrite` và `add_header`.

## LOW (Thấp)

### SERVER_TOKENS
Số phiên bản xuất hiện trong tiêu đề `Server` và trên các trang báo lỗi, giúp kẻ tấn công dễ dàng đối chiếu với các lỗ hổng CVE đã biết. Hãy đặt `server_tokens off;` trong khối http.

### MISSING_SECURITY_HEADERS
Trên các máy chủ HTTPS: thiếu `X-Content-Type-Options nosniff`, `X-Frame-Options` (hoặc CSP `frame-ancestors`), `Content-Security-Policy`, `Referrer-Policy`. Hãy thêm chúng kèm theo tham số `always` để các tiêu đề này cũng được gửi đi trong cả các phản hồi lỗi 4xx/5xx.

### SHORT_HSTS
Giá trị `max-age` của HSTS ngắn hơn 180 ngày. Mức này hoàn toàn ổn trong giai đoạn mới triển khai thử nghiệm; hãy nâng nó lên mức một năm sau khi đã chạy ổn định.

### DOTFILES_EXPOSED
Máy chủ phục vụ các tệp tĩnh nhưng lại không có quy tắc chặn các tệp ẩn bắt đầu bằng dấu chấm, dẫn đến việc các tệp như `/.git/config`, `/.env` hoặc `/.htpasswd` có thể bị tải xuống nếu chúng tồn tại bên dưới thư mục gốc của web (web root). Cách khắc phục: thêm `location ~ /\.(?!well-known) { deny all; }` (vẫn giữ cho các yêu cầu xác thực chứng chỉ ACME hoạt động bình thường).

### NO_RATE_LIMIT
Một đường dẫn đăng nhập, xác thực hoặc cấp phát token không được áp dụng `limit_req`. Hãy khai báo một vùng nhớ (zone) trong khối http và áp dụng nó vào location tương ứng; trả về mã lỗi 429 bằng chỉ thị `limit_req_status 429;`.

### PROXY_HEADERS
Một khối location dùng `proxy_pass` nhưng không gửi kèm các tiêu đề Host, X-Forwarded-For và X-Forwarded-Proto, khiến ứng dụng phía sau ghi nhật ký nhầm địa chỉ IP của máy chủ proxy, tạo sai các URL chuyển hướng và có thể hiểu nhầm các yêu cầu HTTPS là HTTP. Hãy nhớ quy tắc kế thừa: chỉ cần một dòng `proxy_set_header` trong khối location cũng sẽ thay thế toàn bộ các dòng `proxy_set_header` được kế thừa từ cấp trên.

## INFO (Thông tin)

### INCLUDE_NOT_FOLLOWED
Kịch bản chỉ đọc những tệp mà bạn truyền trực tiếp vào. Hãy yêu cầu kết quả của lệnh `nginx -T` hoặc các tệp được đính kèm qua lệnh include.

## Kiểm tra thủ công (không tự động hóa)
- `proxy_pass https://...` trỏ tới một máy chủ upstream ngoài internet mà không bật `proxy_ssl_verify on;` và `proxy_ssl_trusted_certificate`.
- PHP: khối `location ~ \.php$` bắt buộc phải sử dụng `try_files $uri =404;` (hoặc `fastcgi_split_path_info` kèm theo bước kiểm tra) để các tệp hình ảnh do người dùng tải lên không thể bị thực thi như mã PHP; các thư mục chứa tệp tải lên tuyệt đối không bao giờ được chuyển tiếp sang bộ xử lý PHP.
- Dùng câu lệnh `if` bên trong khối location với bất kỳ chỉ thị nào khác ngoài `return` hoặc `rewrite ... last` (nguyên tắc nổi tiếng "if is evil").
- Các khối `default_server`: một yêu cầu có tiêu đề Host lạ không xác định nên nhận về `return 444;` kèm chứng chỉ tự ký hoặc bật `ssl_reject_handshake on;` (từ bản 1.19.4 trở lên) thay vì rơi vào trang web đầu tiên trong cấu hình.
- Quyền truy cập tệp (Permissions): các tiến trình worker của nginx không được phép có quyền ghi vào thư mục gốc của web (web root); các khóa bí mật (private keys) phải đặt `chmod 600` và thuộc quyền sở hữu của root.
- Tiêu đề kích thước lớn / các trình khách kết nối chậm (slow clients): cấu hình `client_body_timeout`, `client_header_timeout`, `large_client_header_buffers` cho các API công khai đang chịu tấn công.
- Ghi nhật ký (Logging): nhật ký truy cập (access logs) không được ghi lại các token nằm trong chuỗi truy vấn (query strings); hãy sử dụng một định dạng `log_format` tùy chỉnh nếu cần.
FILE:references/tls-and-headers.md
# TLS và Các Tiêu Đề Bảo Mật

## Thiết lập TLS vượt qua hầu hết các đợt kiểm toán (nginx 1.25+ với OpenSSL 1.1.1 hoặc 3.x)
```
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;
ssl_prefer_server_ciphers off;
ssl_session_timeout 1d;
ssl_session_cache shared:SSL:10m;
ssl_session_tickets off;
```
Đây là cấu hình thuộc hồ sơ "intermediate" (trung cấp) của Mozilla. Các bộ mã hóa của TLS 1.3 không được đặt thông qua chỉ thị `ssl_ciphers` (OpenSSL tự xử lý chúng). Nếu giữ lại các bộ mã hóa DHE, hãy cung cấp thêm `ssl_dhparam` với độ dài tối thiểu 2048 bit hoặc loại bỏ các mục DHE khỏi danh sách.

Tính năng OCSP stapling (`ssl_stapling on; ssl_stapling_verify on;`) là tùy chọn; Let's Encrypt đã ngừng hỗ trợ OCSP vào năm 2025, vì vậy đừng thêm cấu hình này đối với các chứng chỉ của Let's Encrypt.

HTTP/2: dùng `http2 on;` bên trong khối server trên nginx 1.25.1 trở lên; các phiên bản cũ hơn sử dụng `listen 443 ssl http2;`.

## Lộ trình triển khai HSTS
1. Đảm bảo mọi tên miền phụ (subdomain) đều hoạt động tốt qua giao thức HTTPS.
2. Bắt đầu với thời hạn ngắn: `add_header Strict-Transport-Security "max-age=300" always;`
3. Sau một tuần chạy ổn định không phát sinh sự cố: nâng lên `max-age=31536000; includeSubDomains`.
4. Chỉ thêm tham số `preload` và gửi đăng ký lên trang hstspreload.org khi bạn đã hoàn toàn chắc chắn; việc gỡ bỏ khỏi danh sách preload sẽ mất đến hàng tháng trời.
Tiêu đề HSTS gửi trên các phản hồi HTTP thuần sẽ bị trình duyệt bỏ qua, vì vậy hãy đặt nó trên khối server cổng 443.

## Các tiêu đề bảo mật (Security headers)
| Tiêu đề | Giá trị điển hình | Ghi chú |
|---|---|---|
| X-Content-Type-Options | `nosniff` | Ngăn chặn hành vi tự động đoán kiểu MIME đối với các tệp tải lên |
| X-Frame-Options | `DENY` hoặc `SAMEORIGIN` | Chống tấn công Clickjacking; chỉ thị `frame-ancestors` của CSP là giải pháp tương đương hiện đại hơn |
| Content-Security-Policy | `default-src 'self'` và các quy tắc khác | Đặc thù theo từng ứng dụng; hãy triển khai thử nghiệm trước bằng `Content-Security-Policy-Report-Only` |
| Referrer-Policy | `strict-origin-when-cross-origin` | Giữ cho các đường dẫn và chuỗi truy vấn không bị lọt vào nhật ký của các trang web khác |
| Permissions-Policy | `camera=(), microphone=(), geolocation=()` | Tùy chọn |
Hãy thêm tham số `always` để các tiêu đề này luôn được gửi kèm trong cả những phản hồi báo lỗi.

## Quy tắc kế thừa của add_header
```
server {
    add_header X-Content-Type-Options nosniff always;   # A
    location / { }                                      # nhận được A
    location /api/ {
        add_header Cache-Control no-store;              # CHỈ nhận được tiêu đề này, A bị mất
    }
}
```
Một khối chỉ kế thừa các chỉ thị `add_header` từ khối cha của nó nếu bản thân nó không tự định nghĩa bất kỳ chỉ thị `add_header` nào. Hãy gom các tiêu đề dùng chung vào một tệp snippet và include tệp đó vào mọi khối có khai báo thêm tiêu đề riêng. Phiên bản nginx 1.29.3 (tháng 10 năm 2025) đã bổ sung chỉ thị `add_header_inherit merge;`, giúp các khối lồng bên trong cộng gộp thêm vào danh sách tiêu đề được kế thừa; hãy kiểm tra phiên bản bằng `nginx -v` trước khi đề xuất dùng nó (Debian 13 đi kèm bản 1.26).

## Kiểm thử
```
sudo nginx -t && sudo systemctl reload nginx
curl -sI https://shop.example.com/ | grep -iE 'strict|x-content|x-frame|content-security|referrer|server:'
curl -sI http://shop.example.com/page?x=1        # kỳ vọng mã 301 chuyển tới https://shop.example.com/page?x=1
curl -s -o /dev/null -w '%{http_code}\n' https://shop.example.com/.git/config   # kỳ vọng mã 403 hoặc 404
curl -s -o /dev/null -w '%{http_code}\n' https://shop.example.com/images../      # kỳ vọng mã 404 sau khi sửa lỗi alias
openssl s_client -connect shop.example.com:443 -tls1_1 </dev/null   # kỳ vọng quá trình bắt tay thất bại
```
Để kiểm tra toàn diện từ bên ngoài: sử dụng công cụ kiểm tra máy chủ của SSL Labs hoặc chạy `testssl.sh shop.example.com`.
FILE:templates/review-report.md
# Báo cáo rà soát bảo mật Nginx: <tên trang web hoặc máy chủ>

**Phiên bản:** <nginx 1.26.3, OpenSSL 3.x>   **Vai trò:** <trang web tĩnh / reverse proxy trỏ tới Node trên cổng :3000 / PHP-FPM>
**Các tệp đã rà soát:** <đầu ra lệnh nginx -T, 2 tệp>   **TLS được chấm dứt bởi:** <nginx / CDN / bộ cân bằng tải>
**Tóm tắt:** <một câu: vấn đề nghiêm trọng nhất và bản sửa lỗi sẽ thay đổi điều gì>

## Các phát hiện lỗi (Findings)
| # | Mức độ | Mã lỗi | Tệp:dòng | Vấn đề | Cách khắc phục |
|---|---|---|---|---|---|
| 1 | HIGH | <ALIAS_TRAVERSAL> | <nginx.conf:51> | <location /images cho phép truy cập /images../> | <location /images/ { alias .../images/; }> |
| 2 | <MEDIUM> | <...> | <...> | <...> | <...> |

## Các mục giữ nguyên không thay đổi (kèm lý do)
- <ADMIN_OPEN trên /admin/: đã được giới hạn bởi tường lửa đám mây chỉ cho phép mạng VPN; có thêm deny all nữa hay không? có/không>

## Các thay đổi trong cấu hình
```diff
- ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
+ ssl_protocols TLSv1.2 TLSv1.3;
```

## Áp dụng và kiểm thử
1. Sao lưu: `sudo cp -a /etc/nginx /etc/nginx.bak-<ngày>`
2. Chỉnh sửa tệp, sau đó chạy `sudo nginx -t && sudo systemctl reload nginx`
3. Các bước kiểm tra:
   - `curl -sI https://<host>/` hiển thị đầy đủ các tiêu đề bảo mật
   - `curl -sI http://<host>/x?y=1` trả về mã 301 chuyển tới cùng đường dẫn đó trên giao thức https
   - <các lệnh kiểm tra duyệt đường dẫn / tệp ẩn bắt đầu bằng dấu chấm / trang quản trị tương ứng với các phát hiện lỗi>
4. Khôi phục (Roll back): phục hồi lại bản sao lưu và nạp lại (reload) cấu hình.

## Các việc cần theo dõi tiếp (Follow-ups)
- <HSTS: nâng giá trị max-age lên 31536000 sau ngày <ngày>>
- <CSP: chuyển từ chế độ Report-Only sang chế độ bắt buộc thực thi (enforcing) sau khi đã rà soát các báo cáo>
FILE:examples/shop-site.conf
# /etc/nginx/nginx.conf for shop.example.com (single-file setup)
# A small online shop: static storefront, product images, a Node.js API on :3000
# and a legacy admin panel. Written in 2019, certificates renewed by certbot.

user www-data;
worker_processes auto;

events {
    worker_connections 1024;
}

http {
    include /etc/nginx/mime.types;
    sendfile on;
    keepalive_timeout 65;
    client_max_body_size 0;

    ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
    ssl_ciphers "HIGH:RC4-SHA:DES-CBC3-SHA:!aNULL";
    ssl_prefer_server_ciphers on;

    server {
        listen 80;
        server_name shop.example.com www.shop.example.com;
        root /var/www/shop;

        location /.well-known/acme-challenge/ {
            root /var/www/certbot;
        }
    }

    server {
        listen 443 ssl;
        http2 on;
        server_name shop.example.com www.shop.example.com;

        ssl_certificate /etc/letsencrypt/live/shop.example.com/fullchain.pem;
        ssl_certificate_key /etc/letsencrypt/live/shop.example.com/privkey.pem;

        add_header Strict-Transport-Security "max-age=31536000" always;
        add_header X-Content-Type-Options nosniff always;

        root /var/www/shop;
        index index.html;

        location / {
            try_files $uri $uri/ /index.html;
        }

        location /images {
            alias /var/www/shop-media/images/;
        }

        location /downloads/ {
            alias /var/www/shop-media/downloads/;
            autoindex on;
        }

        location /api/ {
            add_header Cache-Control "no-store";
            proxy_pass http://127.0.0.1:3000;
        }

        location /api/auth/login {
            proxy_pass http://127.0.0.1:3000;
            proxy_set_header Host $host;
        }

        location /admin/ {
            proxy_pass http://127.0.0.1:8080/;
        }

        location /old-blog {
            return 301 https://blog.example.com$uri;
        }
    }
}
FILE:examples/shop-site-fixed.conf
# shop.example.com after the review: same routes, hardened.
# Shared security headers live in one snippet so locations with their own
# add_header can include it again (add_header is not inherited into a block
# that sets any add_header of its own).

user www-data;
worker_processes auto;

events {
    worker_connections 1024;
}

http {
    include /etc/nginx/mime.types;
    sendfile on;
    keepalive_timeout 65;
    server_tokens off;
    client_max_body_size 10m;

    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_prefer_server_ciphers off;
    ssl_session_timeout 1d;
    ssl_session_cache shared:SSL:10m;

    limit_req_zone $binary_remote_addr zone=login:10m rate=5r/m;

    server {
        listen 80;
        server_name shop.example.com www.shop.example.com;

        location /.well-known/acme-challenge/ {
            root /var/www/certbot;
        }

        location / {
            return 301 https://$host$request_uri;
        }
    }

    server {
        listen 443 ssl;
        http2 on;
        server_name shop.example.com www.shop.example.com;

        ssl_certificate /etc/letsencrypt/live/shop.example.com/fullchain.pem;
        ssl_certificate_key /etc/letsencrypt/live/shop.example.com/privkey.pem;

        # same lines as /etc/nginx/snippets/security-headers.conf
        add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
        add_header X-Content-Type-Options nosniff always;
        add_header X-Frame-Options SAMEORIGIN always;
        add_header Content-Security-Policy "default-src 'self'; img-src 'self' data:; frame-ancestors 'self'" always;
        add_header Referrer-Policy strict-origin-when-cross-origin always;

        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;

        root /var/www/shop;
        index index.html;

        location ~ /\.(?!well-known) {
            deny all;
        }

        location / {
            try_files $uri $uri/ /index.html;
        }

        location /images/ {
            alias /var/www/shop-media/images/;
        }

        location /downloads/ {
            alias /var/www/shop-media/downloads/;
        }

        location /api/ {
            add_header Cache-Control "no-store" always;
            add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
            add_header X-Content-Type-Options nosniff always;
            add_header X-Frame-Options SAMEORIGIN always;
            add_header Content-Security-Policy "default-src 'self'; img-src 'self' data:; frame-ancestors 'self'" always;
            add_header Referrer-Policy strict-origin-when-cross-origin always;
            proxy_pass http://127.0.0.1:3000;
        }

        location /api/auth/login {
            limit_req zone=login burst=5 nodelay;
            proxy_pass http://127.0.0.1:3000;
        }

        location /admin/ {
            allow 10.20.0.0/16;
            deny all;
            proxy_pass http://127.0.0.1:8080/;
        }

        location /old-blog {
            return 301 https://blog.example.com$request_uri;
        }
    }
}
FILE:scripts/nginx_review.py
#!/usr/bin/env python3
"""Nginx config security reviewer (Python 3 standard library only).

Parses nginx.conf / sites-available files into a block tree and flags common
security and hardening problems: old TLS protocols, weak ciphers, missing HSTS
and security headers, the add_header inheritance trap, alias off-by-slash path
traversal, autoindex, unlimited body size, $uri in redirects (CRLF injection),
plain-HTTP servers without a redirect, open admin paths, login paths without
rate limiting, exposed dotfiles and proxies without forwarding headers.

Usage:
  python3 nginx_review.py site.conf [more.conf ...] [--json]
  python3 nginx_review.py --self-test
Exit codes: 0 = no HIGH findings, 1 = HIGH findings, 2 = unreadable/unparseable input.
"""
import argparse, json, re, sys

SEV_ORDER = {"HIGH": 0, "MEDIUM": 1, "LOW": 2, "INFO": 3}
OLD_PROTOCOLS = {"SSLv2", "SSLv3", "TLSv1", "TLSv1.1"}
WEAK_CIPHER_WORDS = ["RC4", "DES", "3DES", "MD5", "NULL", "EXPORT", "EXP-", "aNULL", "eNULL", "ADH", "AECDH"]
SECURITY_HEADERS = {
    "x-content-type-options": "X-Content-Type-Options nosniff",
    "x-frame-options": "X-Frame-Options DENY or SAMEORIGIN (or CSP frame-ancestors)",
    "content-security-policy": "Content-Security-Policy",
    "referrer-policy": "Referrer-Policy strict-origin-when-cross-origin",
}
LOGIN_RE = re.compile(r"login|signin|sign-in|auth|token|password|wp-login", re.I)
ADMIN_RE = re.compile(r"admin|phpmyadmin|actuator|server-status|nginx_status|/metrics|wp-admin", re.I)


class ParseError(Exception):
    pass


class Node:
    def __init__(self, name, args, line, children=None):
        self.name, self.args, self.line, self.children = name, args, line, children

    def get(self, name):
        return [c for c in (self.children or []) if c.name == name]

    def first(self, name):
        found = self.get(name)
        return found[0] if found else None

    def walk(self):
        for c in self.children or []:
            yield c
            if c.children is not None:
                yield from c.walk()


def tokenize(text):
    tokens, i, line, n = [], 0, 1, len(text)
    while i < n:
        ch = text[i]
        if ch == "\n":
            line += 1; i += 1
        elif ch.isspace():
            i += 1
        elif ch == "#":
            while i < n and text[i] != "\n":
                i += 1
        elif ch in "{};":
            tokens.append((ch, line, True)); i += 1
        elif ch in "\"'":
            q, j, buf = ch, i + 1, []
            while j < n and text[j] != q:
                if text[j] == "\\" and j + 1 < n:
                    buf.append(text[j + 1]); j += 2; continue
                if text[j] == "\n":
                    line += 1
                buf.append(text[j]); j += 1
            if j >= n:
                raise ParseError(f"line {line}: unterminated quote")
            tokens.append(("".join(buf), line, False)); i = j + 1
        else:
            j = i
            while j < n and not text[j].isspace() and text[j] not in "{};":
                if text[j] == "$" and j + 1 < n and text[j + 1] == "{":  # var
                    k = text.find("}", j)
                    j = k + 1 if k != -1 else n
                    continue
                j += 1
            tokens.append((text[i:j], line, False)); i = j
    return tokens


def parse(text):
    tokens = tokenize(text)
    root = Node("main", [], 0, [])
    stack, cur = [root], []
    for tok, line, special in tokens:
        if special and tok == ";":
            if not cur:
                continue
            stack[-1].children.append(Node(cur[0][0], [t for t, _ in cur[1:]], cur[0][1]))
            cur = []
        elif special and tok == "{":
            if not cur:
                raise ParseError(f"line {line}: '{{' without a directive name")
            node = Node(cur[0][0], [t for t, _ in cur[1:]], cur[0][1], [])
            stack[-1].children.append(node); stack.append(node); cur = []
        elif special and tok == "}":
            if cur:
                raise ParseError(f"line {line}: missing ';' before '}}'")
            if len(stack) == 1:
                raise ParseError(f"line {line}: unexpected '}}'")
            stack.pop()
        else:
            cur.append((tok, line))
    if cur:
        raise ParseError(f"line {cur[0][1]}: directive '{cur[0][0]}' not terminated with ';'")
    if len(stack) != 1:
        raise ParseError(f"block '{stack[-1].name}' opened on line {stack[-1].line} is not closed")
    return root


def headers_of(node):
    return {d.args[0].lower(): d for d in node.get("add_header") if d.args}


def is_tls(server):
    for l in server.get("listen"):
        if "ssl" in l.args or "quic" in l.args or any(a.endswith(":443") or a == "443" for a in l.args):
            return True
    s = server.first("ssl")
    return bool(s and s.args and s.args[0] == "on")


def listens_plain(server):
    ls = server.get("listen")
    if not ls:
        return True  # default listen *:80
    for l in ls:
        if not ("ssl" in l.args or "quic" in l.args or any(a.endswith(":443") or a == "443" for a in l.args)):
            return True
    return False


def names(server):
    out = []
    for d in server.get("server_name"):
        out.extend(d.args)
    return set(out) or {"_"}


def inherited(scopes, name):
    """Value of a directive as nginx inherits it: innermost scope that sets it."""
    for s in reversed(scopes):
        d = s.first(name)
        if d is not None:
            return d
    return None


def effective_headers(scopes):
    for s in reversed(scopes):
        h = headers_of(s)
        if h:
            return h
    return {}


def review(text, fname="nginx.conf"):
    root = parse(text)
    http = root.first("http")
    top = http if http is not None else root
    base = [root] + ([http] if http is not None else [])
    servers = [s for s in top.get("server")]
    findings = []

    def add(sev, code, line, msg, fix):
        findings.append({"severity": sev, "code": code, "file": fname, "line": line, "message": msg, "fix": fix})

    for d in root.walk():
        if d.name == "include":
            add("INFO", "INCLUDE_NOT_FOLLOWED", d.line, f"include {' '.join(d.args)} is not read by this checker",
                "Review the included files too (pass them as extra arguments) or paste `nginx -T` output.")

    tokens = [d for d in root.walk() if d.name == "server_tokens"]
    if not any(d.args and d.args[0] == "off" for d in tokens):
        add("LOW", "SERVER_TOKENS", (http or root).line, "server_tokens is not off, so error pages and the Server header show the nginx version",
            "Add `server_tokens off;` in the http block.")

    tls_names = set()
    for s in servers:
        if is_tls(s):
            tls_names |= names(s)

    for s in servers:
        scopes = base + [s]
        label = " ".join(sorted(names(s)))
        tls = is_tls(s)
        if tls:
            if inherited(scopes, "ssl_certificate") is None:
                add("HIGH", "NO_CERTIFICATE", s.line, f"TLS server {label} has no ssl_certificate in scope",
                    "Add ssl_certificate and ssl_certificate_key (fullchain + key).")
            proto = inherited(scopes, "ssl_protocols")
            if proto is not None:
                old = [p for p in proto.args if p in OLD_PROTOCOLS]
                if old:
                    add("HIGH", "OLD_TLS_PROTOCOL", proto.line, f"{label}: ssl_protocols enables {', '.join(old)}",
                        "Use `ssl_protocols TLSv1.2 TLSv1.3;` (SSLv3/TLS 1.0/1.1 are deprecated, RFC 8996).")
            ciph = inherited(scopes, "ssl_ciphers")
            if ciph is not None and ciph.args:
                parts = re.split(r"[:\s]+", " ".join(ciph.args))
                weak = sorted({w for p in parts if p and not p.startswith("!") and not p.startswith("-")
                               for w in WEAK_CIPHER_WORDS if w.lower() in p.lower()})
                if weak:
                    add("HIGH", "WEAK_CIPHERS", ciph.line, f"{label}: ssl_ciphers allows weak suites ({', '.join(weak)})",
                        "Use the Mozilla 'intermediate' cipher list, or remove ssl_ciphers to use the safe OpenSSL default.")
            hdrs = effective_headers(scopes)
            if "strict-transport-security" not in hdrs:
                add("MEDIUM", "NO_HSTS", s.line, f"TLS server {label} sends no Strict-Transport-Security header",
                    'Add `add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;` (start with a short max-age).')
            else:
                hsts = hdrs["strict-transport-security"]
                m = re.search(r"max-age=(\d+)", " ".join(hsts.args), re.I)
                if m and int(m.group(1)) < 15552000:
                    add("LOW", "SHORT_HSTS", hsts.line, f"{label}: HSTS max-age {m.group(1)} is under 180 days",
                        "Raise max-age to 31536000 once HTTPS works everywhere.")
            missing = [v for k, v in SECURITY_HEADERS.items() if k not in hdrs]
            if "x-frame-options" not in hdrs and "content-security-policy" in hdrs and \
                    "frame-ancestors" in " ".join(hdrs["content-security-policy"].args):
                missing = [v for v in missing if not v.startswith("X-Frame")]
            if missing:
                add("LOW", "MISSING_SECURITY_HEADERS", s.line, f"{label}: missing {'; '.join(missing)}",
                    "Add them with `always` in the server block (check the inheritance rule for locations).")
        elif listens_plain(s) and names(s) & tls_names:
            redirects = any(r.args and r.args[0] in ("301", "302", "307", "308") and len(r.args) > 1 and r.args[1].startswith("https://")
                            for r in [d for d in s.walk() if d.name == "return"])
            rewrites = any(len(r.args) > 1 and r.args[1].startswith("https://") for r in [d for d in s.walk() if d.name == "rewrite"])
            if not (redirects or rewrites):
                add("MEDIUM", "HTTP_NO_REDIRECT", s.line, f"plain-HTTP server {label} serves content instead of redirecting to HTTPS",
                    "Use `return 301 https://$host$request_uri;` in the port-80 server (keep only /.well-known/acme-challenge/ if needed).")

        server_hdrs = effective_headers(scopes)
        has_dot_rule = False
        locations = [d for d in s.walk() if d.name == "location"]
        for loc in locations:
            path = loc.args[-1] if loc.args else ""
            mod = loc.args[0] if len(loc.args) > 1 else ""
            if mod in ("~", "~*") and (r"/\." in path or path.startswith(r"\.") or r"(^|/)\." in path):
                if loc.get("deny") or any(r.args and r.args[0] in ("403", "404") for r in loc.get("return")):
                    has_dot_rule = True
            own = headers_of(loc)
            hi = inherited(scopes + [loc], "add_header_inherit")
            merge = bool(hi and hi.args and hi.args[0] == "merge")  # nginx 1.29.3+
            if own and server_hdrs and not merge:
                lost = sorted(set(server_hdrs) - set(own))
                if lost:
                    add("MEDIUM", "ADD_HEADER_INHERITANCE", loc.line,
                        f"location {path} has its own add_header, so it silently drops {', '.join(lost)} set at server/http level",
                        "Repeat those headers in this location, include a shared headers snippet, or on nginx 1.29.3+ set `add_header_inherit merge;`.")
            al = loc.first("alias")
            if al and al.args and mod in ("", "^~") and not path.endswith("/") and al.args[0].endswith("/"):
                add("HIGH", "ALIAS_TRAVERSAL", al.line,
                    f"location {path} (no trailing slash) with alias {al.args[0]} (trailing slash) allows {path}../ path traversal",
                    f"Make both end with a slash: `location {path}/ {{ alias {al.args[0]}; }}`.")
            ai = loc.first("autoindex")
            if ai and ai.args and ai.args[0] == "on":
                add("MEDIUM", "AUTOINDEX_ON", ai.line, f"autoindex on in location {path} lists directory contents",
                    "Turn it off, or restrict the location with allow/deny or auth_basic.")
            if ADMIN_RE.search(path):
                guarded = loc.get("deny") or loc.get("auth_basic") or loc.get("auth_request") or loc.get("satisfy") \
                    or any(r.args and r.args[0] in ("403", "404", "444") for r in loc.get("return"))
                if not guarded:
                    add("MEDIUM", "ADMIN_OPEN", loc.line, f"location {path} looks like an admin/status endpoint with no allow/deny or auth",
                        "Add `allow <office/VPN range>; deny all;` or auth_basic/auth_request, or remove the route.")
            if LOGIN_RE.search(path) and not inherited(scopes + [loc], "limit_req"):
                add("LOW", "NO_RATE_LIMIT", loc.line, f"location {path} looks like a login/auth endpoint without limit_req",
                    "Define `limit_req_zone $binary_remote_addr zone=login:10m rate=5r/m;` in http and `limit_req zone=login burst=5 nodelay;` here.")
            if loc.first("proxy_pass"):
                ph = set()
                for sc in reversed(scopes + [loc]):  # like add_header: innermost level that sets any wins
                    if sc.get("proxy_set_header"):
                        ph = {d.args[0].lower() for d in sc.get("proxy_set_header") if d.args}
                        break
                miss = [h for h in ("host", "x-forwarded-for", "x-forwarded-proto") if h not in ph]
                if miss:
                    add("LOW", "PROXY_HEADERS", loc.line, f"location {path} proxies without {', '.join(miss)}",
                        "Set proxy_set_header Host $host; X-Forwarded-For $proxy_add_x_forwarded_for; X-Forwarded-Proto $scheme; (note: any proxy_set_header in a location replaces inherited ones).")
        serves_files = inherited(scopes, "root") or any(
            (l.first("root") or l.first("alias")) and not (l.args and l.args[-1].startswith("/.well-known/")) for l in locations)
        if serves_files and not has_dot_rule:
            add("LOW", "DOTFILES_EXPOSED", s.line, f"{label}: no rule blocks hidden files such as /.git/ or /.env",
                r"Add `location ~ /\.(?!well-known) { deny all; }`.")

    for d in root.walk():
        if d.name == "client_max_body_size" and d.args and d.args[0] in ("0", "0m", "0k"):
            add("MEDIUM", "UNLIMITED_BODY", d.line, "client_max_body_size 0 disables the request body limit",
                "Set a real limit (e.g. 10m, or larger only in the upload location).")
        if d.name in ("return", "rewrite", "add_header") and any(re.search(r"\$uri\b|\$document_uri\b", a) for a in d.args):
            add("MEDIUM", "URI_IN_REDIRECT", d.line, f"{d.name} uses $uri (decoded, may contain CR/LF), which allows header injection",
                "Use $request_uri (raw, still encoded) in redirects and headers.")
    findings.sort(key=lambda f: (SEV_ORDER[f["severity"]], f["line"]))
    return {"file": fname, "servers": [{"line": s.line, "names": sorted(names(s)), "tls": is_tls(s)} for s in servers],
            "findings": findings}


def to_markdown(results):
    out = ["# Nginx config security review", ""]
    allf = [f for r in results for f in r["findings"]]
    counts = {k: sum(1 for f in allf if f["severity"] == k) for k in SEV_ORDER}
    out.append("Findings: " + ", ".join(f"{v} {k}" for k, v in counts.items()))
    for r in results:
        out += ["", f"## {r['file']}", "", "| Server | Line | TLS |", "|---|---|---|"]
        for s in r["servers"]:
            out.append(f"| {' '.join(s['names'])} | {s['line']} | {'yes' if s['tls'] else 'no'} |")
        out += ["", "| Severity | Code | Line | Problem | Fix |", "|---|---|---|---|---|"]
        for f in r["findings"]:
            out.append(f"| {f['severity']} | {f['code']} | {f['line']} | {f['message']} | {f['fix']} |")
        if not r["findings"]:
            out.append("| - | - | - | no findings | - |")
    out += ["", "Always run `nginx -t` before reloading, and test with `curl -sI https://<host>/` afterwards."]
    return "\n".join(out)


def self_test():
    results = []

    def check(name, cond):
        results.append(cond)
        print(("PASS " if cond else "FAIL ") + name)

    def codes(conf):
        return [f["code"] for f in review(conf)["findings"]]

    good = """
    http {
      server_tokens off;
      limit_req_zone $binary_remote_addr zone=login:10m rate=5r/m;
      server { listen 80; server_name a.test; return 301 https://$host$request_uri; }
      server {
        listen 443 ssl; http2 on; server_name a.test;
        ssl_certificate /c.pem; ssl_certificate_key /k.pem;
        ssl_protocols TLSv1.2 TLSv1.3;
        add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
        add_header X-Content-Type-Options nosniff always;
        add_header Content-Security-Policy "default-src 'self'; frame-ancestors 'none'" always;
        add_header Referrer-Policy strict-origin-when-cross-origin always;
        root /srv/www;
        location ~ /\\.(?!well-known) { deny all; }
        location /static/ { alias /srv/static/; }
        location /login { limit_req zone=login burst=5 nodelay; proxy_pass http://app;
          proxy_set_header Host $host; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
          proxy_set_header X-Forwarded-Proto $scheme; }
      }
    }"""
    check("clean config has no findings", codes(good) == [])
    check("comments and quotes parsed", parse('a "b c;" d; # x;\n e f;').children[0].args == ["b c;", "d"])
    check("var token kept", parse("set $x hosta;").children[0].args == ["$x", "hosta"])
    for bad, msg in [("server { listen 80;", "unclosed block"), ("a b", "missing ;"), ("}", "stray brace"), ('a "b;', "unterminated quote")]:
        try:
            parse(bad); check("parse error: " + msg, False)
        except ParseError:
            check("parse error: " + msg, True)
    check("SERVER_TOKENS when missing", "SERVER_TOKENS" in codes("http { server { listen 80; } }"))
    tls = "server { listen 443 ssl; server_name b.test; ssl_certificate /c; %s }"
    check("OLD_TLS_PROTOCOL", "OLD_TLS_PROTOCOL" in codes(tls % "ssl_protocols TLSv1 TLSv1.2;"))
    check("TLSv1.2 alone fine", "OLD_TLS_PROTOCOL" not in codes(tls % "ssl_protocols TLSv1.2;"))
    check("WEAK_CIPHERS", "WEAK_CIPHERS" in codes(tls % "ssl_ciphers 'HIGH:RC4-SHA:DES-CBC3-SHA';"))
    check("excluded !RC4 is fine", "WEAK_CIPHERS" not in codes(tls % "ssl_ciphers 'HIGH:!aNULL:!MD5:!RC4';"))
    check("NO_CERTIFICATE", "NO_CERTIFICATE" in codes("server { listen 443 ssl; }"))
    check("cert inherited from http", "NO_CERTIFICATE" not in codes("http { ssl_certificate /c; server { listen 443 ssl; } }"))
    check("NO_HSTS", "NO_HSTS" in codes(tls % ""))
    check("SHORT_HSTS", "SHORT_HSTS" in codes(tls % 'add_header Strict-Transport-Security "max-age=300";'))
    check("MISSING_SECURITY_HEADERS", "MISSING_SECURITY_HEADERS" in codes(tls % ""))
    inh = tls % 'add_header Strict-Transport-Security "max-age=31536000"; location /api/ { add_header Cache-Control no-store; }'
    r = review(inh)["findings"]
    check("ADD_HEADER_INHERITANCE names lost header",
          any(f["code"] == "ADD_HEADER_INHERITANCE" and "strict-transport-security" in f["message"] for f in r))
    check("headers inherited when location has none", "ADD_HEADER_INHERITANCE" not in codes(
        tls % 'add_header Strict-Transport-Security "max-age=31536000"; location /api/ { proxy_pass http://x; }'))
    check("add_header_inherit merge respected", "ADD_HEADER_INHERITANCE" not in codes(
        tls % 'add_header_inherit merge; add_header Strict-Transport-Security "max-age=31536000"; location /api/ { add_header Cache-Control no-store; }'))
    check("ALIAS_TRAVERSAL", "ALIAS_TRAVERSAL" in codes("server { location /img { alias /srv/img/; } }"))
    check("alias with matching slashes fine", "ALIAS_TRAVERSAL" not in codes("server { location /img/ { alias /srv/img/; } }"))
    check("regex location alias not flagged", "ALIAS_TRAVERSAL" not in codes(r"server { location ~ ^/img/(.*)$ { alias /srv/img/$1; } }"))
    check("AUTOINDEX_ON", "AUTOINDEX_ON" in codes("server { location /files/ { autoindex on; } }"))
    check("HTTP_NO_REDIRECT", "HTTP_NO_REDIRECT" in codes(
        "server { listen 80; server_name c.test; root /x; } server { listen 443 ssl; server_name c.test; ssl_certificate /c; }"))
    check("no HTTP_NO_REDIRECT for unrelated name", "HTTP_NO_REDIRECT" not in codes(
        "server { listen 80; server_name d.test; } server { listen 443 ssl; server_name c.test; ssl_certificate /c; }"))
    check("ADMIN_OPEN", "ADMIN_OPEN" in codes("server { location /admin/ { proxy_pass http://x; } }"))
    check("admin with deny fine", "ADMIN_OPEN" not in codes("server { location /admin/ { allow 10.0.0.0/8; deny all; } }"))
    check("NO_RATE_LIMIT", "NO_RATE_LIMIT" in codes("server { location /api/login { proxy_pass http://x; } }"))
    check("rate limit inherited from server", "NO_RATE_LIMIT" not in codes(
        "server { limit_req zone=z; location /api/login { proxy_pass http://x; } }"))
    check("PROXY_HEADERS", "PROXY_HEADERS" in codes("server { location / { proxy_pass http://x; } }"))
    check("proxy_set_header in location replaces inherited", "PROXY_HEADERS" in codes(
        "server { proxy_set_header Host $host; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; "
        "proxy_set_header X-Forwarded-Proto $scheme; location / { proxy_set_header Upgrade $http_upgrade; proxy_pass http://x; } }"))
    check("DOTFILES_EXPOSED", "DOTFILES_EXPOSED" in codes("server { root /srv; }"))
    check("ACME-only port 80 server not flagged for dotfiles", "DOTFILES_EXPOSED" not in codes(
        "server { listen 80; location /.well-known/acme-challenge/ { root /var/www/certbot; } location / { return 301 https://$host$request_uri; } }"))
    check("UNLIMITED_BODY", "UNLIMITED_BODY" in codes("http { client_max_body_size 0; }"))
    check("URI_IN_REDIRECT", "URI_IN_REDIRECT" in codes("server { return 301 https://$host$uri; }"))
    check("$request_uri fine", "URI_IN_REDIRECT" not in codes("server { return 301 https://$host$request_uri; }"))
    check("INCLUDE_NOT_FOLLOWED", "INCLUDE_NOT_FOLLOWED" in codes("http { include /etc/nginx/conf.d/*.conf; }"))
    check("findings sorted HIGH first", review("server { listen 443 ssl; location /files/ { autoindex on; } }")["findings"][0]["severity"] == "HIGH")
    passed = sum(results)
    print(f"{passed}/{len(results)} passed")
    return 0 if passed == len(results) else 1


def main():
    ap = argparse.ArgumentParser(description="Nginx config security reviewer")
    ap.add_argument("files", nargs="*", help="nginx config files (nginx.conf, sites-available/*, or `nginx -T` output)")
    ap.add_argument("--json", action="store_true", help="print JSON instead of Markdown")
    ap.add_argument("--self-test", action="store_true")
    a = ap.parse_args()
    if a.self_test:
        return self_test()
    if not a.files:
        ap.error("give at least one config file, or --self-test")
    results = []
    for f in a.files:
        try:
            with open(f, encoding="utf-8") as fh:
                results.append(review(fh.read(), f))
        except (OSError, UnicodeDecodeError, ParseError) as e:
            print(f"error: {f}: {e}", file=sys.stderr)
            return 2
    print(json.dumps(results, indent=2) if a.json else to_markdown(results))
    return 1 if any(f["severity"] == "HIGH" for r in results for f in r["findings"]) else 0


if __name__ == "__main__":
    sys.exit(main())
SecurityPythonDevOps+2
F@f
0
Trình Lập Kế Hoạch Thời Gian và Nước Uống Cho Tuyến Đi Bộ Đường Dài
Kỹ năng

Lập kế hoạch cho chuyến đi bộ đường dài (day hike) trong ngày theo từng chặng. Ước tính thời gian bằng quy tắc Naismith và các hiệu chỉnh xuống dốc của Langmuir, có điều chỉnh theo địa hình, thể lực, trọng lượng ba lô và thời gian nghỉ giải lao, đưa ra giờ đồng hồ cụ thể cho từng chặng, kiểm tra thời điểm kết thúc so với giờ mặt trời lặn và giờ xuất phát an toàn muộn nhất, đồng thời tính toán lượng nước cần mang theo giữa các điểm tiếp nước và lượng đồ ăn nhẹ cần chuẩn bị. Cảnh báo các trường hợp kết thúc sau khi trời tối, các chặng dài không có điểm tiếp nước và địa hình khó vào cuối ngày. Bao gồm công cụ lập kế hoạch viết bằng thư viện chuẩn Python có hỗ trợ --self-test.

---
name: hiking-route-time-and-water-planner
description: Lập kế hoạch cho chuyến đi bộ đường dài (day hike) trong ngày theo từng chặng. Ước tính thời gian bằng quy tắc Naismith và các hiệu chỉnh xuống dốc của Langmuir, có điều chỉnh theo địa hình, thể lực, trọng lượng ba lô và thời gian nghỉ giải lao, đưa ra giờ đồng hồ cụ thể cho từng chặng, kiểm tra thời điểm kết thúc so với giờ mặt trời lặn và giờ xuất phát an toàn muộn nhất, đồng thời tính toán lượng nước cần mang theo giữa các điểm tiếp nước và lượng đồ ăn nhẹ cần chuẩn bị. Cảnh báo các trường hợp kết thúc sau khi trời tối, các chặng dài không có điểm tiếp nước và địa hình khó vào cuối ngày. Bao gồm công cụ lập kế hoạch viết bằng thư viện chuẩn Python có hỗ trợ --self-test.
---

# Trình Lập Kế Hoạch Thời Gian và Nước Uống Cho Tuyến Đi Bộ Đường Dài

Bạn giúp những người đi bộ đường dài (hikers) biến một tuyến đường thành một lịch trình thực tế: khi nào họ sẽ đến từng điểm, liệu họ có kết thúc chuyến đi khi trời còn sáng hay không, và cần mang theo bao nhiêu nước uống cũng như thức ăn. Bạn chỉ rõ quy tắc đằng sau mỗi con số, ưu tiên các ước tính thận trọng, và luôn chừa khoảng trống dự phòng cho một ngày di chuyển chậm hơn dự kiến. Bạn không phải là sự thay thế cho kiến thức bản địa, bản đồ hay dự báo thời tiết, và bạn luôn nêu rõ điều đó khi cần thiết.

## Các tệp trong kỹ năng này

- `scripts/hike_plan.py` - đọc tệp CSV danh sách các chặng (legs) và in ra lịch trình, kiểm tra ánh sáng ban ngày, lượng nước cho mỗi đoạn, thức ăn và các phát hiện cảnh báo (chỉ dùng thư viện chuẩn Python 3)
- `references/time-rules.md` - quy tắc Naismith, các hiệu chỉnh của Langmuir, các hệ số địa hình/thể lực/trọng lượng ba lô, thời gian nghỉ giải lao và cách hiệu chuẩn chúng
- `references/water-food-and-safety.md` - tốc độ uống nước, lập kế hoạch tiếp nước và xử lý nước, đồ ăn nhẹ, biên độ an toàn trước khi trời tối, thời điểm quay đầu và danh sách kiểm tra an toàn
- `templates/hike-plan.md` - bản kế hoạch để chia sẻ với nhóm và gửi lại cho người liên hệ ở nhà
- `examples/eagle-ridge-loop.csv` - tuyến đường vòng dài 22 km vào cuối tháng 10 có một đoạn leo bám đá (scramble) và một điểm tiếp nước

## Quy trình làm việc

### 1. Chia tuyến đường thành các chặng
Hỏi thông tin về tuyến đường (từ ứng dụng bản đồ hoặc sách hướng dẫn), ngày đi, và thông tin về nhóm: tốc độ di chuyển, thể lực, thành viên nhỏ tuổi nhất hoặc chậm nhất, trọng lượng ba lô. Chia nhỏ tuyến đường tại các đỉnh núi, đèo, trạm nghỉ (huts), ngã rẽ và nguồn nước; đối với mỗi chặng, hãy lấy khoảng cách, độ cao leo lên (ascent) và độ cao xuống dốc (descent) (từ biểu đồ độ cao của ứng dụng) cùng loại địa hình (road - đường nhựa/lâm nghiệp, path - đường mòn tốt, rough - gồ ghề, scramble - leo bám đá, snow - tuyết, bushwhack - băng rừng rậm). Đánh dấu những nơi có thể tiếp thêm nước. Lấy giờ mặt trời lặn của ngày và địa điểm đó, cùng nhiệt độ dự báo.

### 2. Chạy công cụ lập kế hoạch
```
python3 scripts/hike_plan.py examples/eagle-ridge-loop.csv --start 09:30 --sunset 17:20 --temp 24 --fitness 1.1 --pack-kg 9
python3 scripts/hike_plan.py my-route.csv --start 07:00 --sunset 20:45 --temp 31 --break-min 15 --json
python3 scripts/hike_plan.py --self-test
```
Mã thoát (exit code) 1 có nghĩa là có phát hiện mức HIGH (kết thúc sau khi mặt trời lặn), 2 có nghĩa là dữ liệu đầu vào không hợp lệ.

### 3. Điều chỉnh kế hoạch
Đối với AFTER_DARK hoặc SMALL_MARGIN: xuất phát sớm hơn (kịch bản sẽ in ra giờ xuất phát hợp lý muộn nhất), rút ngắn tuyến đường, hoặc chọn một điểm rút lui (bail-out point) và thời điểm bắt buộc quay đầu (turnaround time). Đối với LONG_DRY_STRETCH: thêm điểm tiếp nước, mang thêm dụng cụ chứa nước, hoặc chuyển đoạn leo dốc dài sang buổi sáng mát mẻ. Đối với HARD_TERRAIN_LATE: đảo ngược chiều đi của tuyến đường vòng để đoạn leo bám đá (scramble) được thực hiện trước. Giải thích các công thức từ `references/time-rules.md` khi được hỏi, và gợi ý hiệu chuẩn lại hệ số thể lực sau khi hoàn thành chặng đầu tiên.

### 4. Bàn giao kế hoạch
Điền vào `templates/hike-plan.md`: lịch trình kèm giờ đồng hồ cụ thể, thời điểm bắt buộc quay đầu, danh sách nước uống và thức ăn, các phương án rút lui và thông tin khẩn cấp để gửi lại cho người ở nhà.

## Kết quả ví dụ
Đối với `examples/eagle-ridge-loop.csv` (22.2 km, +1170 m / -1150 m) xuất phát lúc 09:30 với hệ số thể lực 1.1 và nhiệt độ 24 C: 7 giờ 08 phút di chuyển, 8 giờ 19 phút tính cả nghỉ giải lao, kết thúc vào khoảng 17:49, tức là muộn hơn 29 phút so với giờ mặt trời lặn lúc 17:20 (cảnh báo mức HIGH AFTER_DARK; giờ xuất phát hợp lý muộn nhất là 08:01). Bốn chặng đầu tiên không có nguồn nước, vì vậy phải mang theo 4.4 L nước (cảnh báo mức MEDIUM LONG_DRY_STRETCH). Nếu xuất phát lúc 08:00 sẽ kết thúc lúc 16:19, còn dư 61 phút trời sáng; đoạn đường khô hạn vẫn giữ nguyên, do đó kế hoạch bổ sung thêm một túi nước (bladder) 2 L hoặc giấu sẵn một chai nước dự trữ tại Pine Saddle.

## Quy tắc

- Các con số ước tính chỉ là điểm khởi đầu: hãy dặn nhóm so sánh thời gian thực tế ở chặng đầu tiên với kế hoạch và tính toán lại tỷ lệ cho các chặng sau.
- Luôn giữ ít nhất 60 phút dự phòng trước khi trời tối, và mỗi ba lô đều phải có một đèn đội đầu (headlamp) vào mọi thời điểm trong năm.
- Tuyệt đối không gợi ý uống nước chưa qua xử lý; luôn khuyến nghị lọc hoặc xử lý bất kỳ nguồn nước tự nhiên nào.
- Thời tiết, tuyết, việc vượt sông suối và điều kiện thực tế của tuyến đường luôn có quyền ưu tiên cao hơn bất kỳ lịch trình nào: hãy nhắc người dùng kiểm tra dự báo thời tiết và báo cáo thực địa, đồng thời quay đầu ngay khi điều kiện thay đổi xấu đi.
- Không đưa ra lời khuyên y tế vượt quá hướng dẫn cơ bản về bù nước và dinh dưỡng; hãy chỉ dẫn họ tham gia đào tạo sơ cứu và lưu số điện thoại cứu hộ tại địa phương.
FILE:references/time-rules.md
# Quy Tắc Tính Thời Gian

## Quy tắc Naismith (1892)
Dành 1 giờ cho mỗi 5 km trên đường bằng cộng thêm 1 giờ cho mỗi 600 m leo cao (bản gốc là 3 dặm một giờ cộng 1 giờ cho mỗi 2000 ft).
```
moving minutes = distance_km / pace_kmh * 60 + ascent_m / 10
```
Ví dụ: 4.2 km với 380 m leo cao = 50.4 + 38 = 88 phút trước khi áp dụng các hệ số điều chỉnh.

## Các hiệu chỉnh của Langmuir khi xuống dốc
Quy tắc Naismith coi việc xuống dốc giống như đi trên đường bằng. Langmuir đã tinh chỉnh lại như sau:
- dốc xuống thoải, từ 5 đến 12 độ (khoảng 9 đến 21 %): trừ đi 10 phút cho mỗi 300 m xuống dốc
- dốc xuống gắt, trên 12 độ: cộng thêm 10 phút cho mỗi 300 m xuống dốc
- dưới 5 độ: không thay đổi
Kịch bản ước tính góc xuống dốc từ độ cao giảm xuống và tỷ lệ quãng đường đi xuống trong chặng đó. Kịch bản không bao giờ để thời gian đi một chặng giảm xuống thấp hơn một nửa thời gian đi trên đường bằng của chặng đó.

## Các hệ số nhân được kịch bản sử dụng
| Hệ số | Giá trị | Khi nào áp dụng |
|---|---|---|
| Terrain road | 0.9 | Đường trải nhựa hoặc đường lâm nghiệp |
| Terrain path | 1.0 | Đường mòn đi bộ tốt |
| Terrain rough | 1.25 | Đường mòn nhiều đá, rễ cây, đất đá rời rạc hoặc lầy lội |
| Terrain scramble | 1.6 | Cần dùng tay bám, các bậc đá chênh vênh |
| Terrain snow | 1.5 | Tuyết mềm hoặc tuyết cũ chưa cần dùng đinh giày (crampon) |
| Terrain bushwhack | 1.6 | Không có đường mòn, phải vạch cây cối rậm rạp |
| Fitness (Thể lực) | 0.85 / 1.0 / 1.2 / 1.4 | Nhanh và khỏe / trung bình / thong thả hoặc nhóm hỗn hợp / có trẻ nhỏ |
| Pack (Ba lô) | +2 % mỗi kg vượt quá 10 kg | Đi qua đêm hoặc mang vác nặng |
| Breaks (Nghỉ giải lao) | 10 phút mỗi giờ (mặc định) | Dùng 15 đến 20 phút cho các chuyến đi chụp ảnh, gia đình, nhóm đông người |

Hệ số địa hình được nhân cho toàn bộ chặng, bao gồm cả thời gian leo dốc.

## Hiệu chuẩn (Calibrating)
Sau chặng đầu tiên, hãy lấy thời gian thực tế chia cho thời gian dự kiến. Nếu nhóm mất 1 giờ 50 phút cho một chặng dự kiến 1 giờ 37 phút, hệ số chênh lệch là 1.13: hãy chạy lại kịch bản với tham số `--fitness` nhân với hệ số đó và kiểm tra lại giờ kết thúc. Các nhóm luôn di chuyển theo tốc độ của người chậm nhất; hãy lập kế hoạch dựa theo người đó.

## Các quy tắc khác bạn có thể bắt gặp
- Hiệu chỉnh Tranter (Tranter's corrections): bảng tra cứu thể lực và độ mệt mỏi áp dụng thêm trên quy tắc Naismith, được dùng cho những ngày leo núi dài.
- Hàm đi bộ đường dài của Tobler (Tobler's hiking function): tốc độ = 6 * exp(-3.5 * |slope + 0.05|) km/h, được sử dụng trong các công cụ GIS; hàm này cho ra tổng thời gian tương tự trên các tuyến đường hỗn hợp.

## Thời điểm bắt buộc quay đầu (Turnaround time)
Đối với tuyến đường đi và về cùng một lối (out-and-back) hoặc tuyến đường có lối thoát rút lui (bail-out), hãy đặt ra một thời điểm quay đầu cố định: thời gian muộn nhất bạn có thể rời khỏi điểm xa nhất mà vẫn quay về kịp lúc còn một giờ ánh sáng ban ngày. Ví dụ: mặt trời lặn lúc 17:20, giữ dự phòng 60 phút, lượt về mất 3 giờ 10 phút -> bắt buộc phải quay đầu trước 13:10 dù bạn đang ở bất kỳ đâu.
FILE:references/water-food-and-safety.md
# Nước Uống, Thức Ăn và An Toàn

## Tốc độ uống nước được kịch bản sử dụng
| Nhiệt độ | Số lít mỗi giờ (tính trên tổng thời gian) |
|---|---|
| dưới 15 C | 0.4 |
| 15 đến 24 C | 0.5 |
| 25 đến 29 C | 0.75 |
| từ 30 C trở lên | 1.0 |
Cộng thêm 0.1 L cho mỗi 100 m leo cao, vì việc leo dốc khiến bạn đổ mồ hôi nhiều hơn. Nhu cầu thực tế rất khác nhau giữa mỗi người; những người ra nhiều mồ hôi, trẻ em và người ở độ cao lớn có thể cần nhiều nước hơn. Hãy uống từng ngụm nhỏ thường xuyên thay vì uống thật nhiều cùng một lúc, và bổ sung chất điện giải (viên muối, bột pha nước uống hoặc đồ ăn nhẹ có vị mặn) vào những ngày nắng nóng hoặc khi uống nhiều hơn khoảng 3 L nước.

## Lập kế hoạch mang nước
- Điểm xuất phát và mọi chặng được đánh dấu `water_at_end = yes` là các điểm tiếp nước.
- Lượng nước cần mang (Carry) = lượng nước lớn nhất cần dùng giữa hai điểm tiếp nước cộng với 0.5 L dự phòng.
- Mang nhiều hơn 3 L (3 kg) là khá nặng; các phương án xử lý bao gồm giấu sẵn nước dự trữ trên đường, chọn tuyến đường đi qua trạm nghỉ hoặc suối nước, hoặc chuyển đoạn leo dốc nóng bức nhất sang sáng sớm.
- Hãy xác nhận chắc chắn rằng các con suối và trạm nghỉ thực sự có nước vào thời điểm đó trong năm (nhiều trạm nghỉ đóng cửa vào cuối mùa thu; các con suối thường cạn khô vào cuối mùa hè).

## Xử lý nước
Xử lý tất cả các nguồn nước tự nhiên: dùng bộ lọc sợi rỗng (loại bỏ vi khuẩn và động vật nguyên sinh), viên nén chlorine dioxide (diệt được cả vi-rút, cần chờ đủ thời gian tác dụng, thường là 30 phút, và lên đến 4 giờ đối với Cryptosporidium), bút chiếu tia UV cho nước trong, hoặc đun sôi trong 1 phút (3 phút nếu ở độ cao trên 2000 m). Hãy lọc nước đục qua một lớp vải trước khi xử lý.

## Thức ăn
Khoảng 200 đến 300 kcal mỗi giờ khi đang di chuyển sẽ giúp duy trì năng lượng ổn định trong một ngày dài: các loại hạt, thanh năng lượng, trái cây sấy khô, phô mai, bánh mì kẹp. Kịch bản sử dụng mức 250 kcal cho mỗi giờ trên tổng thời gian. Hãy đóng gói dư ra một phần đồ ăn nhẹ cho mỗi người làm nguồn dự phòng khẩn cấp.

## Ánh sáng ban ngày
- Tra cứu giờ mặt trời lặn cho đúng địa điểm và ngày cụ thể (qua ứng dụng thời tiết hoặc thiên văn). Ở vùng núi, ánh nắng mặt trời sẽ khuất khỏi các thung lũng và các sườn dốc hướng đông sớm hơn so với giờ mặt trời lặn chính thức.
- Chạng vạng dân dụng (civil twilight) cung cấp khoảng 20 đến 40 phút ánh sáng có thể nhìn đường sau khi mặt trời lặn ở các vùng vĩ độ trung bình, và ngắn hơn ở vùng nhiệt đới. Tuyệt đối không lập kế hoạch dựa vào khoảng thời gian này.
- Luôn giữ 60 phút ánh sáng ban ngày dự phòng (giá trị `--margin` mặc định). Mọi ba lô đều phải trang bị một đèn đội đầu có pin đã sạc đầy.

## Danh sách kiểm tra an toàn
- Đã kiểm tra dự báo thời tiết vào tối hôm trước và sáng ngày khởi hành (giông bão, sức gió trên sống núi, độ cao đóng băng).
- Đã gửi lại kế hoạch tuyến đường, giờ xuất phát, thời điểm quay đầu và giờ về dự kiến cho một người ở nhà, kèm theo thông tin cần gọi cho ai và vào lúc nào.
- Đã tải bản đồ ngoại tuyến (offline) trên điện thoại, kèm theo sạc dự phòng; mang theo bản đồ giấy và la bàn đối với các tuyến đường hẻo lánh.
- Trang phục mặc nhiều lớp (layers) và áo khoác chống thấm nước, mũ và găng tay khi đi phía trên đường giới hạn cây gỗ (tree line) ngay cả vào mùa hè, cùng đồ chống nắng.
- Bộ sơ cứu nhỏ gọn, băng dán chống phồng rộp, chăn cứu sinh khẩn cấp, còi báo hiệu.
- Nắm rõ số điện thoại khẩn cấp tại địa phương (112 ở Châu Âu, 911 ở Bắc Mỹ) và quy trình gọi cứu hộ đường núi.
- Quy tắc bất di bất dịch: cả nhóm luôn đi cùng nhau và phải quay đầu khi đến giờ bắt buộc quay đầu, ngay khi thấy tia chớp đầu tiên, hoặc khi có bất kỳ ai cảm thấy không khỏe.
FILE:templates/hike-plan.md
# Kế hoạch đi bộ đường dài: <tên tuyến đường>, <ngày>

**Nhóm:** <tên / số lượng thành viên>   **Trưởng nhóm:** <tên, số điện thoại>
**Tuyến đường:** <vòng lặp 22.2 km, +1170 m / -1150 m, liên kết bản đồ>
**Xuất phát:** <08:00 tại bãi đỗ xe Eagle Ridge>   **Dự kiến kết thúc:** <16:19>   **Mặt trời lặn:** <17:20>
**Thời điểm bắt buộc quay đầu:** <13:00 tại đỉnh núi, bất kể tình huống nào>
**Thời hạn báo động muộn nhất cho người liên hệ ở nhà:** <19:00>

## Lịch trình
| Chặng | km | Lên / xuống m | Địa hình | Giờ bắt đầu dự kiến | Giờ kết thúc dự kiến | Tiếp nước |
|---|---|---|---|---|---|---|
| <Bãi đỗ xe đến Pine Saddle> | <4.2> | <+380 / -20> | <path> | <08:00> | <09:53> | <không> |

Các thiết lập đã dùng: <tốc độ 5 km/h, thể lực 1.1, nghỉ 10 phút/giờ, 24 C>. Kiểm tra lại sau chặng đầu tiên và tính lại tỷ lệ.

## Nước uống và thức ăn
- Mang theo từ lúc xuất phát: <4.4 L mỗi người> (không có điểm tiếp nước cho đến <Spring Hut>)
- Tiếp nước tại: <vòi nước ở Spring Hut (mở cửa đến hết tháng 10?), xử lý nước suối>
- Đồ ăn nhẹ: <2100 kcal mỗi người> + một phần dự phòng
- Chất điện giải: <có / không>

## Các phương án rút lui (Bail-out options)
- <Sau Pine Saddle: đi theo đường lâm nghiệp quay lại bãi đỗ xe, 3.5 km, 1 giờ>
- <Bỏ qua đoạn băng qua sống núi: đi xuống theo đường mòn phía bắc từ đỉnh núi>

## Trang bị mang theo (Kit)
- <đèn đội đầu, quần áo nhiều lớp, áo chống thấm, bản đồ ngoại tuyến + sạc dự phòng, bộ sơ cứu, còi, bộ lọc nước hoặc viên khử trùng>

## Thời tiết và điều kiện thực địa
- Dự báo: <đã kiểm tra vào ... : trời khô ráo, 24 C, gió 20 km/h trên sống núi>
- Ghi chú: <đoạn leo bám đá bị ướt sau cơn mưa; tránh đi nếu trời ướt>

## Khẩn cấp
- Số điện thoại khẩn cấp: <112>   Cứu hộ đường núi: <...>
- Người liên hệ ở nhà: <tên, số điện thoại>: nếu không nhận được tin nhắn trước <19:00>, hãy gọi số <số điện thoại> kèm theo bản kế hoạch này.
FILE:examples/eagle-ridge-loop.csv
leg,distance_km,ascent_m,descent_m,terrain,water_at_end
Car park to Pine Saddle,4.2,380,20,path,no
Pine Saddle to Eagle Ridge summit,3.1,520,40,rough,no
Eagle Ridge traverse,1.6,120,150,scramble,no
Ridge down to Spring Hut,3.8,30,560,path,yes
Spring Hut to Blue Lake,4.5,80,210,path,no
Blue Lake back to car park,5.0,40,170,road,yes
FILE:scripts/hike_plan.py
#!/usr/bin/env python3
"""Hiking route time and water planner (Python 3 standard library only).

Reads a CSV of route legs and estimates moving time per leg with Naismith's rule
(5 km/h plus 1 hour per 600 m of ascent) and Langmuir's descent corrections,
adjusted for terrain, fitness and pack weight, then adds breaks, gives clock
times for every leg, checks the finish against sunset, and works out how much
water to carry between refill points and how much food to pack.

CSV columns (header required; ; or , separated):
  leg, distance_km, ascent_m, descent_m, terrain, water_at_end
terrain: road | path | rough | scramble | snow | bushwhack   water_at_end: yes/no

Usage:
  python3 hike_plan.py route.csv --start 08:30 --sunset 17:10 --temp 24
  python3 hike_plan.py route.csv --start 07:00 --fitness 1.2 --pack-kg 14 --json
  python3 hike_plan.py --self-test
Exit codes: 0 = plan fine, 1 = HIGH findings (finish after sunset), 2 = bad input.
"""
import argparse, csv, io, json, math, sys

TERRAIN = {"road": 0.9, "path": 1.0, "trail": 1.0, "rough": 1.25, "scramble": 1.6, "snow": 1.5, "bushwhack": 1.6}
REQUIRED = ["leg", "distance_km", "ascent_m", "descent_m", "terrain", "water_at_end"]
SEV = {"HIGH": 0, "MEDIUM": 1, "LOW": 2}


class InputError(Exception):
    pass


def num(v, field, row):
    try:
        x = float(str(v).strip().replace(",", "."))
    except ValueError:
        raise InputError(f"row {row}: {field} '{v}' is not a number")
    if x < 0:
        raise InputError(f"row {row}: {field} cannot be negative")
    return x


def hm(s):
    try:
        h, m = s.strip().split(":")
        h, m = int(h), int(m)
        assert 0 <= h < 24 and 0 <= m < 60
        return h * 60 + m
    except Exception:
        raise InputError(f"time '{s}' must be HH:MM")


def clock(mins):
    mins = int(round(mins))
    day, rem = divmod(mins, 1440)
    return f"{rem // 60:02d}:{rem % 60:02d}" + (f" (+{day}d)" if day else "")


def dur(mins):
    mins = int(round(mins))
    return f"{mins // 60}h{mins % 60:02d}"


def read_legs(text):
    sample = text[:2000]
    delim = ";" if sample.count(";") > sample.count(",") else ","
    rd = csv.DictReader(io.StringIO(text), delimiter=delim)
    if not rd.fieldnames:
        raise InputError("empty CSV")
    fields = [f.strip().lower() for f in rd.fieldnames]
    missing = [c for c in REQUIRED if c not in fields]
    if missing:
        raise InputError("missing columns: " + ", ".join(missing))
    legs = []
    for i, raw in enumerate(rd, start=2):
        r = {k.strip().lower(): (v or "").strip() for k, v in raw.items() if k}
        if not any(r.values()):
            continue
        t = r["terrain"].lower() or "path"
        if t not in TERRAIN:
            raise InputError(f"row {i}: terrain '{t}' must be one of {', '.join(TERRAIN)}")
        w = r["water_at_end"].lower()
        if w not in ("yes", "no", "y", "n", "1", "0", "true", "false", ""):
            raise InputError(f"row {i}: water_at_end must be yes or no")
        legs.append({"leg": r["leg"] or f"Leg {len(legs) + 1}",
                     "distance_km": num(r["distance_km"], "distance_km", i),
                     "ascent_m": num(r["ascent_m"], "ascent_m", i),
                     "descent_m": num(r["descent_m"], "descent_m", i),
                     "terrain": t, "water_at_end": w in ("yes", "y", "1", "true")})
    if not legs:
        raise InputError("no legs in CSV")
    return legs


def water_rate(temp_c):
    """Litres per hour of total time, before the ascent extra."""
    if temp_c < 15:
        return 0.4
    if temp_c < 25:
        return 0.5
    if temp_c < 30:
        return 0.75
    return 1.0


def leg_minutes(leg, pace_kmh):
    d, up, down = leg["distance_km"], leg["ascent_m"], leg["descent_m"]
    flat = d / pace_kmh * 60
    climb = up / 10.0  # Naismith: 60 min per 600 m
    descent_adj = 0.0
    steep = False
    if down > 0 and d > 0:
        horiz = d * 1000 * (down / (up + down))  # share of the leg spent descending
        angle = math.degrees(math.atan(down / horiz)) if horiz > 0 else 90
        if 5 <= angle <= 12:
            descent_adj = -down / 300 * 10   # Langmuir: gentle descent is faster
        elif angle > 12:
            descent_adj = down / 300 * 10    # steep descent is slower
        steep = angle > 14
    if up > 0 and d > 0:
        horiz_up = d * 1000 * (up / (up + down))
        steep = steep or math.degrees(math.atan(up / horiz_up)) > 14  # about 25 %
    return max(flat + climb + descent_adj, flat * 0.5), steep


def plan(legs, start, sunset=None, pace=5.0, fitness=1.0, pack_kg=8.0, break_min=10.0, temp=18.0, margin=60):
    if pace <= 0 or fitness <= 0:
        raise InputError("pace and fitness must be positive")
    pack_factor = 1.0 + max(0.0, pack_kg - 10) * 0.02  # +2 % per kg above 10 kg
    rate = water_rate(temp)
    t = start
    rows, findings = [], []
    stretch, stretches, stretch_legs = 0.0, [], []
    for leg in legs:
        base, steep = leg_minutes(leg, pace)
        moving = base * TERRAIN[leg["terrain"]] * fitness * pack_factor
        total = moving * (1 + break_min / 60)
        water = total / 60 * rate + leg["ascent_m"] / 100 * 0.1
        t_start, t = t, t + total
        rows.append({**leg, "moving_min": round(moving), "with_breaks_min": round(total),
                     "start": clock(t_start), "end": clock(t), "end_min": t, "water_l": round(water, 2), "steep": steep})
        stretch += water
        stretch_legs.append(leg["leg"])
        if leg["water_at_end"]:
            stretches.append((stretch, stretch_legs)); stretch, stretch_legs = 0.0, []
        if steep:
            findings.append(("LOW", "STEEP_LEG", f"{leg['leg']}: average gradient over 25 %; expect slower going than the formula and take care on the descent"))
    if stretch_legs:
        stretches.append((stretch, stretch_legs))
    moving_total = sum(r["moving_min"] for r in rows)
    total = t - start
    carry = max(s for s, _ in stretches) + 0.5
    for s, names in stretches:
        if s + 0.5 > 3.0:
            findings.append(("MEDIUM", "LONG_DRY_STRETCH", f"{' -> '.join(names)}: {s + 0.5:.1f} L to carry with no refill; add a refill, cache water or start earlier in the cool"))
    summary = {
        "distance_km": round(sum(l["distance_km"] for l in legs), 1),
        "ascent_m": round(sum(l["ascent_m"] for l in legs)),
        "descent_m": round(sum(l["descent_m"] for l in legs)),
        "moving_min": round(moving_total), "total_min": round(total),
        "start": clock(start), "finish": clock(t),
        "water_total_l": round(sum(r["water_l"] for r in rows), 1),
        "carry_max_l": round(carry, 1),
        "food_kcal": int(round(total / 60 * 250, -1)),
        "water_rate_l_per_h": rate, "pack_factor": round(pack_factor, 2),
    }
    if sunset is not None:
        left = sunset - t
        summary["sunset"] = clock(sunset)
        summary["minutes_before_sunset"] = round(left)
        summary["latest_start"] = clock(sunset - margin - total)
        if left < 0:
            findings.append(("HIGH", "AFTER_DARK", f"finish {clock(t)} is {dur(-left)} after sunset {clock(sunset)}; start by {summary['latest_start']}, shorten the route or plan a bail-out"))
        elif left < margin:
            findings.append(("MEDIUM", "SMALL_MARGIN", f"finish {clock(t)} leaves only {round(left)} min before sunset (target {margin}); start by {summary['latest_start']} and carry a headlamp"))
        for r in rows:
            if r["terrain"] in ("scramble", "snow") and r["end_min"] > sunset - 120:
                findings.append(("MEDIUM", "HARD_TERRAIN_LATE", f"{r['leg']} ({r['terrain']}) ends at {r['end']}, within 2 h of sunset; tired legs and fading light on hard ground"))
    if moving_total > 8 * 60:
        findings.append(("LOW", "LONG_DAY", f"{dur(moving_total)} moving time; check that everyone has done a day this long recently"))
    if temp >= 28 and any(l["ascent_m"] >= 300 for l in legs):
        findings.append(("MEDIUM", "HEAT", f"{temp:g} C with big climbs: start at first light, rest in shade, electrolytes with the water"))
    findings.sort(key=lambda f: SEV[f[0]])
    for r in rows:
        r.pop("end_min")
    return {"legs": rows, "summary": summary, "stretches": [{"legs": n, "litres": round(s + 0.5, 1)} for s, n in stretches],
            "findings": [{"severity": a, "code": b, "message": c} for a, b, c in findings]}


def to_markdown(p, args):
    s = p["summary"]
    out = ["# Hike plan", "",
           f"Settings: pace {args.pace:g} km/h, fitness x{args.fitness:g}, pack {args.pack_kg:g} kg (x{s['pack_factor']}), breaks {args.break_min:g} min per hour, {args.temp:g} C",
           "",
           f"Route: {s['distance_km']} km, +{s['ascent_m']} m / -{s['descent_m']} m; moving {dur(s['moving_min'])}, with breaks {dur(s['total_min'])}",
           f"Start {s['start']}, finish about {s['finish']}" + (f"; sunset {s['sunset']}, " + (f"{s['minutes_before_sunset']} min to spare" if s['minutes_before_sunset'] >= 0 else f"{dur(-s['minutes_before_sunset'])} AFTER sunset") + f"; latest sensible start {s['latest_start']}" if "sunset" in s else ""),
           "", "| Leg | km | Up m | Down m | Terrain | Moving | With breaks | Start | End | Water L | Refill at end |",
           "|---|---|---|---|---|---|---|---|---|---|---|"]
    for r in p["legs"]:
        out.append(f"| {r['leg']} | {r['distance_km']:g} | {r['ascent_m']:g} | {r['descent_m']:g} | {r['terrain']} | {dur(r['moving_min'])} | "
                   f"{dur(r['with_breaks_min'])} | {r['start']} | {r['end']} | {r['water_l']:.1f} | {'yes' if r['water_at_end'] else 'no'} |")
    out += ["", "## Water and food",
            f"- Drinking rate {s['water_rate_l_per_h']} L/h at this temperature plus 0.1 L per 100 m of climbing; total about {s['water_total_l']} L",
            f"- Carry at most {s['carry_max_l']} L (largest stretch between refills, incl. 0.5 L reserve); treat stream water"]
    for st in p["stretches"]:
        out.append(f"  - {' -> '.join(st['legs'])}: {st['litres']} L")
    out.append(f"- Food: about {s['food_kcal']} kcal of snacks (about 250 kcal per hour on the move), plus a spare")
    out += ["", "## Findings"]
    out += [f"- {f['severity']} {f['code']}: {f['message']}" for f in p["findings"]] or ["- none"]
    out += ["", "Estimates only: check them against your group's real pace on the first leg and adjust."]
    return "\n".join(out)


def self_test():
    res = []

    def check(name, cond):
        res.append(cond)
        print(("PASS " if cond else "FAIL ") + name)

    def L(d, up, down, terrain="path", water="no", name="x"):
        return {"leg": name, "distance_km": d, "ascent_m": up, "descent_m": down, "terrain": terrain, "water_at_end": water == "yes"}

    check("flat 10 km at 5 km/h = 120 min", round(leg_minutes(L(10, 0, 0), 5)[0]) == 120)
    check("Naismith 600 m climb adds 60 min", round(leg_minutes(L(5, 600, 0), 5)[0]) == 120)
    g = leg_minutes(L(6, 0, 900), 5)[0]  # angle ~8.5 deg: gentle
    check("gentle descent is faster (Langmuir)", round(g) == 72 - 30)
    st = leg_minutes(L(2, 0, 600), 5)  # ~16.7 deg: steep
    check("steep descent is slower and flagged", round(st[0]) == 24 + 20 and st[1])
    check("steep ascent flagged", leg_minutes(L(1, 400, 0), 5)[1])
    check("descent never below half the flat time", leg_minutes(L(10, 0, 1500), 5)[0] >= 60)
    p = plan([L(10, 0, 0)], hm("08:00"), break_min=0)
    check("finish time no breaks", p["summary"]["finish"] == "10:00")
    p = plan([L(10, 0, 0)], hm("08:00"), break_min=10)
    check("10 min break per hour", p["summary"]["total_min"] == 140)
    check("terrain multiplier", plan([L(10, 0, 0, "scramble")], 0, break_min=0)["summary"]["moving_min"] == 192)
    check("fitness multiplier", plan([L(10, 0, 0)], 0, fitness=1.2, break_min=0)["summary"]["moving_min"] == 144)
    check("pack over 10 kg slows", plan([L(10, 0, 0)], 0, pack_kg=15, break_min=0)["summary"]["moving_min"] == 132)
    p = plan([L(10, 0, 0)], hm("15:00"), sunset=hm("16:30"), break_min=0)
    check("AFTER_DARK HIGH", p["findings"][0]["code"] == "AFTER_DARK" and p["findings"][0]["severity"] == "HIGH")
    check("latest start", p["summary"]["latest_start"] == "13:30")
    p = plan([L(10, 0, 0)], hm("14:00"), sunset=hm("16:30"), break_min=0)
    check("SMALL_MARGIN", [f["code"] for f in p["findings"]] == ["SMALL_MARGIN"])
    check("no finding with time to spare", plan([L(10, 0, 0)], hm("08:00"), sunset=hm("18:00"))["findings"] == [])
    check("HARD_TERRAIN_LATE", any(f["code"] == "HARD_TERRAIN_LATE" for f in
          plan([L(3, 0, 0, "scramble")], hm("13:00"), sunset=hm("15:00"))["findings"]))
    check("water rate by temperature", [water_rate(t) for t in (10, 20, 27, 33)] == [0.4, 0.5, 0.75, 1.0])
    p = plan([L(10, 0, 0, water="yes"), L(10, 0, 0)], 0, temp=20, break_min=0)
    check("water per leg", p["legs"][0]["water_l"] == 1.0)
    check("carry = largest stretch + reserve", p["summary"]["carry_max_l"] == 1.5)
    p = plan([L(10, 500, 0), L(10, 0, 500)], 0, temp=31, break_min=0)
    check("LONG_DRY_STRETCH and HEAT", {"LONG_DRY_STRETCH", "HEAT"} <= {f["code"] for f in p["findings"]})
    check("LONG_DAY", any(f["code"] == "LONG_DAY" for f in plan([L(45, 0, 0)], 0)["findings"]))
    check("food kcal", plan([L(10, 0, 0)], 0, break_min=0)["summary"]["food_kcal"] == 500)
    check("clock wraps past midnight", clock(1500) == "01:00 (+1d)")
    legs = read_legs("leg;distance_km;ascent_m;descent_m;terrain;water_at_end\nA;3,5;200;0;Path;yes\n")
    check("semicolon CSV + decimal comma", legs[0]["distance_km"] == 3.5 and legs[0]["water_at_end"])
    for bad, why in [("leg,distance_km\nA,1\n", "missing columns"),
                     ("leg,distance_km,ascent_m,descent_m,terrain,water_at_end\nA,x,0,0,path,no\n", "non-number"),
                     ("leg,distance_km,ascent_m,descent_m,terrain,water_at_end\nA,1,-5,0,path,no\n", "negative"),
                     ("leg,distance_km,ascent_m,descent_m,terrain,water_at_end\nA,1,0,0,lava,no\n", "unknown terrain")]:
        try:
            read_legs(bad); check("rejects " + why, False)
        except InputError:
            check("rejects " + why, True)
    try:
        hm("25:00"); check("rejects bad time", False)
    except InputError:
        check("rejects bad time", True)
    passed = sum(res)
    print(f"{passed}/{len(res)} passed")
    return 0 if passed == len(res) else 1


def main():
    ap = argparse.ArgumentParser(description="Hiking route time and water planner")
    ap.add_argument("csv", nargs="?", help="legs CSV")
    ap.add_argument("--start", help="start time HH:MM")
    ap.add_argument("--sunset", help="sunset time HH:MM")
    ap.add_argument("--pace", type=float, default=5.0, help="flat walking speed km/h (default 5)")
    ap.add_argument("--fitness", type=float, default=1.0, help="time multiplier: 0.85 fast, 1.0 average, 1.2 relaxed, 1.4 with young kids")
    ap.add_argument("--pack-kg", type=float, default=8.0, help="pack weight; +2%% time per kg above 10")
    ap.add_argument("--break-min", type=float, default=10.0, help="break minutes per hour of walking (default 10)")
    ap.add_argument("--temp", type=float, default=18.0, help="expected daytime temperature C")
    ap.add_argument("--margin", type=int, default=60, help="minutes of daylight to keep in hand (default 60)")
    ap.add_argument("--json", action="store_true")
    ap.add_argument("--self-test", action="store_true")
    a = ap.parse_args()
    if a.self_test:
        return self_test()
    if not a.csv or not a.start:
        ap.error("give a CSV and --start, or --self-test")
    try:
        with open(a.csv, encoding="utf-8-sig") as fh:
            legs = read_legs(fh.read())
        p = plan(legs, hm(a.start), hm(a.sunset) if a.sunset else None, a.pace, a.fitness, a.pack_kg, a.break_min, a.temp, a.margin)
    except (OSError, InputError) as e:
        print(f"error: {e}", file=sys.stderr)
        return 2
    print(json.dumps(p, indent=2) if a.json else to_markdown(p, a))
    return 1 if any(f["severity"] == "HIGH" for f in p["findings"]) else 0


if __name__ == "__main__":
    sys.exit(main())
TravelPythonPlanning+2
F@f
0
Trình Lập Thời Khóa Biểu Ôn Thi
Kỹ năng

Xây dựng thời khóa biểu ôn thi chi tiết từng ngày dựa trên ngày thi, trọng số môn học và mức độ tự tin của bạn. Chia nhỏ số giờ học thành các phiên tập trung, dành nhiều thời gian hơn cho các môn yếu và nặng, đan xen các môn học, chuyển sang làm câu hỏi luyện tập trong tuần cuối trước mỗi kỳ thi, xếp một bài giải đề các năm trước bấm giờ vào ngày hôm trước, đồng thời tôn trọng các ngày nghỉ ngơi và ngày bận. Cảnh báo khi có quá ít phiên học cho mỗi chủ đề và các kỳ thi diễn ra liên tiếp. Bao gồm công cụ lập kế hoạch viết bằng thư viện chuẩn Python có hỗ trợ --self-test.

---
name: exam-revision-timetable-planner
description: Xây dựng thời khóa biểu ôn thi chi tiết từng ngày dựa trên ngày thi, trọng số môn học và mức độ tự tin của bạn. Chia nhỏ số giờ học thành các phiên tập trung, dành nhiều thời gian hơn cho các môn yếu và nặng, đan xen các môn học, chuyển sang làm câu hỏi luyện tập trong tuần cuối trước mỗi kỳ thi, xếp một bài giải đề các năm trước bấm giờ vào ngày hôm trước, đồng thời tôn trọng các ngày nghỉ ngơi và ngày bận. Cảnh báo khi có quá ít phiên học cho mỗi chủ đề và các kỳ thi diễn ra liên tiếp. Bao gồm công cụ lập kế hoạch viết bằng thư viện chuẩn Python có hỗ trợ --self-test.
---

# Trình Lập Thời Khóa Biểu Ôn Thi

Bạn giúp học sinh, sinh viên biến một mùa thi căng thẳng thành một kế hoạch thực tế: cần học môn nào trong mỗi phiên, mỗi ngày, cho đến kỳ thi cuối cùng. Bạn sử dụng các phương pháp dựa trên bằng chứng khoa học (lặp lại ngắt quãng - spacing, luyện tập truy hồi - retrieval practice, học đan xen - interleaving, giải đề các năm trước - past papers), giữ cho kế hoạch nhân văn với thời gian nghỉ ngơi và giấc ngủ hợp lý, đồng thời làm rõ các sự đánh đổi khi không có đủ thời gian cho mọi thứ.

## Các tệp trong kỹ năng này

- `scripts/revision_plan.py` - đọc tệp CSV danh sách môn học và in ra bảng môn học, thời khóa biểu từng ngày cùng các phát hiện cảnh báo (chỉ dùng thư viện chuẩn Python 3)
- `references/study-techniques.md` - những việc cần làm trong một phiên học: luyện tập truy hồi, lặp lại ngắt quãng, học đan xen, giải đề các năm trước và những điều cần tránh
- `references/scheduling-rules.md` - cách kịch bản (script) gán mức độ ưu tiên, chỉ tiêu, loại phiên học và các phát hiện cảnh báo, cũng như cách tinh chỉnh nó
- `templates/revision-plan.md` - bản kế hoạch bàn giao lại: tổng quan, thời khóa biểu hàng tuần, quy trình trong phiên học, các mốc rà soát
- `examples/winter-exams.csv` - năm kỳ thi đại học từ ngày 2 đến 11 tháng 12 kèm theo số lượng chủ đề

## Quy trình làm việc

### 1. Thu thập thông tin thực tế
Hỏi thông tin cho từng kỳ thi: môn học, ngày thi, tỷ trọng điểm (số tín chỉ, phần trăm điểm tổng kết, hoặc mức độ quan trọng từ 1-5), mức độ tự tin từ 1 (mất gốc) đến 5 (nắm vững), và số lượng chủ đề hoặc chương. Sau đó hỏi về ngày bắt đầu ôn tập đầu tiên, số giờ thực tế có thể học vào các ngày trong tuần và cuối tuần (sau giờ lên lớp, làm việc, đi lại), thời lượng phiên học mong muốn (mặc định là 50/10, hoặc Pomodoro 25/5 nếu độ tập trung thấp), một ngày nghỉ ngơi hàng tuần và bất kỳ ngày nào đã có lịch bận từ trước.

### 2. Chạy công cụ lập kế hoạch
```
python3 scripts/revision_plan.py examples/winter-exams.csv --start 2026-11-16 --weekday-hours 3 --weekend-hours 5 --rest-day Sun --off 2026-11-21 --day-start 16:00
python3 scripts/revision_plan.py my-exams.csv --start 2026-05-04 --session-min 25 --break-min 5 --json
python3 scripts/revision_plan.py --self-test
```
Mã thoát (exit code) 1 có nghĩa là có phát hiện mức HIGH (số phiên học ít hơn số chủ đề), 2 có nghĩa là dữ liệu đầu vào không hợp lệ.

### 3. Xử lý các sự đánh đổi
Đối với UNDER_COVERED hoặc TIGHT: tăng thêm giờ học, bắt đầu sớm hơn, hạ chỉ tiêu cho các môn đã vững, hoặc thống nhất xem những chủ đề ít điểm nào có thể đọc lướt (dựa vào trọng số trong đề cương hoặc tần suất xuất hiện trong đề thi các năm trước). Đối với BACK_TO_BACK: đẩy phần luyện tập của môn thứ hai lên trước ngày thi của môn thứ nhất. Kiểm tra lại kế hoạch cùng với người học: liệu kế hoạch này có khả thi vào một ngày tồi tệ không? Hãy chừa khoảng trống dự phòng thay vì nhồi nhét kín mọi buổi tối.

### 4. Bàn giao kế hoạch
Điền vào `templates/revision-plan.md`: bảng tổng quan, thời khóa biểu theo tuần, quy trình phiên học từ `references/study-techniques.md`, và hai mốc ngày rà soát để đánh giá lại mức độ tự tin và chạy lại kịch bản.

## Kết quả ví dụ
Đối với `examples/winter-exams.csv` bắt đầu từ ngày 16 tháng 11 với 3 giờ vào các ngày trong tuần, 5 giờ vào thứ Bảy, nghỉ Chủ nhật và ngày 21 tháng 11: tổng cộng 25 ngày, 57 phiên học kéo dài 50 phút (47.5 giờ). Môn Hóa Hữu cơ (trọng số 5, độ tự tin 2) và Kinh tế Vi mô (4, 2) nhận được 15 và 16 phiên, Lịch sử Châu Âu (3, 4) nhận được 6 phiên. Mỗi môn học đều kết thúc bằng việc làm câu hỏi luyện tập và giải một đề thi các năm trước có bấm giờ vào ngày trước kỳ thi của môn đó. Các phát hiện cảnh báo: Xác suất Thống kê (10 phiên cho 8 chủ đề) và Lịch sử Châu Âu (6 phiên cho 6 chủ đề) ở mức TIGHT (sát nút), còn Lịch sử Châu Âu và Kinh tế Vi mô ở trạng thái BACK_TO_BACK (thi liên tiếp) vào ngày 7 và 8 tháng 12.

## Quy tắc

- Lập kế hoạch dựa trên số giờ học sinh thực sự có, chứ không phải số giờ họ ước mình có; bảo vệ giấc ngủ (không xếp các phiên học khuya trước ngày thi).
- Mọi phiên học đều phải kết thúc bằng hoạt động truy hồi kiến thức (trả lời câu hỏi, nhớ lại trên giấy trắng, thẻ ghi nhớ flashcards), chứ không chỉ đọc lại hay tô sáng (highlight).
- Giữ ít nhất một ngày nghỉ trọn vẹn hoặc hai nửa ngày nghỉ mỗi tuần; tình trạng kiệt sức (burnout) gây thiệt hại lớn hơn nhiều so với việc mất đi một ngày học.
- Lập lại kế hoạch sau mỗi đợt rà soát hoặc sau mỗi kỳ thi thay vì cố bám theo một kế hoạch đã vỡ.
- Không làm hộ bài tập có tính điểm cho học sinh; chỉ hỗ trợ lập kế hoạch và phương pháp học tập. Nếu học sinh có dấu hiệu căng thẳng nghiêm trọng hoặc hoảng loạn, hãy khuyến khích họ trò chuyện với bộ phận hỗ trợ sinh viên hoặc bác sĩ.
FILE:references/study-techniques.md
# Các Phương Pháp Học Tập

Những việc cần làm trong mỗi phiên học. Được sắp xếp theo độ mạnh của bằng chứng khoa học (Dunlosky và cộng sự, 2013, "Improving Students' Learning With Effective Learning Techniques", đã đánh giá việc kiểm tra thực hành và thực hành phân tán có mức độ hữu ích cao).

## Luyện tập truy hồi - Retrieval practice (cao)
Khơi gợi lại thông tin từ trí nhớ thay vì đọc lại tài liệu: trả lời các câu hỏi, viết ra mọi thứ bạn nhớ được lên một tờ giấy trắng, sau đó đối chiếu với ghi chú và điền vào những chỗ còn thiếu bằng mực màu khác. Thẻ ghi nhớ (flashcards) sẽ phát huy tác dụng nếu bạn tự trả lời trước khi lật thẻ. Việc trả lời sai trước rồi mới sửa lại cũng giúp ích rất nhiều cho quá trình ghi nhớ.

## Lặp lại ngắt quãng - Spacing (cao)
Trải đều các phiên học của một môn qua nhiều ngày thay vì dồn vào một khối thời gian dài duy nhất. Một nhịp độ ôn tập đơn giản cho mỗi chủ đề: vào ngày hôm sau, sau 3 ngày, sau một tuần, và trước kỳ thi. Thời khóa biểu sẽ đan xen các môn học qua các ngày; hãy bắt đầu mỗi phiên học bằng 5 phút tự nhớ lại nội dung của phiên học trước đó thuộc cùng môn học để tích hợp sẵn hiệu ứng lặp lại ngắt quãng.

## Học đan xen - Interleaving (trung bình)
Trộn lẫn các dạng bài tập trong cùng một môn học (đối với toán và các môn khoa học: kết hợp nhiều dạng bài toán khác nhau trong một bộ đề, thay vì làm 20 bài cùng một dạng) để bạn rèn luyện kỹ năng lựa chọn phương pháp giải, điều mà kỳ thi thực tế đòi hỏi.

## Giải đề các năm trước - Past papers (cao đối với kỳ thi)
Làm ít nhất một đề thi hoàn chỉnh trong điều kiện bấm giờ trước mỗi kỳ thi, sau đó tự chấm điểm theo biểu điểm chính thức và liệt kê số điểm bị mất theo từng chủ đề. Kịch bản sẽ xếp một buổi giải đề vào ngày trước mỗi kỳ thi; hãy bổ sung thêm các buổi giải đề sớm hơn nếu có sẵn nguồn đề.

## Đào sâu và tự giải thích - Elaboration and self-explanation (trung bình)
Đặt câu hỏi "tại sao cách này lại hiệu quả?" và "điều này liên hệ với X như thế nào?", giải thích thành tiếng một khái niệm như thể đang giảng bài cho người khác, vẽ sơ đồ khái niệm (concept map) từ trí nhớ.

## Hiệu quả thấp nếu dùng đơn lẻ
Việc đọc lại, tô sáng (highlighting) và chép lại ghi chú mang lại cảm giác đang làm việc năng suất nhưng thực chất đem lại rất ít lợi ích dài hạn. Chỉ sử dụng chúng như bước đọc lướt đầu tiên trước khi thực hiện truy hồi kiến thức.

## Quy trình một phiên học (mặc định 50/10)
1. 5 phút: tự nhớ lại từ trí nhớ những gì bạn đã làm trong lần học trước của môn này.
2. 30 phút: học hoặc luyện tập một chủ đề (Learn + recall) hoặc làm câu hỏi bài tập (Practice questions).
3. 10 phút: gấp sách lại và tự kiểm tra bản thân; đánh dấu những phần bạn còn bỏ sót.
4. 5 phút: viết ra ba câu hỏi bạn chưa trả lời được để dành cho lần học tiếp theo.
5. 10 phút nghỉ giải lao rời xa màn hình: vận động, uống nước.
Đối với những người có khả năng tập trung ngắn, hãy sử dụng các phiên 25/5 (`--session-min 25 --break-min 5`) và nghỉ giải lao dài hơn sau mỗi bốn phiên.

## Giấc ngủ, dinh dưỡng và ngày trước kỳ thi
Giấc ngủ giúp củng cố trí nhớ; việc thức trắng đêm học bài sẽ làm giảm kết quả thi. Hãy dành buổi tối trước ngày thi để ôn tập nhẹ nhàng và đi ngủ. Ăn uống đầy đủ đúng bữa, vận động mỗi ngày, và để điện thoại ở phòng khác trong suốt các phiên học.
FILE:references/scheduling-rules.md
# Quy Tắc Xếp Lịch

Cách `scripts/revision_plan.py` xây dựng thời khóa biểu.

## Số phiên học mỗi ngày
Số phiên học vừa vặn trong tổng số giờ học hàng ngày: `floor((hours * 60 + break) / (session + break))`. Với các phiên 50/10: 3 giờ -> 3 phiên, 5 giờ -> 5 phiên. Các ngày nghỉ (`--rest-day`) và các ngày bận (`--off`) không xếp phiên học nào. Vào ngày có lịch thi, chỉ một nửa số phiên được lên kế hoạch (ca thi chiếm phần thời gian còn lại), và chỉ dành cho các môn thi sau đó.

## Mức độ ưu tiên và chỉ tiêu
- priority = weight x (6 - confidence). Trọng số 5, độ tự tin 2 -> 20; trọng số 3, độ tự tin 4 -> 6.
- target sessions (số phiên chỉ tiêu) = tất cả các phiên hiện có trước kỳ thi đó x priority / tổng tất cả các priority (tối thiểu là 1).
Trọng số (weight) có thể là số tín chỉ hoặc điểm đánh giá độ quan trọng từ 1-5; chỉ có tỷ lệ tương quan mới quan trọng, vì vậy hãy sử dụng cùng một thang đo cho mọi môn học.

## Cách chọn môn cho từng phiên học
Đối với mỗi phiên học, kịch bản sẽ chọn trong số các môn có ngày thi vẫn còn ở phía trước và có số phiên học trong ngày hôm đó ít hơn `--max-same` (mặc định là 2):
1. ưu tiên các môn vẫn còn thấp hơn chỉ tiêu (target) trước;
2. sau đó chọn một môn khác với phiên ngay trước đó (học đan xen - interleaving);
3. tiếp theo là môn có nhu cầu còn lại tính trên mỗi ngày cho đến ngày thi ở mức cao nhất;
4. nếu bằng nhau thì ưu tiên môn có độ ưu tiên (priority) cao hơn, rồi đến môn thi sớm hơn.
Ngày trước kỳ thi luôn bắt đầu bằng một phiên giải đề các năm trước có bấm giờ cho môn thi đó.

## Các loại phiên học
- Learn + recall (Học + truy hồi): cho đến khi hoàn thành 60 % chỉ tiêu của môn học và cách ngày thi của môn đó hơn 7 ngày.
- Mixed review (Ôn tập tổng hợp): sau khi đạt 60 % chỉ tiêu, cách ngày thi hơn 7 ngày.
- Practice questions (Câu hỏi luyện tập): trong 7 ngày cuối cùng trước kỳ thi.
- Past paper (timed) (Giải đề bấm giờ): vào ngày ngay trước kỳ thi.

## Các phát hiện cảnh báo (Findings)
| Mã | Mức độ nghiêm trọng | Quy tắc | Cách khắc phục điển hình |
|---|---|---|---|
| UNDER_COVERED | HIGH | sessions < topics | tăng số giờ học, bắt đầu sớm hơn, đọc lướt các chủ đề ít điểm |
| TIGHT | MEDIUM | sessions < 1.5 x topics | chấp nhận ôn một lượt duy nhất, hoặc san sẻ thời gian từ các môn đã vững |
| BELOW_TARGET | MEDIUM | planned < 60 % of target | kỳ thi diễn ra sớm; hãy bắt đầu học môn đó trước tiên |
| BACK_TO_BACK | MEDIUM | các kỳ thi vào những ngày liên tiếp | đẩy sớm phần luyện tập của môn thi thứ hai lên trước |
| SAME_DAY | MEDIUM | hai kỳ thi trong cùng một ngày | coi cả hai môn như một khối chung trong tuần cuối cùng |
| NO_REST_DAY | LOW | kế hoạch > 10 ngày mà không có ngày nghỉ hay ngày bận | thêm `--rest-day` |
| LONG_DAYS | LOW | hơn 8 giờ mỗi ngày | giới hạn tối đa ở mức 5-6 giờ tập trung |

## Tinh chỉnh
- Dành thêm thời gian cho một môn học: tăng trọng số (weight) hoặc giảm độ tự tin (confidence) của môn đó rồi chạy lại.
- Giảm số lần chuyển đổi môn học: dùng `--max-same 3`.
- Giờ đồng hồ cụ thể: `--day-start 09:00` sẽ thêm thời gian bắt đầu và kết thúc, kèm theo thời gian nghỉ giải lao giữa các phiên.
- Lập lại kế hoạch: sau mỗi đợt rà soát, cập nhật lại độ tự tin và tham số `--start`, rồi chạy lại; các môn đã thi xong sẽ được loại bỏ khỏi tệp CSV.
FILE:templates/revision-plan.md
# Kế hoạch ôn thi: <tên>, <mùa thi>

**Giai đoạn:** <16 Th11 đến 11 Th12 2026>   **Số giờ:** <3 giờ ngày thường, 5 giờ thứ Bảy>   **Nghỉ ngơi:** <Chủ nhật + 21 Th11>
**Số phiên:** <57 x 50 phút (47.5 giờ)>   **Nhịp độ phiên học:** <50 phút học / 10 phút nghỉ>

## Các kỳ thi và mức độ ưu tiên
| Môn học | Ngày thi | Trọng số | Độ tự tin | Số chủ đề | Số phiên dự kiến | Ghi chú |
|---|---|---|---|---|---|---|
| <Hóa Hữu cơ> | <Thứ Tư 2 Th12> | <5> | <2> | <9> | <15> | <giải đề 1 Th12> |

## Tuần 1: <16-22 Th11>
| Ngày | Phiên 1 | Phiên 2 | Phiên 3 | Phiên 4 | Phiên 5 |
|---|---|---|---|---|---|
| <Thứ Hai 16 Th11> | <16:00 Hóa Hữu cơ: Học> | <17:00 Kinh tế Vi mô: Học> | <...> | | |

## Tuần 2: <...>
<...>

## Quy trình phiên học
1. 5 phút tự nhớ lại phiên học trước (trên giấy trắng)
2. 30 phút học hoặc luyện tập một chủ đề
3. 10 phút tự kiểm tra, đánh dấu những chỗ còn thiếu sót
4. 5 phút: ghi lại ba câu hỏi còn bỏ ngỏ cho lần học tới
5. 10 phút nghỉ giải lao rời xa màn hình

## Các sự đánh đổi đã thống nhất
- <Xác suất Thống kê đang ở mức TIGHT: ôn toàn diện một lượt, lượt thứ hai chỉ tập trung vào các chủ đề 5-8 (chiếm nhiều điểm nhất trong đề thi các năm trước)>
- <Lịch sử Châu Âu / Kinh tế Vi mô thi liên tiếp: hoàn thành phần luyện tập Kinh tế Vi mô trước ngày 5 Th12>

## Các mốc rà soát (Check-ins)
- <Chủ nhật 22 Th11>: đánh giá lại độ tự tin từ 1-5 cho từng môn, chạy lại công cụ lập kế hoạch
- <Chủ nhật 29 Th11>: tương tự, điều chỉnh lại 10 ngày cuối cùng

## Sức khỏe tinh thần và thể chất
- Giấc ngủ: <lên giường trước 23:30, không học sau 22:00 vào buổi tối trước ngày thi>
- Vận động hàng ngày: <đi bộ 30 phút>
- Nếu cảm thấy quá tải: <trò chuyện với ... / liên hệ bộ phận hỗ trợ sinh viên>
FILE:examples/winter-exams.csv
subject,exam_date,weight,confidence,topics
Organic Chemistry,2026-12-02,5,2,9
Statistics,2026-12-04,4,3,8
European History,2026-12-07,3,4,6
Microeconomics,2026-12-08,4,2,7
Academic English,2026-12-11,2,4,4
FILE:scripts/revision_plan.py
#!/usr/bin/env python3
"""Exam revision timetable planner (Python 3 standard library only).

Reads a CSV of subjects with exam dates, weight and confidence, splits the
available study time into focused sessions, and builds a day-by-day timetable
that gives weak, heavy subjects more time, interleaves subjects, stops new work
on a subject after its exam, moves from learning to practice questions in the
last week before each exam and puts a timed past paper on the day before.

CSV columns (header required; ; or , separated):
  subject, exam_date (YYYY-MM-DD), weight (1-5 or credits), confidence (1-5), topics (optional)

Usage:
  python3 revision_plan.py exams.csv --start 2026-11-16 --weekday-hours 3 --weekend-hours 5
  python3 revision_plan.py exams.csv --start 2026-11-16 --rest-day Sun --off 2026-11-28,2026-11-29 --day-start 16:00
  python3 revision_plan.py --self-test
Exit codes: 0 = plan fits, 1 = HIGH findings (not enough sessions), 2 = bad input.
"""
import argparse, csv, datetime as dt, io, json, sys

DAYS = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"]
SEV = {"HIGH": 0, "MEDIUM": 1, "LOW": 2}


class InputError(Exception):
    pass


def parse_date(s, what):
    try:
        return dt.date.fromisoformat(s.strip())
    except ValueError:
        raise InputError(f"{what} '{s}' must be YYYY-MM-DD")


def read_subjects(text):
    sample = text[:2000]
    delim = ";" if sample.count(";") > sample.count(",") else ","
    rd = csv.DictReader(io.StringIO(text), delimiter=delim)
    if not rd.fieldnames:
        raise InputError("empty CSV")
    fields = [f.strip().lower() for f in rd.fieldnames]
    missing = [c for c in ("subject", "exam_date", "weight", "confidence") if c not in fields]
    if missing:
        raise InputError("missing columns: " + ", ".join(missing))
    subs = []
    for i, raw in enumerate(rd, start=2):
        r = {k.strip().lower(): (v or "").strip() for k, v in raw.items() if k}
        if not any(r.values()):
            continue
        try:
            w = float(r["weight"].replace(",", "."))
            c = int(r["confidence"])
            t = int(r["topics"]) if r.get("topics") else None
        except ValueError:
            raise InputError(f"row {i}: weight, confidence and topics must be numbers")
        if w <= 0:
            raise InputError(f"row {i}: weight must be positive")
        if not 1 <= c <= 5:
            raise InputError(f"row {i}: confidence must be 1 (weak) to 5 (strong)")
        if not r["subject"]:
            raise InputError(f"row {i}: subject is empty")
        subs.append({"subject": r["subject"], "exam": parse_date(r["exam_date"], f"row {i}: exam_date"),
                     "weight": w, "confidence": c, "topics": t})
    if not subs:
        raise InputError("no subjects in CSV")
    return subs


def slots_for(hours, session, brk):
    """Number of sessions that fit in `hours` with a break between sessions."""
    mins = hours * 60
    if mins < session:
        return 0
    return int((mins + brk) // (session + brk))


def plan(subs, start, weekday_hours=3.0, weekend_hours=5.0, session=50, brk=10, rest_day=None,
         off=(), max_same=2, day_start=None):
    if session < 15 or brk < 0:
        raise InputError("session must be at least 15 minutes and break not negative")
    for s in subs:
        if s["exam"] <= start:
            raise InputError(f"{s['subject']}: exam {s['exam']} is not after the start date {start}")
    last = max(s["exam"] for s in subs)
    exam_days = {}
    for s in subs:
        exam_days.setdefault(s["exam"], []).append(s["subject"])
    for s in subs:
        s["priority"] = s["weight"] * (6 - s["confidence"])
    days = []
    d = start
    while d < last:
        if d in off or (rest_day is not None and d.weekday() == rest_day):
            n = 0
        else:
            n = slots_for(weekend_hours if d.weekday() >= 5 else weekday_hours, session, brk)
            if d in exam_days:
                n //= 2  # the exam takes half the day
        days.append((d, n))
        d += dt.timedelta(days=1)
    # target sessions: share of the slots available before each exam, by priority
    total_p = sum(s["priority"] for s in subs)
    for s in subs:
        before = sum(n for day, n in days if day < s["exam"])
        s["target"] = max(1, round(before * s["priority"] / total_p))
        s["planned"] = 0
    timetable = []
    for day, n in days:
        today, used = [], {}
        live = [s for s in subs if s["exam"] > day]
        # the day before an exam: a timed past paper first
        for s in live:
            if (s["exam"] - day).days == 1 and len(today) < n:
                today.append((s["subject"], "Past paper (timed)")); used[s["subject"]] = 1; s["planned"] += 1
        while len(today) < n:
            cands = [s for s in live if used.get(s["subject"], 0) < max_same]
            if not cands:
                break
            prev = today[-1][0] if today else None

            def score(s):
                days_left = max(1, (s["exam"] - day).days)
                need = (s["target"] - s["planned"]) / days_left
                # subjects still below target first, then interleave (avoid repeating the previous subject)
                return (s["planned"] < s["target"], s["subject"] != prev, need, s["priority"], -s["exam"].toordinal())
            s = max(cands, key=score)
            left = (s["exam"] - day).days
            kind = "Practice questions" if left <= 7 else ("Learn + recall" if s["planned"] < s["target"] * 0.6 else "Mixed review")
            today.append((s["subject"], kind)); used[s["subject"]] = used.get(s["subject"], 0) + 1; s["planned"] += 1
        timed = []
        t = day_start
        for subj, kind in today:
            if t is not None:
                end = t + session
                timed.append({"subject": subj, "kind": kind, "time": f"{t // 60:02d}:{t % 60:02d}-{end // 60:02d}:{end % 60:02d}"})
                t = end + brk
            else:
                timed.append({"subject": subj, "kind": kind})
        timetable.append({"date": day.isoformat(), "dow": DAYS[day.weekday()], "exams": exam_days.get(day, []), "sessions": timed})
    findings = []
    for s in subs:
        if s["topics"]:
            if s["planned"] < s["topics"]:
                findings.append(("HIGH", "UNDER_COVERED", f"{s['subject']}: {s['planned']} sessions for {s['topics']} topics; add hours, drop low-yield topics or start earlier"))
            elif s["planned"] < 1.5 * s["topics"]:
                findings.append(("MEDIUM", "TIGHT", f"{s['subject']}: {s['planned']} sessions for {s['topics']} topics leaves little time for a second pass"))
        if s["planned"] < 0.6 * s["target"]:
            findings.append(("MEDIUM", "BELOW_TARGET", f"{s['subject']}: {s['planned']} of {s['target']} target sessions fit before the exam"))
    dates = sorted(exam_days)
    for a, b in zip(dates, dates[1:]):
        if (b - a).days <= 1:
            findings.append(("MEDIUM", "BACK_TO_BACK", f"exams on {a} ({', '.join(exam_days[a])}) and {b} ({', '.join(exam_days[b])}); finish the second subject's practice before the first exam"))
    for day, subjects in exam_days.items():
        if len(subjects) > 1:
            findings.append(("MEDIUM", "SAME_DAY", f"{len(subjects)} exams on {day}: {', '.join(subjects)}"))
    if rest_day is None and len(days) > 10 and not off:
        findings.append(("LOW", "NO_REST_DAY", "no rest day in a plan longer than 10 days; take at least half a day off each week"))
    if max(weekday_hours, weekend_hours) > 8:
        findings.append(("LOW", "LONG_DAYS", "more than 8 hours of revision a day rarely stays productive; keep 5-6 focused hours"))
    findings.sort(key=lambda f: SEV[f[0]])
    total_sessions = sum(len(t["sessions"]) for t in timetable)
    return {
        "subjects": [{"subject": s["subject"], "exam": s["exam"].isoformat(), "weight": s["weight"], "confidence": s["confidence"],
                      "priority": s["priority"], "topics": s["topics"], "target": s["target"], "planned": s["planned"],
                      "hours": round(s["planned"] * session / 60, 1)} for s in sorted(subs, key=lambda x: x["exam"])],
        "summary": {"start": start.isoformat(), "last_exam": last.isoformat(), "days": len(days), "sessions": total_sessions,
                    "study_hours": round(total_sessions * session / 60, 1), "session_min": session, "break_min": brk},
        "timetable": timetable,
        "findings": [{"severity": a, "code": b, "message": c} for a, b, c in findings],
    }


def to_markdown(p):
    s = p["summary"]
    out = ["# Revision timetable", "",
           f"{s['start']} to {s['last_exam']}: {s['days']} days, {s['sessions']} sessions of {s['session_min']} min ({s['study_hours']} h of focused study), {s['break_min']} min breaks",
           "", "| Subject | Exam | Weight | Confidence | Priority | Topics | Target | Planned | Hours |", "|---|---|---|---|---|---|---|---|---|"]
    for x in p["subjects"]:
        out.append(f"| {x['subject']} | {x['exam']} | {x['weight']:g} | {x['confidence']} | {x['priority']:g} | {x['topics'] or '-'} | {x['target']} | {x['planned']} | {x['hours']} |")
    out += ["", "## Day by day", ""]
    for t in p["timetable"]:
        head = f"- **{t['dow']} {t['date']}**" + (f" (EXAM: {', '.join(t['exams'])})" if t["exams"] else "")
        if not t["sessions"]:
            out.append(head + ": rest")
            continue
        parts = [(f"{x['time']} " if "time" in x else "") + f"{x['subject']} ({x['kind']})" for x in t["sessions"]]
        out.append(head + ": " + "; ".join(parts))
    out += ["", "## Findings"]
    out += [f"- {f['severity']} {f['code']}: {f['message']}" for f in p["findings"]] or ["- none"]
    out += ["", "Start each session with 5 minutes of recall from memory on the previous session of that subject, and end with 3 questions you could not answer."]
    return "\n".join(out)


def self_test():
    res = []

    def check(name, cond):
        res.append(cond)
        print(("PASS " if cond else "FAIL ") + name)

    D = dt.date
    S = lambda name, exam, w=1, c=3, t=None: {"subject": name, "exam": exam, "weight": w, "confidence": c, "topics": t}
    check("slots: 3 h of 50+10 = 3", slots_for(3, 50, 10) == 3)
    check("slots: 50 min fits exactly 1", slots_for(50 / 60, 50, 10) == 1)
    check("slots: under one session = 0", slots_for(0.5, 50, 10) == 0)
    check("slots: 25+5 in 2 h = 4", slots_for(2, 25, 5) == 4)
    p = plan([S("A", D(2026, 1, 8))], D(2026, 1, 5), weekday_hours=2, weekend_hours=2, session=50, brk=10)
    check("no sessions on or after exam day", all(t["date"] < "2026-01-08" for t in p["timetable"]))
    check("day before exam starts with past paper", p["timetable"][-1]["sessions"][0]["kind"] == "Past paper (timed)")
    check("practice questions in last 7 days", all(x["kind"] != "Learn + recall" for t in p["timetable"] for x in t["sessions"]))
    subs = [S("Weak", D(2026, 3, 1), w=3, c=1), S("Strong", D(2026, 3, 1), w=3, c=5)]
    p = plan(subs, D(2026, 2, 1), weekday_hours=3, weekend_hours=3, max_same=3)
    pl = {x["subject"]: x["planned"] for x in p["subjects"]}
    check("weak subject gets more sessions", pl["Weak"] > 3 * pl["Strong"])
    check("priority = weight x (6 - confidence)", p["subjects"][0]["priority"] in (15, 3))
    p = plan([S("A", D(2026, 3, 1)), S("B", D(2026, 3, 1))], D(2026, 2, 1), weekday_hours=3, weekend_hours=3)
    check("max 2 sessions of one subject per day", all(
        sum(1 for x in t["sessions"] if x["subject"] == subj) <= 2 for t in p["timetable"] for subj in "AB"))
    check("subjects interleaved while both need time", all(t["sessions"][i]["subject"] != t["sessions"][i + 1]["subject"]
                                      for t in p["timetable"][:10] for i in range(len(t["sessions"]) - 1)))
    p = plan([S("A", D(2026, 2, 16))], D(2026, 2, 2), rest_day=6)
    check("rest day empty", all(not t["sessions"] for t in p["timetable"] if t["dow"] == "Sun"))
    p = plan([S("A", D(2026, 2, 6))], D(2026, 2, 2), off={D(2026, 2, 3)})
    check("off day empty", not [t for t in p["timetable"] if t["date"] == "2026-02-03"][0]["sessions"])
    p = plan([S("A", D(2026, 2, 5)), S("B", D(2026, 2, 9))], D(2026, 2, 2), weekday_hours=4)
    day5 = [t for t in p["timetable"] if t["date"] == "2026-02-05"][0]
    check("exam day: half the slots, other subject only", len(day5["sessions"]) == 2 and all(x["subject"] == "B" for x in day5["sessions"]))
    p = plan([S("A", D(2026, 2, 5), t=20)], D(2026, 2, 2))
    check("UNDER_COVERED HIGH", p["findings"][0]["code"] == "UNDER_COVERED")
    p = plan([S("A", D(2026, 2, 20), t=11)], D(2026, 2, 2), weekday_hours=1, weekend_hours=1, rest_day=6)
    check("TIGHT", any(f["code"] == "TIGHT" for f in p["findings"]))
    p = plan([S("A", D(2026, 2, 9)), S("B", D(2026, 2, 10)), S("C", D(2026, 2, 10))], D(2026, 2, 2))
    codes = [f["code"] for f in p["findings"]]
    check("BACK_TO_BACK and SAME_DAY", "BACK_TO_BACK" in codes and "SAME_DAY" in codes)
    check("NO_REST_DAY", "NO_REST_DAY" in [f["code"] for f in plan([S("A", D(2026, 3, 1))], D(2026, 2, 1))["findings"]])
    check("LONG_DAYS", "LONG_DAYS" in [f["code"] for f in plan([S("A", D(2026, 2, 5))], D(2026, 2, 2), weekend_hours=10)["findings"]])
    p = plan([S("A", D(2026, 2, 5))], D(2026, 2, 2), day_start=9 * 60)
    check("clock times with --day-start", p["timetable"][0]["sessions"][1]["time"] == "10:00-10:50")
    subs = read_subjects("subject;exam_date;weight;confidence;topics\nMaths;2026-12-03;4,5;2;9\n")
    check("semicolon CSV + decimal comma", subs[0]["weight"] == 4.5 and subs[0]["topics"] == 9)
    for bad, why in [("subject,exam_date\nA,2026-01-01\n", "missing columns"),
                     ("subject,exam_date,weight,confidence\nA,2026-13-01,1,3\n", "bad date"),
                     ("subject,exam_date,weight,confidence\nA,2026-01-01,1,9\n", "confidence out of range"),
                     ("subject,exam_date,weight,confidence\nA,2026-01-01,x,3\n", "non-number")]:
        try:
            read_subjects(bad); check("rejects " + why, False)
        except InputError:
            check("rejects " + why, True)
    try:
        plan([S("A", D(2026, 1, 1))], D(2026, 1, 1)); check("rejects exam not after start", False)
    except InputError:
        check("rejects exam not after start", True)
    passed = sum(res)
    print(f"{passed}/{len(res)} passed")
    return 0 if passed == len(res) else 1


def main():
    ap = argparse.ArgumentParser(description="Exam revision timetable planner")
    ap.add_argument("csv", nargs="?", help="subjects CSV")
    ap.add_argument("--start", help="first revision day YYYY-MM-DD (default today)")
    ap.add_argument("--weekday-hours", type=float, default=3.0)
    ap.add_argument("--weekend-hours", type=float, default=5.0)
    ap.add_argument("--session-min", type=int, default=50, help="focused session length (default 50)")
    ap.add_argument("--break-min", type=int, default=10, help="break between sessions (default 10)")
    ap.add_argument("--rest-day", choices=DAYS, help="weekly day off")
    ap.add_argument("--off", default="", help="comma-separated dates with no revision (YYYY-MM-DD)")
    ap.add_argument("--max-same", type=int, default=2, help="max sessions of one subject per day (default 2)")
    ap.add_argument("--day-start", help="clock time of the first session HH:MM (adds times to the timetable)")
    ap.add_argument("--json", action="store_true")
    ap.add_argument("--self-test", action="store_true")
    a = ap.parse_args()
    if a.self_test:
        return self_test()
    if not a.csv:
        ap.error("give a CSV, or --self-test")
    try:
        with open(a.csv, encoding="utf-8-sig") as fh:
            subs = read_subjects(fh.read())
        start = parse_date(a.start, "--start") if a.start else dt.date.today()
        off = {parse_date(x, "--off") for x in a.off.split(",") if x.strip()}
        ds = None
        if a.day_start:
            try:
                h, m = map(int, a.day_start.split(":")); assert 0 <= h < 24 and 0 <= m < 60
            except Exception:
                raise InputError("--day-start must be HH:MM")
            ds = h * 60 + m
        p = plan(subs, start, a.weekday_hours, a.weekend_hours, a.session_min, a.break_min,
                 DAYS.index(a.rest_day) if a.rest_day else None, off, a.max_same, ds)
    except (OSError, InputError) as e:
        print(f"error: {e}", file=sys.stderr)
        return 2
    print(json.dumps(p, indent=2) if a.json else to_markdown(p))
    return 1 if any(f["severity"] == "HIGH" for f in p["findings"]) else 0


if __name__ == "__main__":
    sys.exit(main())
PythonStudentsEducation+2
F@f
0
Ứng dụng React Native taste
Văn bản

Các quy ước mang tính định kiến để xây dựng ứng dụng di động đa nền tảng bằng React Native và Expo.

# Ứng dụng React Native taste của một kỹ sư di động đa nền tảng
- Bắt đầu các ứng dụng mới bằng Expo và quy trình prebuild thay vì một dự án React Native thuần (bare), và chỉ eject khi một mô-đun bản địa (native module) thực sự đòi hỏi điều đó. Độ tin cậy: 0.90
- Viết mọi thứ bằng TypeScript với chế độ nghiêm ngặt (strict mode) được bật, bao gồm cả các tham số điều hướng và các liên kết (bindings) mô-đun bản địa. Độ tin cậy: 0.95
- Sử dụng Expo Router để điều hướng dựa trên tệp và luôn bật định tuyến có kiểu dữ liệu (typed routes) để các liên kết bị hỏng sẽ báo lỗi ngay tại thời điểm biên dịch. Độ tin cậy: 0.80
- Lưu trạng thái máy chủ (server state) trong TanStack Query và giới hạn trạng thái máy khách toàn cục (global client state) trong một kho lưu trữ Zustand nhỏ gọn dành cho phiên đăng nhập và tùy chỉnh cá nhân. Độ tin cậy: 0.85
- Tuyệt đối không lưu trữ token trong AsyncStorage; hãy sử dụng expo-secure-store hoặc Keychain và Keystore cho bất kỳ thông tin nhạy cảm nào. Độ tin cậy: 0.95
- Kết xuất (render) các danh sách dài bằng FlashList hoặc một FlatList đã được tinh chỉnh kỹ với các khóa (keys) ổn định, tuyệt đối không dùng ScrollView kết hợp với hàm map. Độ tin cậy: 0.90
- Chạy các hiệu ứng chuyển động (animations) trên luồng UI bằng Reanimated và xử lý cử chỉ bằng react-native-gesture-handler, thay vì dùng Animated API cũ. Độ tin cậy: 0.85
- Bật Kiến trúc Mới (New Architecture) và Hermes theo mặc định, đồng thời coi những thư viện cản trở việc này là nợ kỹ thuật (tech debt). Độ tin cậy: 0.75
- Duy trì một mô-đun giao diện (theme) duy nhất cho màu sắc, khoảng cách và kiểu chữ, đồng thời hỗ trợ chế độ tối (dark mode) ngay từ màn hình đầu tiên. Độ tin cậy: 0.85
- Tuân thủ các vùng an toàn (safe areas) bằng react-native-safe-area-context thay vì gán cứng khoảng đệm (padding) cho phần tai thỏ (notches). Độ tin cậy: 0.90
- Đặt mã nguồn đặc thù cho từng nền tảng vào các tệp .ios.tsx và .android.tsx thay vì rải rác các câu lệnh kiểm tra Platform.OS. Độ tin cậy: 0.70
- Phát hành các bản vá lỗi JavaScript thông qua EAS Update kèm theo chính sách phiên bản runtime, và sử dụng các bản phát hành trên cửa hàng ứng dụng cho những thay đổi ở tầng native. Độ tin cậy: 0.80
- Dựng (build) và ký số (sign) trong CI bằng EAS Build để không có bản phát hành nào bị phụ thuộc vào một chiếc máy tính cá nhân đơn lẻ. Độ tin cậy: 0.85
- Kiểm thử các thành phần (components) bằng React Native Testing Library và truy vấn theo vai trò (role) cùng văn bản (text), thay vì ưu tiên tìm theo testID trước. Độ tin cậy: 0.80
- Bao phủ các luồng quan trọng từ đầu đến cuối (end-to-end) bằng Maestro hoặc Detox trên các trình giả lập thực tế trong CI. Độ tin cậy: 0.75
- Thêm accessibilityLabel và accessibilityRole vào mọi phần tử có thể chạm (touchable) và giữ kích thước vùng chạm tối thiểu là 44 điểm (points). Độ tin cậy: 0.90
- Xử lý trạng thái ngoại tuyến (offline) như một trạng thái bình thường: hiển thị dữ liệu đã lưu trong bộ nhớ đệm, xếp hàng các thao tác ghi và thử lại khi có kết nối trở lại. Độ tin cậy: 0.80
- Báo cáo các sự cố sập ứng dụng (crashes) và lỗi JavaScript bằng Sentry, bao gồm cả source maps cho mọi bản phát hành. Độ tin cậy: 0.85
- Tối ưu hóa hình ảnh bằng expo-image cùng cơ chế bộ nhớ đệm thay vì dùng thành phần Image mặc định. Độ tin cậy: 0.80
- Đo đạc hiệu năng (profile) bằng React Native DevTools trước khi phỏng đoán các cách khắc phục vấn đề hiệu năng. Độ tin cậy: 0.70
- Chỉ yêu cầu cấp quyền tại đúng thời điểm một tính năng cần đến chúng, kèm theo một màn hình giải thích ngắn gọn trước đó. Độ tin cậy: 0.90
Best PracticesMobile DevelopmentJavaScript+2
F@f
0
Tài liệu Kỹ thuật taste
Văn bản

Các quy ước mang tính định kiến để viết và duy trì tài liệu dành cho nhà phát triển luôn giữ được độ chính xác.

# Tài liệu Kỹ thuật taste của một người viết hướng đến nhà phát triển
- Giữ tài liệu trong cùng kho lưu trữ (repository) với mã nguồn và xét duyệt (review) chúng trong cùng một pull request với thay đổi đó. Độ tin cậy: 0.95
- Viết bằng Markdown hoặc MDX và xây dựng trang tài liệu bằng trình tạo trang tĩnh như Docusaurus, Starlight hoặc MkDocs. Độ tin cậy: 0.85
- Tổ chức nội dung thành bài hướng dẫn từng bước (tutorials), hướng dẫn thực hiện (how-to guides), tài liệu tham chiếu (reference) và giải thích (explanation) theo mô hình Diataxis. Độ tin cậy: 0.85
- Mở đầu tệp README bằng một câu nêu rõ dự án làm gì, sau đó là hướng dẫn cài đặt và phần bắt đầu nhanh (quick start) hoạt động được ngay trong màn hình đầu tiên. Độ tin cậy: 0.95
- Đảm bảo mọi đoạn mã mẫu đều có thể sao chép-dán và chạy được ngay, bao gồm đầy đủ các câu lệnh import cùng những giá trị thực tế. Độ tin cậy: 0.95
- Kiểm thử các đoạn mã mẫu trong CI hoặc trích xuất chúng từ các tệp ví dụ đã được kiểm thử để chúng không bao giờ bị lỗi thời. Độ tin cậy: 0.80
- Tạo tài liệu tham chiếu API tự động từ mã nguồn, chẳng hạn như OpenAPI, TypeDoc hoặc docstrings, thay vì viết thủ công. Độ tin cậy: 0.90
- Viết ở ngôi thứ hai và dùng thể chủ động với các câu ngắn gọn cùng từ ngữ giản dị. Độ tin cậy: 0.90
- Sử dụng các tiêu đề dựa trên tác vụ như "Rotate an API key" (Xoay vòng khóa API) thay vì những tiêu đề mơ hồ như "Keys" (Khóa). Độ tin cậy: 0.85
- Nêu rõ các điều kiện tiên quyết và phiên bản ở ngay đầu mỗi bài hướng dẫn. Độ tin cậy: 0.85
- Hiển thị kết quả đầu ra kỳ vọng sau mỗi câu lệnh để người đọc biết rằng họ đang làm đúng hướng. Độ tin cậy: 0.80
- Kiểm tra văn phong bằng Vale cùng một bộ quy tắc hành văn chung, đồng thời kiểm tra các đường dẫn liên kết trong mỗi lần dựng (build). Độ tin cậy: 0.80
- Sử dụng các khối lưu ý (admonitions) như Note và Warning một cách tiết chế, chỉ dành cho những thông tin làm thay đổi hành động của người đọc. Độ tin cậy: 0.75
- Ưu tiên vẽ sơ đồ dưới dạng mã nguồn bằng Mermaid để chúng có thể được xét duyệt và cập nhật giống như văn bản. Độ tin cậy: 0.75
- Duy trì nhật ký thay đổi (changelog) theo định dạng Keep a Changelog và viết hướng dẫn chuyển đổi (migration guides) cho mọi thay đổi gây hỏng tương thích (breaking change). Độ tin cậy: 0.90
- Ghi lại các quyết định kiến trúc dưới dạng các bản ghi ADR ngắn gọn giải thích rõ bối cảnh và những sự đánh đổi, chứ không chỉ nêu mỗi lựa chọn cuối cùng. Độ tin cậy: 0.85
- Gán người phụ trách và ngày xét duyệt gần nhất cho mỗi trang, đồng thời gỡ bỏ những trang không còn đúng với thực tế. Độ tin cậy: 0.75
- Thêm ô tìm kiếm và các liên kết sâu (deep links) ổn định trỏ tới từng tiêu đề trước khi bổ sung bất kỳ tính năng nào khác cho trang web. Độ tin cậy: 0.80
- Viết văn bản thay thế (alt text) cho mọi ảnh chụp màn hình và tránh dùng ảnh chụp màn hình chứa văn bản vốn có thể trình bày dưới dạng khối mã lệnh. Độ tin cậy: 0.85
- Thu thập phản hồi bằng một tiện ích đơn giản kiểu "Trang này có hữu ích không?" và xem xét kết quả định kỳ hàng tháng. Độ tin cậy: 0.65
- Ghi lại tài liệu về các thông báo lỗi theo đúng nguyên văn để người dùng có thể tìm thấy cách khắc phục thông qua việc tìm kiếm. Độ tin cậy: 0.85
Best PracticesMarkdownDocumentation+2
F@f
0
Thử nghiệm Học máy taste
Văn bản

Các quy ước mang tính định kiến để chạy các thử nghiệm học máy có khả năng tái lập, từ mô hình cơ sở đến môi trường production.

# Thử nghiệm Học máy taste của một kỹ sư ML ứng dụng
- Viết chỉ số đánh giá và một mô hình cơ sở (baseline) đơn giản trước khi huấn luyện bất kỳ mô hình thực tế nào. Độ tin cậy: 0.95
- Chia dữ liệu theo thời gian hoặc theo thực thể khi có khả năng xảy ra rò rỉ dữ liệu (data leakage), tuyệt đối không chia ngẫu nhiên theo mặc định. Độ tin cậy: 0.90
- Đóng băng tập kiểm thử (test set) từ sớm và chỉ xem nó khi đưa ra các quyết định cuối cùng, đồng thời sử dụng một tập xác thực (validation set) riêng biệt để tinh chỉnh. Độ tin cậy: 0.95
- Theo dõi mọi lần chạy bằng MLflow hoặc Weights & Biases, ghi nhật ký (log) cấu hình, các chỉ số, phiên bản mã nguồn và phiên bản tập dữ liệu. Độ tin cậy: 0.90
- Lưu cấu hình thử nghiệm trong các tệp YAML được quản lý phiên bản, nạp bằng Hydra hoặc Pydantic, thay vì dùng các hằng số gán cứng (hardcoded). Độ tin cậy: 0.80
- Thiết lập và ghi lại các hạt giống ngẫu nhiên (random seeds) cho Python, NumPy cùng framework đang dùng, đồng thời báo cáo phương sai qua nhiều seed khác nhau. Độ tin cậy: 0.85
- Quản lý phiên bản các tập dữ liệu bằng DVC hoặc các đường dẫn lưu trữ đối tượng bất biến (immutable object storage) để mọi kết quả đều có thể tái lập. Độ tin cậy: 0.80
- Tạo mẫu thử nghiệm (prototype) trong các notebook nhưng hãy chuyển mã nguồn có thể tái sử dụng vào một gói Python đã được kiểm thử ngay khi nó được dùng đến lần thứ hai. Độ tin cậy: 0.85
- Cố định các thư viện phụ thuộc bằng tệp khóa (lock file) và ghi nhận lại phiên bản CUDA cùng trình điều khiển (driver) trong mỗi lần chạy. Độ tin cậy: 0.75
- Kiểm tra thủ công các mẫu dữ liệu thô và các lỗi gán nhãn trước khi tin tưởng vào bất kỳ chỉ số tổng hợp nào. Độ tin cậy: 0.90
- Ưu tiên các mô hình cây tăng cường độ dốc (gradient boosted trees) cho dữ liệu dạng bảng và chỉ tìm đến học sâu (deep learning) khi chúng tỏ ra kém hiệu quả rõ rệt. Độ tin cậy: 0.80
- Sử dụng PyTorch với một vòng lặp huấn luyện gọn nhẹ hoặc dùng Lightning thay vì các framework tùy chỉnh cồng kềnh. Độ tin cậy: 0.70
- Báo cáo các chỉ số theo từng phân khúc (slice) chẳng hạn như khu vực, thiết bị hoặc lớp (class), chứ không chỉ dừng lại ở một con số tổng thể duy nhất. Độ tin cậy: 0.90
- Hiệu chuẩn (calibrate) các xác suất dự đoán khi mã nguồn ở khâu sau đưa ra quyết định dựa trên ngưỡng (threshold). Độ tin cậy: 0.75
- Tinh chỉnh siêu tham số (hyperparameters) bằng Optuna hoặc tìm kiếm ngẫu nhiên (random search) đơn giản trong phạm vi ngân sách tính toán cố định. Độ tin cậy: 0.75
- Viết kiểm thử đơn vị (unit tests) cho các bước biến đổi đặc trưng (feature transforms) và bộ nạp dữ liệu (data loaders), đồng thời kiểm tra xác nhận (assert) kích thước (shapes) cùng kiểu dữ liệu (dtypes) kỳ vọng. Độ tin cậy: 0.85
- Xác thực dữ liệu đầu vào bằng các bước kiểm tra lược đồ (schema) như Pandera hoặc Great Expectations trước khi huấn luyện. Độ tin cậy: 0.80
- Lưu mô hình cùng với luồng tiền xử lý (preprocessing pipeline) của nó thành một tạo phẩm (artifact) duy nhất để quá trình huấn luyện và phục vụ (serving) không bị lệch nhau. Độ tin cậy: 0.90
- So sánh mô hình mới với mô hình đang chạy trên môi trường production ở chế độ ngoại tuyến (offline) và sau đó thông qua triển khai chạy ngầm (shadow) hoặc thử nghiệm A/B. Độ tin cậy: 0.85
- Giám sát sự trôi dạt đặc trưng (feature drift) và phân phối dự đoán sau khi ra mắt, đồng thời cảnh báo khi có sự dịch chuyển lớn. Độ tin cậy: 0.80
- Viết một ghi chú thử nghiệm ngắn cho mỗi lần chạy quan trọng, nêu rõ giả thuyết, kết quả và bước tiếp theo. Độ tin cậy: 0.85
- Xóa hoặc lưu trữ các nhánh công việc thất bại thay vì để lại hàng tá các notebook làm dở dang. Độ tin cậy: 0.65
Best PracticesPythonTesting+2
F@f
0
Video
Quảng cáo Thể thao Giải Vô địch Cầu lông Điện ảnh (Video Dọc)

Video quảng cáo cầu lông chân thực dài 30 giây, tỷ lệ 9:16 gồm sáu cảnh: cảnh toàn từ trên cao giới thiệu nhà thi đấu, cảnh quay theo sát bước di chuyển chân, cận cảnh góc thấp đôi giày, cú nhảy đập cầu quay chậm, chuyển cảnh chia đôi màn hình phản chiếu gương và cảnh quay người hùng chủ đạo, với chỉnh màu xanh mòng két (teal) và xanh dương cùng negative prompt. Đang thịnh hành trên X, được chia sẻ gốc bởi @DuaFatimaAi: https://x.com/DuaFatimaAi/status/2113431683127259435

Tạo một video quảng cáo thể thao cầu lông đậm chất điện ảnh siêu thực dài 30 giây lấy bối cảnh bên trong một nhà thi đấu vô địch trong nhà khổng lồ chật kín khán giả. Một nữ vận động viên cầu lông tài năng mặc áo đấu thể thao màu đỏ pha trắng, quần đùi đen, giày thể thao trắng và tóc buộc đuôi ngựa đang chuẩn bị cho một trận đấu căng thẳng.

Cảnh 1: Cảnh quay toàn cảnh từ trên cao (aerial establishing shot) đầy choáng ngợp của sân vận động trong nhà khổng lồ, với kết cấu mái thép ấn tượng, hàng nghìn khán giả và những ánh đèn pha mạnh mẽ chiếu sáng rực rỡ sân cầu lông.

Cảnh 2: Một góc máy theo dõi (tracking shot) đầy năng động bám sát nữ vận động viên khi cô di chuyển nhanh nhẹn khắp mặt sân cầu lông màu xanh lá, nắm chặt cây vợt và chuẩn bị đỡ một cú đánh đầy uy lực. Bắt trọn sự tập trung cao độ, bộ pháp chân thực và các chuyển động thể thao của cô.

Cảnh 3: Cận cảnh góc cực thấp (extreme low-angle close-up) ghi lại đôi giày thể thao màu trắng của cô đang thực hiện những bước di chuyển nhanh trên mặt sân. Thể hiện độ ma sát chân thực, bộ pháp tốc độ, hình ảnh phản chiếu tinh tế trên mặt sàn và hiệu ứng nhòe chuyển động (motion blur) tự nhiên.

Cảnh 4: Cảnh quay chậm (slow-motion) đầy kịch tính khi nữ vận động viên bật nhảy lên cao để thực hiện cú đập cầu (smash). Ánh đèn sân vận động rực sáng phía sau lưng cô khi máy quay xoay nhẹ, tạo nên vẻ đẹp thẩm mỹ mạnh mẽ chuẩn quảng cáo thể thao.

Cảnh 5: Các cảnh chuyển tiếp điện ảnh đầy tính nghệ thuật với hiệu ứng phản chiếu gương, các tấm vách chéo trong suốt như kính, bố cục chia đôi màn hình (split-screen) và cận cảnh mặt vợt tiếp xúc với quả cầu. Duy trì giải phẫu cơ thể chân thực và ngoại hình nhân vật nhất quán.

Cảnh 6: Kết thúc bằng một góc quay người hùng (hero shot) từ góc thấp đầy uy lực ghi lại hình ảnh nữ vận động viên đứng tự tin trên sân, bao quanh là ánh đèn sân vận động rực sáng và khán giả đang reo hò cổ vũ.

Phong cách Hình ảnh: Kỹ thuật quay phim thể thao chân thực như ảnh chụp, độ phân giải 4K, ánh sáng sân vận động kịch tính, chỉnh màu xanh mòng két (teal) và xanh dương, kết cấu da chân thực, độ sâu trường ảnh nông, chuyển động máy quay linh hoạt, hành động tốc độ cao, hiệu ứng quay chậm mượt mà, lóa ống kính (lens flares) tinh tế, chất lượng quảng cáo thể thao chuyên nghiệp.

Máy quay: Góc rộng từ trên cao, góc quay bám sát (tracking shots), cận cảnh góc thấp, chi tiết macro, hành động quay chậm và các góc quay người hùng đầy kịch tính.

Tỷ lệ Khung hình: 9:16 dọc
Thời lượng: 30 giây
Chất lượng: Siêu thực, đậm chất điện ảnh, quảng cáo thể thao chuyên nghiệp.

Negative Prompt: Hình ảnh hoạt hình, giải phẫu cơ thể biến dạng, thừa ngón tay, tay chân bị nhân đôi, chuyển động vợt thiếu tự nhiên, quả cầu lơ lửng bất thường, khuôn mặt không nhất quán, khung hình nhấp nháy, chủ thể bị mờ, vật lý phi thực tế, nhòe chuyển động quá mức.
Video GenerationCinematicVisual+2
F@f
0
Ảnh Thời trang Đầm Haute Couture Nghệ thuật Xoắn giấy (Paper-Quilled)
Hình ảnh
Ảnh Thời trang Đầm Haute Couture Nghệ thuật Xoắn giấy (Paper-Quilled)

Ảnh chụp thời trang cao cấp toàn thân của một người mẫu trong chiếc đầm haute couture xoắn giấy (paper-quilled) mang phong cách tiên phong (avant-garde) được tạo thành từ các dải ruy băng 3D xếp chồng lên nhau, với các biến số cho người mẫu, tư thế, chi tiết váy, dải chuyển màu, phụ kiện đội đầu và phông nền, được chiếu sáng bởi đèn spotlight phòng chụp đầy kịch tính. Đang thịnh hành trên X, được chia sẻ gốc bởi @yourPlugAI: https://x.com/yourPlugAI/status/2114219176646586620

Tạo một bức ảnh thời trang cao cấp (high-fashion) chụp toàn thân của tall model with a sleek low bun trong tư thế elegant three-quarter standing pose, đang diện một chiếc đầm haute couture nghệ thuật xoắn giấy (paper-quilled) mang phong cách tiên phong (avant-garde) được làm từ các dải ruy băng 3D mềm mại xếp chồng lên nhau. Chiếc đầm nổi bật với a fitted corset bodice and sweeping train được tạo thành từ các lớp ruy băng uốn lượn theo dải chuyển màu (gradient) vivid cobalt, emerald green, and gold. Phụ kiện đi kèm bao gồm wide-brimmed matching hat with quilled plume details. Đặt trên phông nền màu dark charcoal grey với ánh sáng đèn spotlight studio đầy kịch tính làm nổi bật kết cấu bề mặt và các đường viền sắc nét. Kết xuất (render) với chi tiết 3D sắc sảo, độ tương phản cao.
VisualArtCreative+2
F@f
0
Video
Quảng cáo Chăm sóc Da Kem chống nắng Cao cấp (Video Dọc)

Quảng cáo kem chống nắng cao cấp gồm tám cảnh, tỷ lệ 9:16: người mẫu trong khu vườn nhiệt đới ngập nắng, cận cảnh làn da căng bóng ẩm mượt, cảnh quay sản phẩm chủ đạo xoay quanh bệ đá, cận cảnh macro kết cấu gel, hình ảnh hóa thành phần và cảnh đi dạo trên bãi biển, với chỉnh màu giờ vàng (golden-hour) cùng các quy tắc nhất quán nghiêm ngặt cho người mẫu và bao bì. Đang thịnh hành trên X, được chia sẻ gốc bởi @DuaFatimaAi: https://x.com/DuaFatimaAi/status/2113803220074295618

Tạo một video quảng cáo chăm sóc da đậm chất điện ảnh cao cấp cho một sản phẩm kem chống nắng, lấy cảm hứng từ các quảng cáo làm đẹp xa xỉ. Định dạng dọc 9:16, hình ảnh chân thực như ảnh chụp (photorealistic), chuyển cảnh thanh lịch, ánh nắng vàng dịu nhẹ, kết cấu da tự nhiên, bảng màu cam ấm và vàng kim, kỹ thuật quay phim quảng cáo cao cấp.

Cảnh 1 (0–5s): Một phụ nữ trẻ xinh đẹp với mái tóc dài màu nâu sẫm đứng trong một khu vườn nhiệt đới xanh mướt được bao quanh bởi những tán lá xanh rực rỡ và ánh nắng ban mai ấm áp. Những tia nắng vàng xuyên qua tán lá khi cô nhẹ nhàng chạm vào má, khoe làn da khỏe mạnh, rạng rỡ và tự nhiên. Sử dụng chuyển động máy quay đẩy nhẹ vào (push-in) mượt mà đậm chất điện ảnh.

Cảnh 2 (5–10s): Cận cảnh đặc tả (extreme close-up) khuôn mặt của người phụ nữ khi cô nhẹ nhàng thoa sản phẩm chăm sóc da lên má bằng đầu ngón tay. Làm nổi bật lỗ chân lông chân thực, kết cấu da mịn màng, những điểm bắt sáng tinh tế và vẻ căng bóng ẩm mượt tươi mới. Ánh sáng tự nhiên dịu nhẹ, độ sâu trường ảnh nông, thẩm mỹ quảng cáo làm đẹp cao cấp.

Cảnh 3 (10–15s): Cảnh quay sản phẩm chủ đạo (hero shot) của tuýp kem chống nắng màu cam pha trắng đứng thẳng trên một bệ đá tự nhiên trong khu vườn nhiệt đới. Ánh nắng mặt trời tạo nên những vệt sáng tuyệt đẹp trên khắp bao bì, với tán lá xanh được làm mờ nhẹ ở hậu cảnh. Từ từ xoay máy quay (orbit) xung quanh sản phẩm. Giữ cho bao bì nhất quán với hình ảnh tham chiếu sản phẩm được cung cấp.

Cảnh 4 (15–20s): Cảnh quay cận cảnh macro một đầu ngón tay đang giữ một lượng nhỏ chất gel màu xanh lục nhạt trong mờ. Chất gel có kết cấu bóng mượt, mỏng nhẹ và bắt nắng tuyệt đẹp. Chuyển cảnh sang cận cảnh tuýp kem chống nắng với những giọt nước lơ lửng thanh lịch và các hạt ánh vàng tinh tế.

Cảnh 5 (20–25s): Người phụ nữ mỉm cười tự nhiên khi tận hưởng ánh nắng trong khu vườn nhiệt đới. Mái tóc cô khẽ bay trong làn gió nhẹ. Cắt cảnh sang góc quay sản phẩm chủ đạo sang trọng được bao quanh bởi ánh sáng vàng ấm áp, những giọt nước tinh tế và các yếu tố thực vật mềm mại.

Cảnh 6 (25–30s): Hình ảnh hóa thành phần chăm sóc da tinh tế với các quả cầu vàng trong mờ, những giọt nước và tuýp kem chống nắng màu cam pha trắng rực sáng. Sử dụng kỹ thuật quay macro thanh lịch, bố cục gọn gàng và đồ họa chuyển động (motion graphics) mượt mà. Duy trì phong cách quảng cáo chăm sóc da khoa học cao cấp.

Cảnh 7 (30–35s): Chuyển cảnh sang một bãi biển nhiệt đới rực rỡ với làn nước màu ngọc lam và cát trắng mịn. Người phụ nữ mặc chiếc váy mùa hè màu trắng thanh lịch, bước đi bình yên bên bờ biển. Mái tóc cô bay tự nhiên trong gió biển khi ánh nắng phản chiếu lấp lánh trên mặt nước. Sử dụng các góc máy rộng điện ảnh và chuyển động máy theo dõi (tracking) nhẹ nhàng.

Cảnh 8 (35–40s): Kết thúc bằng cảnh cận cảnh kem chống nắng được lấy ra từ tuýp lên đầu ngón tay, tiếp theo là góc quay từ trên cao (overhead shot) tuyệt đẹp tại bãi biển cảnh người phụ nữ đang thư giãn trên chiếc khăn tắm màu trắng. Khép lại bằng cảnh quay sản phẩm kem chống nắng chủ đạo tinh tế, sang trọng với ánh nắng ấm áp và mặt nước phản chiếu lấp lánh.

Phong cách Hình ảnh: Quảng cáo chăm sóc da xa xỉ chân thực như ảnh chụp, kết cấu da và sản phẩm chân thực, ánh sáng giờ vàng dịu nhẹ, bầu không khí thực vật nhiệt đới, quay phim macro điện ảnh, chuyển động máy quay mượt mà, kỹ thuật cắt cảnh đồng điệu (match cuts) thanh lịch, hiệu ứng lóa ống kính (lens flares) tinh tế, chỉnh màu cao cấp, chất lượng 4K, 24fps.

Yêu cầu về Tính nhất quán: Duy trì cùng một người mẫu nữ, đường nét khuôn mặt, kiểu tóc, bao bì sản phẩm và tông màu cam-trắng xuyên suốt tất cả các cảnh. Giữ cho nhãn sản phẩm rõ nét và nhất quán với hình ảnh tham chiếu. Tránh bàn tay bị biến dạng, thừa ngón tay, làm mịn da nhân tạo, bao bì bị cong vênh và văn bản ngẫu nhiên. Không thêm các tuyên bố sản phẩm không có căn cứ hoặc làm thay đổi nhận diện thương hiệu.
Video GenerationCinematicTrending+2
F@f
0
Đóng Vai Chuyên Gia Khắc Phục Sự Cố Sức Khỏe Bể Cá Tại Nhà
Văn bản

Chẩn đoán từng bước bể cá nước ngọt đang gặp sự cố: kết luận phân loại mức độ khẩn cấp, bảng thuật ngữ bể cá, bảng giải mã triệu chứng cho nước đục và cá bệnh, ngưỡng chỉ số nước và hành động xử lý, trạng thái chu trình nitơ, kế hoạch hành động 7 ngày, kiểm tra phân biệt bệnh lý với vấn đề nguồn nước, độ tương thích mật độ nuôi, ánh sáng và rêu tảo, lịch chăm sóc định kỳ, quy tắc phòng ngừa và khi nào cần tìm trợ giúp.

Hãy đóng vai Chuyên Gia Khắc Phục Sự Cố Sức Khỏe Bể Cá Tại Nhà. Bạn giúp những người chơi cá cảnh tìm ra nguyên nhân vì sao bể cá nước ngọt hoặc đàn cá của họ đang gặp vấn đề và hướng dẫn họ đưa bể cá trở lại trạng thái ổn định, khỏe mạnh. Bạn tư duy như một người nuôi cá dày dạn kinh nghiệm kết hợp với một bác sĩ thú y cẩn trọng: luôn bắt đầu từ chất lượng nước trước khi đổ lỗi cho mầm bệnh, hỏi số liệu đo kiểm cụ thể thay vì phỏng đoán, giải thích mọi thuật ngữ ngay lần đầu tiên sử dụng, và luôn tách biệt rõ giữa nguyên nhân có khả năng cao với nguyên nhân chỉ mang tính giả thuyết. Bạn không bao giờ khuyên châm thuốc một cách mù quáng, và luôn nói rõ khi nào vấn đề cần đến bác sĩ thú y thủy sản chuyên khoa hoặc cửa hàng cá cảnh địa phương có thiết bị kiểm tra chuyên dụng.

Thông tin bể cá của tôi:
- Kích thước và tuổi đời của bể: about 100 liters, set up 7 weeks ago
- Các loài cá và sinh vật khác: 6 neon tetras, 2 platies, 1 bristlenose pleco, a few nerite snails
- Những biểu hiện tôi đang thấy: cloudy white water, two tetras gasping near the surface, one platy with clamped fins
- Thời điểm bắt đầu xuất hiện: 4 days ago, right after I added the platies
- Kết quả đo kiểm nước (nếu có): not tested yet, I have strips but no liquid kit
- Hệ thống lọc, sưởi và đèn chiếu sáng: hang-on-back filter, 26 C heater, LED light on 10 hours a day
- Thói quen bảo dưỡng định kỳ: 25 percent water change every two weeks with dechlorinator, filter sponge rinsed under the tap
- Chế độ cho ăn: flakes twice a day, algae wafer every other night
- Cây thủy sinh và đồ trang trí: a few live plants, driftwood, gravel
- Những thay đổi gần đây: new fish, moved the tank closer to a window

Trước tiên, trong một tin nhắn ngắn gọn, hãy hỏi tôi bất kỳ thông tin quan trọng nào còn thiếu: chỉ số đo chính xác của amoniac (ammonia), nitrit (nitrite), nitrat (nitrate), pH và nhiệt độ (cùng GH hoặc KH nếu tôi có), có con cá nào đã chết chưa và chết nhanh như thế nào, mô tả cận cảnh hoặc hình ảnh về bất kỳ đốm lạ, mảng nấm bông, vết thương hoặc bụng sưng phồng nào, cá mới mua về có được cách ly kiểm dịch (quarantine) hay không, và chỉ số nước máy tại nhà tôi là bao nhiêu. Nếu có bất kỳ dấu hiệu nào giống tình huống khẩn cấp (amoniac hoặc nitrit cao, cá đớp khí trên mặt nước, hỏng sưởi), hãy đưa ra các bước xử lý khẩn cấp ngay lập tức rồi mới đặt câu hỏi sau.

Sau đó, hãy hướng dẫn tôi qua các phần sau:

1. **Tóm Tắt Phân Loại Khẩn Cấp (Triage Summary)**: kết luận ngắn gọn (ổn định, theo dõi sát, cần xử lý ngay hôm nay, khẩn cấp) cùng hai hoặc ba nguyên nhân có khả năng cao nhất theo thứ tự ưu tiên, và một việc duy nhất tôi cần làm ngay trong một giờ tới.

2. **Thuật Ngữ Bể Cá Bạn Sẽ Gặp**: bảng thuật ngữ ngắn gọn gồm tên thuật ngữ và giải thích dễ hiểu: chu trình nitơ (nitrogen cycle), vi sinh có lợi (beneficial bacteria), amoniac (ammonia), nitrit (nitrite), nitrat (nitrate), hội chứng bể mới (new tank syndrome), chạy lọc tạo vi sinh (cycling), tải sinh học (bioload), dung dịch khử clo (dechlorinator), GH, KH, biến động pH (pH swing), bể cách ly (quarantine tank) và bùng phát vi khuẩn làm đục nước (bacterial bloom).

3. **Bảng Giải Mã Triệu Chứng**: bảng liệt kê mọi triệu chứng tôi đã mô tả, với các cột: triệu chứng, ý nghĩa thông thường, khả năng nghiêm trọng hơn, phép thử hoặc quan sát nào giúp phân biệt chúng, và mức độ khẩn cấp. Bao gồm cả hình thức bên ngoài của nước (đục trắng, xanh lá, nâu, vàng, nổi bọt, có mùi hôi) và hành vi của cá (đớp khí, cụp vây, ẩn nấp, cọ mình vào đồ vật, lờ đờ, bỏ ăn, nổi đốm, mọc nấm bông, rách vây, chướng bụng).

4. **Kiểm Tra Chất Lượng Nước**: ý nghĩa của từng kết quả đo, ngưỡng an toàn cho các loài cá của tôi, cách đọc que thử (strips) so với bộ test dung dịch nhỏ giọt (liquid kits), tại sao cần kiểm tra cả nước máy lẫn nước trong bể, và quy tắc rõ ràng về việc cần làm ở từng mức chỉ số (ví dụ: bất kỳ chỉ số amoniac hoặc nitrit nào lớn hơn 0 trong một bể đã ổn định đều đồng nghĩa với việc phải thay nước ngay hôm nay).

5. **Trạng Thái Chu Trình Vi Sinh (Cycle Status)**: đánh giá xem bể của tôi đã hoàn tất chu trình vi sinh hay chưa dựa trên tuổi bể, mật độ nuôi và kết quả test nước; giải thích việc thả thêm cá mới và rửa vật liệu lọc bằng nước máy có thể làm sập hệ vi sinh có lợi như thế nào, cũng như cách bảo vệ hoặc gây dựng lại hệ vi sinh mà không làm chết cá.

6. **Kế Hoạch Hành Động Trong 7 Ngày Tới**: bảng lịch trình từng ngày gồm các cột: ngày, hành động cần làm, các chỉ số cần đo, dấu hiệu cần theo dõi, và kết quả nào cho thấy phương pháp đang phát huy tác dụng. Bao gồm tỷ lệ thay nước an toàn và cách cân bằng nhiệt độ nước mới, điều chỉnh lượng thức ăn, chăm sóc bộ lọc, và khi nào nên ngừng can thiệp thêm.

7. **Bệnh Lý Hay Vấn Đề Nguồn Nước?**: cách phân biệt vấn đề môi trường nước với nhiễm trùng hoặc ký sinh trùng, các bệnh phổ biến khớp với triệu chứng của tôi (ví dụ: bệnh nấm trắng ich, thối vây, columnaris hoặc bệnh nhung velvet), và khi nào việc dùng thuốc là hợp lý. Không bao giờ nêu tên thuốc và liều lượng mà không dặn tôi tuân thủ chính xác hướng dẫn trên nhãn sản phẩm, tháo than hoạt tính khỏi bộ lọc nếu nhãn thuốc yêu cầu, và kiểm tra xem thuốc có an toàn cho ốc, tép, cây thủy sinh và các loài cá da trơn không vảy như cá dọn bể (pleco) của tôi hay không.

8. **Kiểm Tra Mật Độ Nuôi và Độ Tương Thích**: đánh giá xem các loài cá của tôi có phù hợp với kích thước bể, nhiệt độ và độ pH khi sống chung hay không, số lượng cá theo đàn tối thiểu, kích thước khi trưởng thành, tải sinh học, và nguy cơ bắt nạt hoặc rỉa vây. Chỉ đề xuất thay đổi nếu điều đó tốt cho đàn cá.

9. **Ánh Sáng, Rêu Tảo và Cây Thủy Sinh**: việc di chuyển bể gần cửa sổ và số giờ bật đèn ảnh hưởng thế nào đến rêu tảo và nhiệt độ nước, lịch bật đèn hợp lý, và những cách đơn giản để giữ cây thủy sinh khỏe mạnh mà không châm quá liều phân nước.

10. **Lịch Chăm Sóc Định Kỳ**: danh sách kiểm tra hàng tuần, hàng tháng và hàng quý gồm công việc, thời gian cần thiết và lý do tại sao mỗi việc lại quan trọng: thay nước, hút cặn nền, rửa vật liệu lọc trong nước cũ rút từ bể, đo kiểm nước, cạo rêu mặt kính, kiểm tra sưởi và nhiệt kế, cắt tỉa cây, và quy trình cách ly đối với cá mới.

11. **Quy Tắc Phòng Ngừa**: tám quy tắc ngắn gọn lẽ ra đã giúp ngăn chặn sự cố hôm nay, được đối chiếu trực tiếp với những gì tôi đã chia sẻ với bạn.

12. **Khi Nào Cần Tìm Sự Trợ Giúp**: các dấu hiệu rõ ràng cần đến bác sĩ thú y thủy sản hoặc cửa hàng cá cảnh giàu kinh nghiệm, cùng những thông tin và mẫu nước cần mang theo.

Quy tắc: luôn hỏi số liệu cụ thể thay vì tự suy đoán; gắn nhãn mọi nguyên nhân là có khả năng cao (likely), có thể xảy ra (possible) hoặc ít khả năng (unlikely); ưu tiên các biện pháp khắc phục nhẹ nhàng, từ từ hơn là những thay đổi lớn đột ngột; không bao giờ gợi ý thả cá hoặc cây thủy sinh ra môi trường tự nhiên; luôn đặt phúc lợi động vật lên hàng đầu; sử dụng ngôn ngữ dễ hiểu, đoạn văn ngắn gọn và bảng biểu ở những chỗ hữu ích. Kết thúc bằng một việc quan trọng nhất mà tôi cần làm ngay hôm nay.
PlanningChecklistBeginner+1
F@f
0
Kế Hoạch Sửa Chữa và Đàm Phán Từ Báo Cáo Kiểm Định Nhà (JSON)
Văn bản

Dán các phát hiện kiểm định nhà và điều khoản giao dịch của bạn để nhận kế hoạch JSON chặt chẽ: kết luận tổng thể, mọi phát hiện được phân loại theo độ an toàn, mức độ khẩn cấp và khoảng chi phí ước tính, các hạng mục cần chuyên gia kiểm tra thêm trước hạn chót, danh sách xếp hạng yêu cầu sửa chữa hoặc khấu trừ gửi người bán, câu hỏi cho người bán và kiểm định viên, chiến lược đàm phán kèm giới hạn rút lui, triển vọng ngân sách 5 năm và lịch bảo trì năm đầu tiên.

1{
2 "role": "Cố vấn mua nhà thực tế, điềm tĩnh, giúp người mua nhà lần đầu và người mua có kinh nghiệm biến một báo cáo kiểm định nhà dài dòng, đáng lo ngại thành danh sách ưu tiên rõ ràng về những gì thực sự quan trọng, chi phí dự kiến và những gì cần yêu cầu từ người bán, mà không phóng đại các lỗi nhỏ hay đưa ra lời khuyên pháp lý",
3 "task": "Chuyển đổi các phát hiện kiểm định, chi tiết bất động sản và các điều khoản giao dịch của tôi thành một kế hoạch JSON chặt chẽ duy nhất: kết luận tổng thể, mọi phát hiện được sắp xếp theo mức độ an toàn, chi phí và độ khẩn cấp, khoảng chi phí ước tính sơ bộ cho từng hạng mục kèm lý do, những hạng mục nào cần chuyên gia kiểm tra chuyên sâu trước hạn chót, danh sách xếp hạng các yêu cầu sửa chữa hoặc khoản khấu trừ cần đề xuất với người bán, các câu hỏi dành cho người bán và kiểm định viên, chiến lược đàm phán kèm điểm giới hạn rút lui (walk-away line), lịch trình cho đến hạn chót điều khoản dự phòng kiểm định (inspection contingency deadline), và danh sách bảo trì trong năm đầu tiên. Không bao giờ tự bịa ra các phát hiện, số đo, giá cả hoặc quy định địa phương mà tôi không cung cấp.",
4 "context": {
5 "property": "${property:a 1978 two-story detached house, 160 square meters, wood frame, basement}",
6 "location_and_climate": "${location:a suburb with cold snowy winters and humid summers}",
7 "purchase_price": "${price:420000}",
8 "currency": "${currency:USD}",
9 "inspection_findings": "${findings:roof shingles near end of life with some curling; furnace 22 years old, works; water heater 14 years old; double-tapped breakers in the electrical panel; a few ungrounded outlets; minor efflorescence and one hairline crack in the basement wall; slow drain in the upstairs bathroom; missing handrail on basement stairs; GFCI missing in the kitchen; gutters clogged; attic insulation thin}",
10 "inspector_notes": "${inspector_notes:recommends an electrician and a roofer evaluate; no active leaks seen}",
...+133 dòng nữa
Personal FinanceBudgetingDecision Making+3
F@f
0
Kế Hoạch Đếm Ngược Trước Ngày Lên Đường Du Học (YAML)
Văn bản

Chuyển đổi điểm đến, chương trình học và ngày đi của bạn thành kế hoạch đếm ngược du học định dạng YAML: kiểm tra hạn chót giấy tờ và thị thực, kế hoạch theo từng tuần từ 10 tuần trước khi bay, thiết lập tài chính và ngân hàng, các bước chuẩn bị sức khỏe và bảo hiểm, hậu cần nhà ở và khi mới đến, đóng gói hành lý trong giới hạn cân nặng, những việc cần tạm dừng ở quê nhà, lịch trình ngày khởi hành và danh sách việc cần làm trong tuần đầu tiên.

1role: Cố vấn du học có tổ chức, luôn khích lệ tinh thần, chuyên giúp sinh viên biến những tháng ngày hỗn loạn trước khi lên đường đi học một học kỳ hoặc chương trình cấp bằng ở nước ngoài thành một kế hoạch đếm ngược bình tĩnh theo từng tuần để không bỏ sót điều gì quan trọng và tuần đầu tiên khi mới đến diễn ra suôn sẻ
2task: >
3 Chuyển đổi điểm đến, chương trình học, các mốc thời gian và hoàn cảnh của tôi thành một kế hoạch khởi hành hoàn chỉnh dưới dạng YAML:
4 kiểm tra giấy tờ và hạn chót, lịch đếm ngược từng tuần từ 10 tuần trước ngày khởi hành cho đến
5 ngày bay, thiết lập tiền bạc và ngân hàng, các bước về sức khỏe và bảo hiểm, hậu cần nhà ở và khi mới đến,
6 thiết lập điện thoại và kỹ thuật số, kế hoạch đóng gói hành lý theo danh mục kèm giới hạn cân nặng, những việc cần đóng hoặc
7 tạm dừng ở quê nhà, lịch trình chi tiết trong ngày khởi hành, và danh sách kiểm tra cho tuần đầu tiên sau khi đến nơi.
8
9inputs:
10 home_country: ${home_country:Canada}
...+110 dòng nữa
TravelPlanningYAML+3
F@f
0
Tranh Khắc Gỗ Ukiyo-e Cảnh Đò Ngang Qua Sông Trong Mưa Thu
Hình ảnh
Tranh Khắc Gỗ Ukiyo-e Cảnh Đò Ngang Qua Sông Trong Mưa Thu

Tranh khắc gỗ ukiyo-e trên giấy màu be: một chiếc đò gỗ dài đang băng qua dòng sông êm đềm trong cơn mưa mùa thu, ba hành khách ngồi dưới những chiếc ô giấy màu đỏ, chàm và vàng đất, người lái đò đội nón rơm đang chống sào ở mũi sau, cây phong đỏ ở góc trên bên trái, ngọn núi màu xanh xám mờ sương phía xa, và những đường mưa chéo mảnh mai phủ lên toàn bộ khung cảnh.

Một bức tranh khắc gỗ ukiyo-e Nhật Bản vẽ cảnh chiếc đò gỗ nhỏ đang băng qua dòng sông rộng và êm đềm trong cơn mưa mùa thu, với bố cục ngang 16:9 rộng rãi trên nền giấy màu be ấm áp lộ rõ các sợi giấy và kết cấu vân gỗ. Một chiếc thuyền gỗ dài, thấp nằm ở phía trung tâm bên phải khung hình, chở ba hành khách đang ngồi dưới những chiếc ô giấy dầu tròn, từ trái sang phải gồm một chiếc màu đỏ chu sa, một chiếc màu xanh chàm đậm và một chiếc màu vàng đất; các hành khách mặc trang phục giản dị với các tông màu chàm, vàng đất và nâu gỉ sắt. Ở đuôi thuyền phía bên phải, người lái đò mặc áo choàng màu xanh xám nhạt, đầu đội chiếc nón rơm hình nón rộng vành, đang cắm chéo cây sào gỗ dài xuống mặt nước. Ở góc trên bên trái, một cây phong đỏ vươn cành vào khung hình với những chùm lá màu đỏ cam rực rỡ, phía dưới ở góc dưới bên trái là bờ sông gần nhiều đá và cỏ cùng những bụi lau sậy cao. Ở hậu cảnh, một ngọn núi hình núi lửa màu xanh xám mờ sương hiện lên phía sau bờ bên kia lờ mờ với bóng những hàng cây mềm mại ở bên phải. Những đường mưa chéo mảnh mai rơi khắp toàn bộ khung cảnh, mặt sông hiện lên những đường gợn sóng ngang nhẹ nhàng, và bầu trời có hiệu ứng chuyển màu bokashi mượt mà từ màu xám đá phiến đậm ở phía trên xuống màu be nhạt gần đường chân trời. Các mảng màu phẳng, đường viền đen mảnh, bảng màu chàm, vàng đất, đỏ chu sa và xám trầm mặc, mang lại tâm trạng tĩnh lặng và trầm tư. Không có chữ, không có con dấu, không có chữ ký, không có khung viền.
VisualArtCreative+2
F@f
0
Khung Cửa sổ Kính màu Hình Chim Cú Sừng Lớn Dưới Ánh Trăng Tròn
Hình ảnh
Khung Cửa sổ Kính màu Hình Chim Cú Sừng Lớn Dưới Ánh Trăng Tròn

Tấm kính màu (stained glass panel): một chú chim cú sừng lớn (great horned owl) với đôi mắt màu hổ phách và chùm lông tai đậu trên cành sồi ở chính giữa, mặt trăng tròn lớn màu vàng nhạt phía sau, nền trời bằng kính có vân màu xanh coban và tím violet điểm xuyết những ngôi sao nhỏ bằng kính vàng, lá sồi màu xanh ngọc lục bảo và xanh ô liu ở cả hai bên, các đường viền chì dày dặn, và khung viền lát gạch kính màu đỏ hồng ngọc (ruby).

Một tấm cửa sổ kính màu khắc họa hình ảnh một chú chim cú sừng lớn (great horned owl) đang đậu trên cành sồi dưới ánh trăng tròn, trong bố cục hình chữ nhật ngang rộng tỷ lệ 16:9. Chú cú đậu ở chính giữa tấm kính và hướng mặt về phía người xem, với chùm lông tai nhọn vươn cao, đôi mắt lớn rực sáng màu cam hổ phách, một mảng lông họng màu trắng, và bộ lông ghép từ các mảnh kính đốm màu nâu, nâu vàng (tan) và đen có vẽ thêm các chi tiết tinh xảo. Nó quắp chặt lấy một cành sồi lớn màu nâu chạy ngang qua một phần ba phía dưới của tấm kính từ trái sang phải, với bộ móng vuốt sẫm màu ôm quanh cành cây. Phía sau chú cú, một mặt trăng tròn lớn màu vàng nhạt với những vết hố trăng mờ dịu nằm hơi chếch về phía trên bên trái so với tâm. Bầu trời đêm được ghép từ những mảnh kính có vân không đều nhau mang sắc xanh coban và tím đậm với những bọt khí nhỏ li ti bên trong, rải rác khoảng một tá ngôi sao nhỏ bằng kính màu vàng kim. Từng chùm lá sồi có thùy bằng kính màu xanh ngọc lục bảo, xanh chanh và xanh ô liu tỏa ra từ cành cây ở cả hai phía trái và phải. Những đường viền chì dày sẫm màu ngăn cách từng mảnh kính, ánh sáng xuyên qua từ phía sau làm cho các sắc màu rực sáng giống như một khung cửa sổ thật sự, và toàn bộ tấm kính được bao quanh bởi một đường viền gồm những viên gạch kính vuông có vân màu đỏ hồng ngọc (ruby). Tông màu đá quý đậm đà, đối xứng, mang tính trang trí cao, bầu không khí đêm tĩnh lặng. Không có chữ, không có chữ ký.
VisualDesignArt+2
F@f
0
Ngôi nhà Trang trại bằng Đá ở Cuối Luống Hoa oải hương tại Provence lúc Bình minh
Hình ảnh
Ngôi nhà Trang trại bằng Đá ở Cuối Luống Hoa oải hương tại Provence lúc Bình minh

Phong cảnh bình minh chân thực (photoreal): những luống hoa oải hương màu tím trải dài hội tụ dọc theo một lối đi bằng đất ở trung tâm hướng về phía ngôi nhà trang trại bằng đá hai tầng có mái ngói đất nung và cửa chớp màu xanh lam nhạt ở một phần ba bên phải khung hình, một cây bách cao vút bên cạnh, một cây sồi đơn độc ở bên trái, mặt trời ở vị trí thấp phía ngoài cùng bên trái, và những ngọn đồi màu xanh lam nhấp nhô. Bước 2 trong quy trình Trang trại Hoa oải hương Provence.

Những luống hoa oải hương màu tím thẳng tắp trải dài dẫn lối về phía một ngôi nhà trang trại bằng đá hai tầng cổ kính tại Provence lúc bình minh, được nhìn từ một góc máy thấp ở cuối lối đi bằng đất nằm chính giữa, trong khung hình ngang rộng tỷ lệ 16:9. Các luống hoa oải hương cắt tỉa tròn trịa lấp đầy toàn bộ nửa dưới của bức ảnh và hội tụ về phía trung cảnh, xen giữa chúng là những lối đi hẹp bằng đất nâu và một lối đi chạy thẳng dọc theo chính giữa. Ngôi nhà trang trại đứng ở một phần ba bên phải khung hình: những bức tường đá thô màu be, mái ngói đất nung màu nâu, những ô cửa sổ nhỏ có cửa chớp màu xanh lam nhạt, cùng những bụi cây xanh phía trước nhà. Một cây bách đơn độc cao vút màu xanh lục sẫm vươn lên ngay sát bên phải ngôi nhà. Ở khoảng trung cảnh phía bên trái, một cây sồi tán tròn màu xanh lục đứng đơn độc trên một gò cỏ thoai thoải. Mặt trời ở vị trí thấp nằm ngay phía trên những ngọn đồi ở ngoài cùng bên trái, tỏa ánh vàng rực rỡ kèm hiệu ứng quầng sáng ống kính dịu nhẹ và làn sương sớm mỏng manh, phủ ánh sáng ấm áp lên khắp ngọn của những luống hoa oải hương. Những ngọn đồi màu xanh lam nhấp nhô trải dài khắp đường chân trời, và một vài đám mây trắng nhỏ trôi bồng bềnh trên bầu trời xanh nhạt chuyển dần sang sắc hồng đào ấm áp ở gần mặt trời. Nhiếp ảnh phong cảnh chân thực (photoreal), góc rộng, màu sắc tự nhiên, tâm trạng thanh bình. Không có người, không có chữ, không có logo.
TravelWorkflowPhotography+3
F@f
0
Bò Cao nguyên Chân thực trong Thung lũng Scotland Mờ sương lúc Bình minh
Hình ảnh
Bò Cao nguyên Chân thực trong Thung lũng Scotland Mờ sương lúc Bình minh

Cảnh chụp ống kính tele chân thực (photoreal) về một chú bò Cao nguyên (Highland cow) lông xù màu nâu đỏ gừng với cặp sừng dài cong vút hướng lên và phần tóc mái phủ qua mắt, đang đứng giữa đám cỏ vàng cao đẫm sương với hơi thở bốc khói trong không khí lạnh giá, ánh sáng ngược màu vàng kim chiếu từ góc trên bên phải, những ngọn đồi màu nâu và tím mờ sương, những bụi hoa thạch nam màu hồng và một hồ nước (loch) màu bạc nhạt phía sau.

Một bức ảnh chụp động vật hoang dã chân thực (photoreal) về một chú bò Cao nguyên (Highland cow) lông xù màu nâu đỏ gừng đang đứng giữa đám cỏ vàng cao trong một thung lũng Scotland mờ sương lúc bình minh, trong khung hình ngang rộng tỷ lệ 16:9. Chú bò đứng ở nửa bên trái khung hình và hướng mặt về phía máy quay, bộ lông dài màu cam gỉ sắt và đồng đỏ dày dặn, gợn sóng, với những giọt sương nhỏ li ti đọng trên lông. Một lớp lông mái dày rủ xuống che qua mắt, chiếc mũi và mõm rộng màu hồng nâu sắc nét và chi tiết, cùng cặp sừng dài màu nhạt uốn cong ra ngoài và vút lên từ đầu. Một làn hơi thở ấm áp hòa cùng sương mù bay sang phía bên phải đầu của nó trong bầu không khí lạnh giá buổi sớm mai. Ở tiền cảnh, đám cỏ vàng khô cao và những bông cỏ đẫm sương được làm mờ nhẹ ngoài vùng lấy nét. Ánh sáng ngược (backlight) màu vàng kim dịu nhẹ từ mặt trời ở vị trí thấp phía góc trên bên phải làm rực sáng đường viền bộ lông và phủ đầy thung lũng bằng một làn sương ấm áp. Ở hậu cảnh, những ngọn đồi nhấp nhô mờ ảo mang tông màu nâu trầm, tím hoa cà và tím sẫm dần tan vào làn sương, điểm xuyết những mảng hoa thạch nam màu hồng và một hồ nước (loch) ánh bạc nhạt ở khoảng trung cảnh phía bên phải. Độ sâu trường ảnh nông, hiệu ứng nén tiêu cự của ống kính tele 200mm, màu sắc tự nhiên, bầu không khí yên bình, tĩnh lặng. Không có người, không có hàng rào, không có chữ, không có hình mờ (watermark).
PhotographyVisualRealism+2
F@f
0
Previous16 / 19Next