
Health Plan
- 5 installs
- 8 repo stars
- Updated April 28, 2026
- vigorx777/health-plan
Helps with productivity & planning tasks.
About
health-plan is a Claude Code skill for productivity & planning. It helps solo builders move faster with AI-assisted development.
- health-plan
- Productivity & Planning
- AI-coding skill
Health Plan by the numbers
- 5 all-time installs (skills.sh)
- Ranked #2,303 of 3,282 Productivity & Planning skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/vigorx777/health-plan --skill health-planAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 5 |
|---|---|
| repo stars | ★ 8 |
| Last updated | April 28, 2026 |
| Repository | vigorx777/health-plan ↗ |
What it does
Helps with productivity & planning tasks.
Files
Health Plan
Use this skill to run a staged health-planning conversation from user-provided physiological data to analysis, optional 7-day training planning, OpenAI gpt-image-2-compatible image prompts, and optional daily image generation through the official gpt-image-2 API or the Codex built-in imagegen fallback.
This is a pure prompt-template skill. Do not run deterministic calculation scripts unless the user explicitly asks. Perform simple calculations and consistency checks directly in reasoning.
Required References
Read these files when using this skill:
references/evidence.mdfor stable reference ranges and training/nutrition anchors.references/safety.mdfor red flags, medical boundaries, and intensity downgrade rules.references/output-template.mdfor the required output structure.
If the user asks about latest medical guidelines, controversial claims, high-risk symptoms, drugs, disease treatment, or current tool recommendations, browse the web and cite authoritative sources.
Data Handling Workflow
1. Parse the uploaded data. Identify available fields, but do not produce the full analysis yet. Fields include: sex, age, height, weight, BMI, body score, body-fat percentage, fat mass, visceral fat level, muscle mass, muscle rate, skeletal muscle mass, fat-free mass, water mass/rate, protein mass/rate, bone mass/rate, basal metabolic rate, body age, waist-hip ratio, smart-scale heart rate, resting heart rate, HRV, and trend/baseline values.
2. Normalize units and calculate sanity checks. Calculate BMI if height and weight exist. Calculate fat mass, fat-free mass, and FFMI when possible. Compare calculated values with user-provided values. Mark inconsistent, blurry, impossible, or single-device uncertain values as 需复测/需确认. Keep these checks internal until the required user context is collected.
3. Check safety first. Apply references/safety.md. If red flags are present, immediately provide a short safety triage and avoid high-intensity planning until the issue is clarified.
4. Collect required context before analysis. Prefer Codex built-in parameter selection components for training goal, training experience, equipment/location, daily available time, injury or pain history, sleep status, and dietary restrictions. If built-in selection components are unavailable, fall back to concise Chinese text options. Do not output the body status report until these fields are collected, summarized, and confirmed, or until the user explicitly refuses to provide them.
5. Analyze and classify only after context collection. Choose one primary classification:
体态重组: normal BMI, moderate body fat, room to gain muscle or improve composition.减脂优先: elevated BMI/body fat/visceral fat or waist-risk indicators.增肌优先: low BMI, low muscle mass, or user goal is muscle gain.恢复优先: elevated resting heart rate, low HRV, poor sleep, pain, excessive fatigue, or recent illness.医学复核优先: red flags, abnormal repeated resting values, or symptoms needing clinician evaluation.
Conversation State Machine
Follow this order. Do not skip ahead.
1. 信息收集 After receiving physiological data, do not provide the full analysis immediately. First collect required training context. Prefer Codex built-in parameter selection components; use text options only as fallback:
- Training goal.
- Training experience.
- Equipment/location.
- Daily available time.
- Injury or pain history.
- Sleep status.
- Dietary restrictions.
Ask in small batches if needed. Keep each question concise and provide practical options. Only do immediate safety triage if the uploaded data contains red flags.
2. 上下文确认 After all seven fields are collected, show a concise training-context summary card and ask the user to confirm or correct it. Do not analyze until the user confirms the context.
3. 状态分析 Once required context is complete, generate the body status report and abnormal-data suggestions. Do not generate the 7-day plan yet.
4. 分析确认 Ask the user to confirm whether the analysis is accurate enough and invite them to enter planning in the same reply. If the user corrects data or context, revise the analysis first. If the user reply contains a clear forward intent such as 进入规划, 开始规划, 继续, 可以, 没问题, 确认, or equivalent, treat it as both analysis confirmation and planning authorization; go directly to 健身规划 without asking 规划询问.
5. 规划询问 Use this only when the user's analysis-confirmation reply is ambiguous and does not clearly authorize planning.
6. 健身规划 If the user agrees, generate the 7-day training plan. Do not display GPT-Imagine 2 prompts yet.
7. 规划确认 Ask the user to confirm whether the plan is acceptable. If the user requests changes, revise the plan before image generation.
8. 图片提示词或图片生成 After the plan is confirmed, create one gpt-image-2-compatible prompt per day. By default, display the seven prompts as reusable deliverables. If the user explicitly asks to generate images, resolve the image provider first: use openai_gpt_image_2 when OPENAI_API_KEY is configured and the API is available; otherwise fall back to Codex built-in imagegen. Submit Day 1 through Day 7 as seven independent image-generation jobs. The official API path must run jobs concurrently and return per-day status. The Codex built-in fallback may generate sequentially if the available tool path cannot parallelize. Do not merge the week into a weekly overview image unless requested. If the user provides a reference image, match its canvas size, aspect ratio, visual structure, and layout system across all generated daily images.
Image Provider Resolution
Resolve image generation in this order:
1. openai_gpt_image_2
- Use by default for actual image generation.
- Required environment:
OPENAI_API_KEY. - Default endpoint:
POST https://api.openai.com/v1/images/generations. - Default model:
gpt-image-2. - Default size:
1536x1024. - Default quality:
medium. - Default concurrency:
7. - Use
scripts/generate_daily_images.pywhen an API-backed batch run is appropriate.
2. codex_builtin_imagegen
- Use when
OPENAI_API_KEYis missing, the official API is unavailable, the user explicitly requests Codex preview generation, or the API script returnsfallback_required=codex_builtin_imagegen. - Follow the
imagegenskill and use the built-inimage_gentool. - Keep the same seven daily prompts, 1536x1024 layout target, and per-day job semantics.
- State that this is a fallback preview path and may generate sequentially.
Do not silently skip image generation if the official API prerequisites are missing. Switch to the Codex built-in imagegen fallback unless the user has explicitly forbidden fallback generation.
Image Generation Job Contract
When actual image generation is requested, use this behavior:
- Create exactly seven jobs:
day_1throughday_7. - With
openai_gpt_image_2, submit all seven jobs concurrently after plan confirmation. - With
codex_builtin_imagegen, keep seven independent jobs but generate sequentially if the built-in tool path cannot parallelize. - Track each job independently with states:
queued,generating,succeeded,failed,retrying. - Return per-day image references only after each job succeeds; do not expose prompts by default.
- If one or more jobs fail, retry only the failed day jobs. Do not regenerate successful days.
- Keep the UI status visible as:
生成中,成功,失败重试,失败. - Preserve deterministic mapping: Day N prompt must only produce Day N image.
- If the backend has a concurrency limit lower than seven, enqueue all seven jobs immediately and let the backend worker pool drain them without blocking the conversation on each individual image.
Planning Rules
- Default cycle: 7 days after analysis is confirmed and the user either explicitly opts into planning or uses a clear forward-intent phrase.
- Default image style: professional fitness poster.
- Default image-prompt output: 7 separate OpenAI gpt-image-2-compatible daily prompts, one for each day of the plan.
- Actual image generation: only after explicit user confirmation. Prefer the official
openai_gpt_image_2API path whenOPENAI_API_KEYis configured; otherwise follow theimagegenskill and generate one image per daily prompt through Codex built-in fallback. - Product backend generation: submit seven independent daily jobs concurrently by default through
gpt-image-2. Sequential generation is only a Codex built-in preview fallback when the available tool path cannot parallelize. - Image size/layout consistency: if the user uploads a reference image, use that reference as the required visual template for every daily image. Preserve the same aspect ratio, canvas dimensions when supported, header structure, panel grid, typography hierarchy, icon/card style, color system, and footer layout. If no reference image exists, default to a 1536x1024 landscape canvas with a 3:2 aspect ratio.
- Default daily poster structure: top header with large
DAY Nand training title; top summary cards for当天目标,核心动作,目标肌群; right-side or top-right cards for姿势要点,组数次数/RPE,安全提醒; three numbered exercise panels with consistent image composition and muscle insets; bottom area for action cues, training tip, and estimated duration. - Use RPE for intensity. New users usually stay at RPE 6-7; intermediate users can use RPE 7-8; avoid RPE 9-10 unless the user is experienced and no risk flags exist.
- Prefer sustainable plans over aggressive transformation claims.
- For beginners, avoid prescribing high-risk heavy barbell lifts as mandatory. Offer safer machine, dumbbell, cable, or bodyweight alternatives.
- Include warm-up, main training, accessory work, cardio, cooldown/stretching, nutrition focus, and recovery focus for each day.
- Match each daily image prompt to that day's actual training theme. Each prompt/image must include the day's goal, core exercises, target muscles, posture cues, sets/reps/RPE, and safety reminders. Do not invent unrealistic before/after effects.
- Keep visual structure consistent across Day 1-Day 7. Only change the day number, training title, exercise content, target muscles, cues, and duration.
Language Rules
- Use non-diagnostic language:
提示,可能,建议复测,建议咨询医生. - Do not say:
诊断为,治疗,治愈,保证,必然. - Clearly separate data-derived findings from user-provided context and any remaining uncertainties.
- When listing file names in the local workspace, show only file names, not full paths.
Evidence Anchors
Use these as stable, practical anchors. They support fitness guidance, not diagnosis. If a claim is medically high-stakes, controversial, or requires current guidelines, browse authoritative sources.
Body Composition
- Adult BMI categories commonly used by CDC/WHO-style references:
<18.5: underweight.18.5-24.9: healthy/normal range.25.0-29.9: overweight.>=30: obesity range.- BMI is a screening metric. Interpret with body fat, waist measurements, muscle mass, age, ethnicity, training status, and health history.
- Male body-fat interpretation for fitness planning:
~10-15%: lean/athletic for many men.~15-20%: healthy, moderate, often suitable for body recomposition.>20-25%: consider fat-loss emphasis depending on waist/visceral indicators.- Female body-fat ranges are normally higher; avoid applying male thresholds to female users.
- Smart-scale/BIA body composition is sensitive to hydration, food intake, exercise, alcohol, menstrual cycle, skin temperature, and device model. Treat trends as more reliable than one-off readings.
Derived Metrics
BMI = weight_kg / (height_m ^ 2).fat_mass_kg = weight_kg * body_fat_pct / 100.fat_free_mass_kg = weight_kg - fat_mass_kg.FFMI = fat_free_mass_kg / (height_m ^ 2).- Use derived values to check consistency, not to overrule clinical judgment.
Waist And Visceral Risk
- Waist-hip ratio reference commonly used for central adiposity risk:
- Men: risk tends to increase at
>=0.90. - Women: risk tends to increase at
>=0.85. - Waist-to-height ratio can be used when waist circumference exists:
<0.5is often treated as a practical low-risk target.- Visceral fat levels are device-specific. Interpret according to the device's reference labels if available; otherwise classify cautiously as trend data.
Heart Rate And HRV
- Typical adult resting heart rate is often cited around
60-100 bpm. - Resting heart rate below 60 can be normal in trained or endurance-active people if asymptomatic.
- Repeated true resting heart rate above 100 bpm, especially with symptoms, warrants medical review.
- A single elevated smart-scale heart-rate reading may reflect standing posture, recent activity, stress, caffeine, dehydration, fever, alcohol, poor sleep, or measurement error.
- HRV is highly individual. Prefer personal baseline and trend over population comparison.
- HRV falling materially below baseline for several days can suggest recovery stress; downgrade intensity.
Physical Activity And Training
- General adult baseline: at least 150 minutes/week moderate-intensity aerobic activity or 75 minutes/week vigorous activity, plus at least 2 days/week muscle-strengthening activity.
- For body recomposition, prioritize progressive resistance training 3-4 days/week plus 2-3 lower-intensity cardio sessions.
- Use RPE:
- RPE 5-6: easy to moderate, several reps in reserve.
- RPE 7: challenging but controlled, about 3 reps in reserve.
- RPE 8: hard, about 2 reps in reserve.
- RPE 9-10: very hard/maximal; avoid for beginners and recovery-risk users.
Nutrition
- Protein for active users: commonly
1.4-2.0 g/kg/day. - For muscle gain or body recomposition, a practical default is
1.6 g/kg/day, adjusted for user tolerance, kidney disease history, diet, and clinician advice. - Fat loss should usually use a modest energy deficit, not aggressive restriction.
- Muscle gain can use maintenance to small surplus calories, especially if the user is lean or under-muscled.
- Hydration, sleep, and meal consistency are part of the plan when HRV/recovery is a concern.
Output Template
Use these staged templates. Do not output all sections at once unless the conversation has already reached that stage.
Stage 1: 信息收集
After receiving physiological data, acknowledge receipt and collect missing context before analysis.
Prefer Codex built-in parameter selection components. If the UI component is unavailable, use Chinese text options. Ask in small batches, normally 2-3 items per message.
Required fields:
- 训练目标:体态重组 / 减脂优先 / 增肌优先 / 力量表现 / 恢复改善 / 其他。
- 训练经验:新手 / 规律训练 3-12 个月 / 规律训练 1-3 年 / 训练 3 年以上。
- 器械条件:健身房 / 居家无器械 / 哑铃弹力带 / 混合训练 / 其他。
- 每日可用时间:20-30 分钟 / 30-45 分钟 / 45-60 分钟 / 60-75 分钟。
- 伤病史:无明显伤病 / 膝踝不适 / 腰背不适 / 肩颈不适 / 心血管或医生限制 / 其他。
- 睡眠:稳定 7 小时以上 / 6-7 小时 / 少于 6 小时 / 作息不规律。
- 饮食限制:无 / 素食 / 乳糖不耐 / 控糖或低碳 / 外食为主 / 其他。
Example wording:
我已收到你的生理数据。先不直接分析,需要先补齐训练上下文,否则计划会不准确。
请先选择这 3 项:
1. 训练目标:体态重组 / 减脂优先 / 增肌优先 / 力量表现 / 恢复改善 / 其他
2. 训练经验:新手 / 规律训练 3-12 个月 / 规律训练 1-3 年 / 训练 3 年以上
3. 器械条件:健身房 / 居家无器械 / 哑铃弹力带 / 混合训练 / 其他Stage 2: 上下文确认
Only output this after all seven required fields are collected. Do not analyze yet.
Use this summary card:
训练上下文摘要
- 训练目标:...
- 训练经验:...
- 器械条件:...
- 每日可用时间:...
- 伤病史/疼痛限制:...
- 睡眠状态:...
- 饮食限制:...
请确认以上信息是否准确。确认后我再开始身体状态分析;如果有任何一项需要修改,请直接指出。Stage 3: 状态分析输出
Only output this after required context is complete.
0. 前置说明
State:
- This is fitness/health education, not medical diagnosis.
- Which physiological data and training-context fields are available.
- Which fields remain uncertain or need retesting.
1. 身体状态分析报告
总体结论
Give a concise classification:
体态重组减脂优先增肌优先恢复优先医学复核优先
Explain the top 3 reasons from the data.
体重体型
Discuss weight, height, BMI, standard/healthy range, and whether weight change should be a primary goal.
脂肪状态
Discuss body-fat percentage, fat mass, visceral fat, waist-hip ratio, and whether the user should reduce fat, maintain, or focus on composition.
肌肉状态
Discuss muscle mass, skeletal muscle mass, fat-free mass, FFMI if calculable, and training implications.
代谢与恢复
Discuss BMR, water/protein/bone indicators if provided, confirmed sleep/recovery context, and HRV trend if available.
心率与 HRV
Discuss resting heart rate, smart-scale heart rate, baseline, annual min/max if provided, and HRV. Clearly flag values that need repeat measurement.
风险与不确定项
List:
- inconsistent or blurry fields.
- device-dependent fields.
- missing fields that would improve personalization.
2. 异常数据与调整建议
Use this table:
| 优先级 | 指标 | 当前表现 | 可能原因 | 复测/确认方式 | 调整建议 | 何时就医 |
|---|
Rules:
- Include only relevant abnormal, borderline, uncertain, or high-impact items.
- If no clear abnormal data exists, say so and list optimization opportunities instead.
- Do not diagnose.
3. 分析确认
End Stage 3 by asking for confirmation:
请确认:以上分析是否符合你的实际情况?如果有训练经验、伤病、睡眠、饮食或身体状态需要修正,请直接指出;如果分析没问题,请回复“进入规划”,我会直接生成 7 天训练日程,不再重复确认。Stage 4: 规划询问
Use this stage only if the user's reply after Stage 3 is ambiguous. If the user says 进入规划, 开始规划, 继续, 可以, 没问题, 确认, or equivalent forward-intent wording, skip this stage and generate the 7-day plan directly.
我理解你已确认分析。是否现在进入 7 天健身规划?如果进入,我会基于刚才确认过的身体状态和训练条件生成每天的训练日程。Stage 5: 7 天健身规划
Before the table, state the confirmed context:
- Goal.
- Training level.
- Equipment/location.
- Available time.
- Injury constraints.
- Nutrition baseline.
Use this daily format:
Day [N]:[训练主题]
- 目标:...
- 热身:...
- 主训练:动作、组数、次数、RPE、休息时间。
- 辅助训练:动作、组数、次数、RPE、休息时间。
- 有氧:类型、时长、强度。
- 拉伸/恢复:...
- 营养重点:...
- 睡眠/恢复重点:...
- 调整规则:如果当天静息心率明显高于基线或 HRV 明显低于基线,则...
Use varied daily themes:
- Upper body push/pull.
- Lower body strength.
- Zone-2 cardio.
- Mobility/recovery.
- Full-body resistance.
- Core/posture.
- Rest or active recovery.
End Stage 5 by asking for confirmation:
请确认:这份 7 天规划是否可执行?如果需要调整训练天数、器械、时间、动作难度或恢复安排,我会先修改。确认没问题后,我会生成 7 条 OpenAI gpt-image-2 兼容的每日训练示意图提示词;如果你明确要实际出图,我会优先使用 OpenAI gpt-image-2 API 并发生成,缺少 API 配置时自动降级到 Codex 内置 imagegen。Stage 6: 图片提示词与可选出图
After the user confirms the plan:
- Generate exactly one OpenAI gpt-image-2-compatible prompt per day, for seven total prompts.
- Display the prompts by default as reusable deliverables.
- If the user explicitly asks to generate images, resolve the provider first.
- Default provider:
openai_gpt_image_2, usingPOST /v1/images/generations, modelgpt-image-2, size1536x1024, and seven concurrent day jobs. - Fallback provider:
codex_builtin_imagegen, using the Codex built-inimagegenskill andimage_gentool whenOPENAI_API_KEYis missing or the official API path is unavailable. - Generate Day 1 through Day 7 as seven independent daily images if actual generation is requested.
- In the official API path, submit all seven daily jobs concurrently. In Codex built-in fallback mode, use sequential generation only if the available image tool cannot parallelize.
- Do not combine the week into one overview image unless the user explicitly requests an extra overview image.
- If a day fails during actual image generation, retry only that failed day. Do not regenerate successful days.
- If the user provides a reference image, use it as the visual layout template for all seven daily images. Match its canvas size and aspect ratio when supported by the generation backend. The current default reference style is a 1536x1024 landscape poster with a 3:2 aspect ratio.
Provider status card:
图片生成通道
- 首选通道:OpenAI gpt-image-2 API
- 当前通道:OpenAI gpt-image-2 API / Codex 内置 imagegen 兜底
- 触发原因:用户确认实际出图
- 降级原因:无 / 缺少 OPENAI_API_KEY / 官方 API 不可用 / 用户指定 Codex 预览
- 并发策略:官方 API 并发 7 个任务;Codex 兜底按工具能力逐日生成Use one consistent layout system for every daily image:
- Top-left: large
DAY Nand training title. - Top summary row:
当天目标,核心动作,目标肌群. - Top-right or right-side info cards:
姿势要点,组数次数/RPE,安全提醒. - Main body: three numbered exercise panels. Use the day's three most important movements; for recovery/rest days, use three recovery actions or tracking actions.
- Muscle inset: include target-muscle diagrams or simplified highlighted muscle insets where appropriate.
- Bottom section: action cues, training tip, and estimated duration.
- Visual consistency: same typography hierarchy, navy/white color system, icon style, card borders, spacing, and panel proportions across Day 1-Day 7.
Each daily image must include:
- 当天目标.
- 核心动作.
- 目标肌群.
- 动作姿势要点.
- 组数次数/RPE.
- 安全提醒.
Parallel job status contract:
| Day | Job ID | Status | Image reference | Retry policy |
|---|---|---|---|---|
| Day 1 | day_1 | queued/generating/succeeded/failed/retrying | only after success | retry only Day 1 if failed |
| Day 2 | day_2 | queued/generating/succeeded/failed/retrying | only after success | retry only Day 2 if failed |
| Day 3 | day_3 | queued/generating/succeeded/failed/retrying | only after success | retry only Day 3 if failed |
| Day 4 | day_4 | queued/generating/succeeded/failed/retrying | only after success | retry only Day 4 if failed |
| Day 5 | day_5 | queued/generating/succeeded/failed/retrying | only after success | retry only Day 5 if failed |
| Day 6 | day_6 | queued/generating/succeeded/failed/retrying | only after success | retry only Day 6 if failed |
| Day 7 | day_7 | queued/generating/succeeded/failed/retrying | only after success | retry only Day 7 if failed |
User-facing result format:
训练示意图生成结果
- 当前通道:OpenAI gpt-image-2 API / Codex 内置 imagegen 兜底
- Day 1:成功,[图片]
- Day 2:生成中
- Day 3:失败重试中
- Day 4:成功,[图片]
- Day 5:成功,[图片]
- Day 6:成功,[图片]
- Day 7:成功,[图片]
失败项只会重试对应日期,不会重新生成已成功图片。Daily prompt structure:
生成一张 OpenAI gpt-image-2 兼容的专业健身海报,与参考训练海报同尺寸、同结构、同排版系统,默认 1536x1024 横向 3:2;[Day N 当天训练主题];顶部左侧使用大号 DAY N 和训练标题;顶部摘要区包含 当天目标[...], 核心动作[...], 目标肌群[...];右上信息卡包含 姿势要点[...], 组数次数/RPE[...], 安全提醒[...];主体为三个编号动作面板,分别展示[动作1], [动作2], [动作3],每个面板包含动作照片/示意、目标肌群小图、动作要点;底部包含训练小贴士和预计用时;统一海军蓝与白色配色、清晰中文标签、真实人体比例、居家/健身训练场景、高质感运动摄影与信息图结合;避免夸张肌肉、避免医疗诊断文字、避免危险姿势、避免前后对比承诺。
Rules:
- Keep prompts aligned with each day's actual plan.
- Mention safety cues when the day includes loaded movements.
- Do not include medical claims.
- Do not include unrealistic instant transformation language.
Stage 7: 追踪指标
Include a compact checklist after either the analysis or planning stage:
- 7 日平均体重.
- 腰围 or 腰臀比.
- 训练动作重量/次数.
- 静息心率.
- HRV vs baseline.
- 睡眠时长/质量.
- 主观疲劳 and soreness.
Safety Rules
This skill provides health and fitness education, not medical diagnosis, treatment, or emergency guidance.
Red Flags
If any of these appear, advise medical evaluation and avoid high-intensity training until cleared:
- Chest pain, chest pressure, fainting, unexplained shortness of breath, severe dizziness, or palpitations.
- Resting heart rate repeatedly
>100 bpmwhen calm, seated/lying, and not recently exercising. - Very irregular pulse, sudden unexplained exercise intolerance, or new severe fatigue.
- Blood pressure crisis symptoms if blood pressure is provided.
- Rapid unexplained weight loss or gain.
- Fever, acute infection, recent surgery, or recent significant injury.
- Severe joint pain, sharp pain during movement, neurological symptoms, or pain that worsens with training.
- Known cardiovascular, renal, endocrine, or metabolic disease without clinician clearance.
Measurement Cautions
- Treat smart-scale heart rate, BIA body fat, visceral fat level, body age, and single HRV readings as screening/trend data.
- Ask the user to repeat questionable readings under standardized conditions:
- Morning after bathroom use.
- Before food, caffeine, alcohol, or training.
- Similar hydration state.
- Same device and same posture.
- If wearable and scale data conflict, prefer repeated resting measurements over isolated readings.
Intensity Downgrade Rules
Downgrade to recovery-first or low/moderate intensity when:
- Resting heart rate is materially above baseline for 2-3 days.
- HRV is materially below baseline for 2-3 days.
- Sleep is poor for multiple nights.
- User reports illness, unusual fatigue, soreness that changes movement, or elevated stress.
- The user's training experience is unknown and the plan includes complex loaded movements.
Recovery-first substitutions:
- Replace intervals with zone-2 walking, cycling, or easy jogging.
- Replace heavy compounds with machines, bodyweight, controlled tempo, or mobility.
- Reduce total sets by 30-50%.
- Keep RPE at 5-6.
Prohibited Claims
Do not promise:
- Guaranteed fat loss, muscle gain, disease prevention, treatment, or cure.
- Diagnostic certainty from smart-scale/wearable data.
- A fixed amount of weight/fat change without uncertainty.
- Medical clearance.
Use safer wording:
该指标提示...建议复测确认...如果反复出现或伴随症状,建议咨询医生...从健身规划角度,可以优先...
#!/usr/bin/env python3
"""Generate health-plan daily training posters with OpenAI gpt-image-2.
The script is intentionally dependency-free so missing OpenAI SDK installs do
not block the official API path. It reports a Codex imagegen fallback signal
when OPENAI_API_KEY is unavailable.
"""
from __future__ import annotations
import argparse
import base64
import concurrent.futures
import json
import os
from pathlib import Path
import sys
import time
import urllib.error
import urllib.request
DEFAULT_BASE_URL = "https://api.openai.com/v1"
DEFAULT_MODEL = "gpt-image-2"
DEFAULT_SIZE = "1536x1024"
DEFAULT_QUALITY = "medium"
FALLBACK_PROVIDER = "codex_builtin_imagegen"
OFFICIAL_PROVIDER = "openai_gpt_image_2"
def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(
description="Generate Day 1-Day 7 health-plan images with OpenAI gpt-image-2."
)
parser.add_argument("--jobs", required=True, help="JSON string or path containing jobs.")
parser.add_argument("--out-dir", default="health-plan/generated_images", help="Output directory.")
parser.add_argument("--size", default=DEFAULT_SIZE, help="Image size, default 1536x1024.")
parser.add_argument("--quality", choices=["medium", "high"], default=DEFAULT_QUALITY)
parser.add_argument("--concurrency", type=int, default=7)
parser.add_argument("--dry-run", action="store_true")
parser.add_argument(
"--provider",
choices=["auto", OFFICIAL_PROVIDER, FALLBACK_PROVIDER],
default="auto",
)
parser.add_argument("--base-url", default=os.environ.get("OPENAI_BASE_URL", DEFAULT_BASE_URL))
parser.add_argument("--model", default=os.environ.get("OPENAI_IMAGE_MODEL", DEFAULT_MODEL))
parser.add_argument("--output-format", choices=["png", "jpeg", "webp"], default="png")
parser.add_argument("--timeout", type=int, default=300)
parser.add_argument("--retries", type=int, default=1)
parser.add_argument(
"--mock-success",
action="store_true",
help="Testing hook. Write placeholder files instead of calling the API.",
)
parser.add_argument(
"--mock-delay",
type=float,
default=0.0,
help="Testing hook. Sleep this many seconds per successful mock job.",
)
parser.add_argument(
"--simulate-fail-day",
action="append",
type=int,
default=[],
help="Testing hook. May be repeated.",
)
parser.add_argument(
"--allow-partial",
action="store_true",
help="Allow fewer than seven jobs for concurrency tests.",
)
return parser.parse_args()
def load_jobs(jobs_arg: str, allow_partial: bool) -> list[dict]:
source = Path(jobs_arg)
if source.exists():
raw = source.read_text(encoding="utf-8")
else:
raw = jobs_arg
try:
payload = json.loads(raw)
except json.JSONDecodeError as exc:
raise SystemExit(f"Invalid --jobs JSON: {exc}") from exc
if isinstance(payload, dict):
jobs = payload.get("jobs")
else:
jobs = payload
if not isinstance(jobs, list):
raise SystemExit("--jobs must be a list or an object with a jobs list.")
if not allow_partial and len(jobs) != 7:
raise SystemExit("Expected exactly 7 jobs. Use --allow-partial for test runs.")
if allow_partial and not (1 <= len(jobs) <= 7):
raise SystemExit("--allow-partial requires 1-7 jobs.")
normalized: list[dict] = []
seen_days: set[int] = set()
for index, job in enumerate(jobs, start=1):
if not isinstance(job, dict):
raise SystemExit(f"Job {index} must be an object.")
day = int(job.get("day", index))
if day in seen_days:
raise SystemExit(f"Duplicate day: {day}")
seen_days.add(day)
prompt = str(job.get("prompt", "")).strip()
if not prompt:
raise SystemExit(f"Job day_{day} is missing prompt.")
normalized.append(
{
"day": day,
"job_id": str(job.get("job_id", f"day_{day}")),
"prompt": prompt,
"output_filename": str(job.get("output_filename", f"day_{day}.png")),
}
)
return sorted(normalized, key=lambda item: item["day"])
def fallback_manifest(reason: str, jobs: list[dict]) -> dict:
return {
"provider": FALLBACK_PROVIDER,
"fallback_required": FALLBACK_PROVIDER,
"fallback_reason": reason,
"jobs": [
{
"day": job["day"],
"job_id": job["job_id"],
"status": "queued",
"image_reference": None,
"retry_policy": f"retry only {job['job_id']} if failed",
}
for job in jobs
],
}
def request_image(args: argparse.Namespace, api_key: str, job: dict, out_dir: Path) -> dict:
attempts = 0
last_error = None
max_attempts = max(1, args.retries + 1)
while attempts < max_attempts:
attempts += 1
try:
if job["day"] in args.simulate_fail_day:
raise RuntimeError(f"Simulated failure for day_{job['day']}")
return generate_once(args, api_key, job, out_dir, attempts)
except Exception as exc: # noqa: BLE001 - final manifest needs readable errors.
last_error = str(exc)
if attempts < max_attempts:
time.sleep(min(2**attempts, 8))
return {
"day": job["day"],
"job_id": job["job_id"],
"status": "failed",
"image_reference": None,
"attempts": attempts,
"error": last_error,
"retry_policy": f"retry only {job['job_id']}",
}
def generate_once(
args: argparse.Namespace, api_key: str, job: dict, out_dir: Path, attempts: int
) -> dict:
if args.mock_success:
if args.mock_delay > 0:
time.sleep(args.mock_delay)
output_path = out_dir / job["output_filename"]
output_path.write_text(
f"mock image for {job['job_id']} using {args.model}\n",
encoding="utf-8",
)
return {
"day": job["day"],
"job_id": job["job_id"],
"status": "succeeded",
"image_reference": str(output_path),
"attempts": attempts,
"retry_policy": f"retry only {job['job_id']} if failed",
}
endpoint = args.base_url.rstrip("/") + "/images/generations"
payload = {
"model": args.model,
"prompt": job["prompt"],
"size": args.size,
"quality": args.quality,
"n": 1,
"output_format": args.output_format,
}
body = json.dumps(payload).encode("utf-8")
request = urllib.request.Request(
endpoint,
data=body,
headers={
"Authorization": f"Bearer {api_key}",
"Content-Type": "application/json",
},
method="POST",
)
try:
with urllib.request.urlopen(request, timeout=args.timeout) as response:
data = json.loads(response.read().decode("utf-8"))
except urllib.error.HTTPError as exc:
detail = exc.read().decode("utf-8", errors="replace")
raise RuntimeError(f"OpenAI API HTTP {exc.code}: {detail}") from exc
image_items = data.get("data") or []
if not image_items:
raise RuntimeError("OpenAI API response contained no image data.")
image_item = image_items[0]
output_path = out_dir / job["output_filename"]
if image_item.get("b64_json"):
output_path.write_bytes(base64.b64decode(image_item["b64_json"]))
elif image_item.get("url"):
download_image(str(image_item["url"]), output_path, args.timeout)
else:
raise RuntimeError("OpenAI API image item contained neither b64_json nor url.")
return {
"day": job["day"],
"job_id": job["job_id"],
"status": "succeeded",
"image_reference": str(output_path),
"attempts": attempts,
"retry_policy": f"retry only {job['job_id']} if failed",
}
def download_image(url: str, output_path: Path, timeout: int) -> None:
with urllib.request.urlopen(url, timeout=timeout) as response:
output_path.write_bytes(response.read())
def dry_run_manifest(args: argparse.Namespace, jobs: list[dict]) -> dict:
return {
"provider": OFFICIAL_PROVIDER,
"dry_run": True,
"model": args.model,
"size": args.size,
"quality": args.quality,
"concurrency": args.concurrency,
"jobs": [
{
"day": job["day"],
"job_id": job["job_id"],
"status": "queued",
"image_reference": None,
"retry_policy": f"retry only {job['job_id']} if failed",
}
for job in jobs
],
}
def write_manifest(out_dir: Path, manifest: dict) -> None:
out_dir.mkdir(parents=True, exist_ok=True)
(out_dir / "manifest.json").write_text(
json.dumps(manifest, ensure_ascii=False, indent=2) + "\n",
encoding="utf-8",
)
def main() -> int:
args = parse_args()
jobs = load_jobs(args.jobs, args.allow_partial)
out_dir = Path(args.out_dir)
if args.provider == FALLBACK_PROVIDER:
manifest = fallback_manifest("provider explicitly set to codex_builtin_imagegen", jobs)
write_manifest(out_dir, manifest)
print(json.dumps(manifest, ensure_ascii=False, indent=2))
return 0
api_key = os.environ.get("OPENAI_API_KEY")
if not api_key:
manifest = fallback_manifest("missing OPENAI_API_KEY", jobs)
write_manifest(out_dir, manifest)
print(json.dumps(manifest, ensure_ascii=False, indent=2))
return 0
if args.dry_run:
manifest = dry_run_manifest(args, jobs)
write_manifest(out_dir, manifest)
print(json.dumps(manifest, ensure_ascii=False, indent=2))
return 0
out_dir.mkdir(parents=True, exist_ok=True)
worker_count = max(1, min(args.concurrency, len(jobs)))
with concurrent.futures.ThreadPoolExecutor(max_workers=worker_count) as executor:
futures = [executor.submit(request_image, args, api_key, job, out_dir) for job in jobs]
results = [future.result() for future in concurrent.futures.as_completed(futures)]
manifest = {
"provider": OFFICIAL_PROVIDER,
"model": args.model,
"size": args.size,
"quality": args.quality,
"concurrency": worker_count,
"jobs": sorted(results, key=lambda item: item["day"]),
}
write_manifest(out_dir, manifest)
print(json.dumps(manifest, ensure_ascii=False, indent=2))
return 0
if __name__ == "__main__":
sys.exit(main())