skill-calorie 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (373) hide show
  1. package/dist/analysis/anomaly/common.d.ts +53 -0
  2. package/dist/analysis/anomaly/common.d.ts.map +1 -0
  3. package/dist/analysis/anomaly/common.js +69 -0
  4. package/dist/analysis/anomaly/common.js.map +1 -0
  5. package/dist/analysis/anomaly/diet.d.ts +8 -0
  6. package/dist/analysis/anomaly/diet.d.ts.map +1 -0
  7. package/dist/analysis/anomaly/diet.js +95 -0
  8. package/dist/analysis/anomaly/diet.js.map +1 -0
  9. package/dist/analysis/anomaly/exercise.d.ts +9 -0
  10. package/dist/analysis/anomaly/exercise.d.ts.map +1 -0
  11. package/dist/analysis/anomaly/exercise.js +116 -0
  12. package/dist/analysis/anomaly/exercise.js.map +1 -0
  13. package/dist/analysis/anomaly/index.d.ts +19 -0
  14. package/dist/analysis/anomaly/index.d.ts.map +1 -0
  15. package/dist/analysis/anomaly/index.js +51 -0
  16. package/dist/analysis/anomaly/index.js.map +1 -0
  17. package/dist/analysis/anomaly/overall.d.ts +13 -0
  18. package/dist/analysis/anomaly/overall.d.ts.map +1 -0
  19. package/dist/analysis/anomaly/overall.js +128 -0
  20. package/dist/analysis/anomaly/overall.js.map +1 -0
  21. package/dist/analysis/anomaly/overall2.d.ts +5 -0
  22. package/dist/analysis/anomaly/overall2.d.ts.map +1 -0
  23. package/dist/analysis/anomaly/overall2.js +94 -0
  24. package/dist/analysis/anomaly/overall2.js.map +1 -0
  25. package/dist/analysis/anomaly/weight.d.ts +8 -0
  26. package/dist/analysis/anomaly/weight.d.ts.map +1 -0
  27. package/dist/analysis/anomaly/weight.js +106 -0
  28. package/dist/analysis/anomaly/weight.js.map +1 -0
  29. package/dist/analysis/anomaly/weight2.d.ts +4 -0
  30. package/dist/analysis/anomaly/weight2.d.ts.map +1 -0
  31. package/dist/analysis/anomaly/weight2.js +59 -0
  32. package/dist/analysis/anomaly/weight2.js.map +1 -0
  33. package/dist/analysis/contraindications.d.ts +63 -0
  34. package/dist/analysis/contraindications.d.ts.map +1 -0
  35. package/dist/analysis/contraindications.js +121 -0
  36. package/dist/analysis/contraindications.js.map +1 -0
  37. package/dist/analysis/cross.d.ts +75 -0
  38. package/dist/analysis/cross.d.ts.map +1 -0
  39. package/dist/analysis/cross.js +283 -0
  40. package/dist/analysis/cross.js.map +1 -0
  41. package/dist/analysis/dashboard.d.ts +18 -0
  42. package/dist/analysis/dashboard.d.ts.map +1 -0
  43. package/dist/analysis/dashboard.js +48 -0
  44. package/dist/analysis/dashboard.js.map +1 -0
  45. package/dist/analysis/deficit.d.ts +44 -0
  46. package/dist/analysis/deficit.d.ts.map +1 -0
  47. package/dist/analysis/deficit.js +54 -0
  48. package/dist/analysis/deficit.js.map +1 -0
  49. package/dist/analysis/diet.d.ts +76 -0
  50. package/dist/analysis/diet.d.ts.map +1 -0
  51. package/dist/analysis/diet.js +164 -0
  52. package/dist/analysis/diet.js.map +1 -0
  53. package/dist/analysis/exercise.d.ts +98 -0
  54. package/dist/analysis/exercise.d.ts.map +1 -0
  55. package/dist/analysis/exercise.js +298 -0
  56. package/dist/analysis/exercise.js.map +1 -0
  57. package/dist/analysis/exerciseReview.d.ts +36 -0
  58. package/dist/analysis/exerciseReview.d.ts.map +1 -0
  59. package/dist/analysis/exerciseReview.js +50 -0
  60. package/dist/analysis/exerciseReview.js.map +1 -0
  61. package/dist/analysis/precision.d.ts +15 -0
  62. package/dist/analysis/precision.d.ts.map +1 -0
  63. package/dist/analysis/precision.js +38 -0
  64. package/dist/analysis/precision.js.map +1 -0
  65. package/dist/analysis/result.d.ts +15 -0
  66. package/dist/analysis/result.d.ts.map +1 -0
  67. package/dist/analysis/result.js +13 -0
  68. package/dist/analysis/result.js.map +1 -0
  69. package/dist/analysis/review.d.ts +118 -0
  70. package/dist/analysis/review.d.ts.map +1 -0
  71. package/dist/analysis/review.js +195 -0
  72. package/dist/analysis/review.js.map +1 -0
  73. package/dist/analysis/series.d.ts +42 -0
  74. package/dist/analysis/series.d.ts.map +1 -0
  75. package/dist/analysis/series.js +127 -0
  76. package/dist/analysis/series.js.map +1 -0
  77. package/dist/analysis/simulate.d.ts +43 -0
  78. package/dist/analysis/simulate.d.ts.map +1 -0
  79. package/dist/analysis/simulate.js +109 -0
  80. package/dist/analysis/simulate.js.map +1 -0
  81. package/dist/analysis/simulate2.d.ts +65 -0
  82. package/dist/analysis/simulate2.d.ts.map +1 -0
  83. package/dist/analysis/simulate2.js +173 -0
  84. package/dist/analysis/simulate2.js.map +1 -0
  85. package/dist/analysis/trend.d.ts +39 -0
  86. package/dist/analysis/trend.d.ts.map +1 -0
  87. package/dist/analysis/trend.js +48 -0
  88. package/dist/analysis/trend.js.map +1 -0
  89. package/dist/analysis/utils.d.ts +17 -0
  90. package/dist/analysis/utils.d.ts.map +1 -0
  91. package/dist/analysis/utils.js +52 -0
  92. package/dist/analysis/utils.js.map +1 -0
  93. package/dist/analysis/volatility.d.ts +43 -0
  94. package/dist/analysis/volatility.d.ts.map +1 -0
  95. package/dist/analysis/volatility.js +96 -0
  96. package/dist/analysis/volatility.js.map +1 -0
  97. package/dist/analysis/weight.d.ts +86 -0
  98. package/dist/analysis/weight.d.ts.map +1 -0
  99. package/dist/analysis/weight.js +219 -0
  100. package/dist/analysis/weight.js.map +1 -0
  101. package/dist/analysis/weightCompare.d.ts +63 -0
  102. package/dist/analysis/weightCompare.d.ts.map +1 -0
  103. package/dist/analysis/weightCompare.js +212 -0
  104. package/dist/analysis/weightCompare.js.map +1 -0
  105. package/dist/analysis/weightCompare2.d.ts +63 -0
  106. package/dist/analysis/weightCompare2.d.ts.map +1 -0
  107. package/dist/analysis/weightCompare2.js +211 -0
  108. package/dist/analysis/weightCompare2.js.map +1 -0
  109. package/dist/analysis/weightCompare3.d.ts +13 -0
  110. package/dist/analysis/weightCompare3.d.ts.map +1 -0
  111. package/dist/analysis/weightCompare3.js +199 -0
  112. package/dist/analysis/weightCompare3.js.map +1 -0
  113. package/dist/cli/cmd_read.d.ts +8 -0
  114. package/dist/cli/cmd_read.d.ts.map +1 -0
  115. package/dist/cli/cmd_read.js +719 -0
  116. package/dist/cli/cmd_read.js.map +1 -0
  117. package/dist/cli/keys.d.ts +333 -0
  118. package/dist/cli/keys.d.ts.map +1 -0
  119. package/dist/cli/keys.js +103 -0
  120. package/dist/cli/keys.js.map +1 -0
  121. package/dist/cli/write.d.ts +24 -0
  122. package/dist/cli/write.d.ts.map +1 -0
  123. package/dist/cli/write.js +832 -0
  124. package/dist/cli/write.js.map +1 -0
  125. package/dist/fetch/audit.d.ts +25 -0
  126. package/dist/fetch/audit.d.ts.map +1 -0
  127. package/dist/fetch/audit.js +46 -0
  128. package/dist/fetch/audit.js.map +1 -0
  129. package/dist/fetch/batch.d.ts +68 -0
  130. package/dist/fetch/batch.d.ts.map +1 -0
  131. package/dist/fetch/batch.js +183 -0
  132. package/dist/fetch/batch.js.map +1 -0
  133. package/dist/fetch/body.d.ts +89 -0
  134. package/dist/fetch/body.d.ts.map +1 -0
  135. package/dist/fetch/body.js +217 -0
  136. package/dist/fetch/body.js.map +1 -0
  137. package/dist/fetch/cli.d.ts +3 -0
  138. package/dist/fetch/cli.d.ts.map +1 -0
  139. package/dist/fetch/cli.js +195 -0
  140. package/dist/fetch/cli.js.map +1 -0
  141. package/dist/fetch/cross-skill.d.ts +29 -0
  142. package/dist/fetch/cross-skill.d.ts.map +1 -0
  143. package/dist/fetch/cross-skill.js +42 -0
  144. package/dist/fetch/cross-skill.js.map +1 -0
  145. package/dist/fetch/db.d.ts +5 -0
  146. package/dist/fetch/db.d.ts.map +1 -0
  147. package/dist/fetch/db.js +4 -0
  148. package/dist/fetch/db.js.map +1 -0
  149. package/dist/fetch/diet.d.ts +174 -0
  150. package/dist/fetch/diet.d.ts.map +1 -0
  151. package/dist/fetch/diet.js +239 -0
  152. package/dist/fetch/diet.js.map +1 -0
  153. package/dist/fetch/errors.d.ts +5 -0
  154. package/dist/fetch/errors.d.ts.map +1 -0
  155. package/dist/fetch/errors.js +8 -0
  156. package/dist/fetch/errors.js.map +1 -0
  157. package/dist/fetch/exercise.d.ts +72 -0
  158. package/dist/fetch/exercise.d.ts.map +1 -0
  159. package/dist/fetch/exercise.js +270 -0
  160. package/dist/fetch/exercise.js.map +1 -0
  161. package/dist/fetch/goal.d.ts +48 -0
  162. package/dist/fetch/goal.d.ts.map +1 -0
  163. package/dist/fetch/goal.js +73 -0
  164. package/dist/fetch/goal.js.map +1 -0
  165. package/dist/fetch/goalHistory.d.ts +24 -0
  166. package/dist/fetch/goalHistory.d.ts.map +1 -0
  167. package/dist/fetch/goalHistory.js +51 -0
  168. package/dist/fetch/goalHistory.js.map +1 -0
  169. package/dist/fetch/history.d.ts +28 -0
  170. package/dist/fetch/history.d.ts.map +1 -0
  171. package/dist/fetch/history.js +29 -0
  172. package/dist/fetch/history.js.map +1 -0
  173. package/dist/fetch/index.d.ts +23 -0
  174. package/dist/fetch/index.d.ts.map +1 -0
  175. package/dist/fetch/index.js +16 -0
  176. package/dist/fetch/index.js.map +1 -0
  177. package/dist/fetch/nutritionGoal.d.ts +115 -0
  178. package/dist/fetch/nutritionGoal.d.ts.map +1 -0
  179. package/dist/fetch/nutritionGoal.js +154 -0
  180. package/dist/fetch/nutritionGoal.js.map +1 -0
  181. package/dist/fetch/photos.d.ts +76 -0
  182. package/dist/fetch/photos.d.ts.map +1 -0
  183. package/dist/fetch/photos.js +193 -0
  184. package/dist/fetch/photos.js.map +1 -0
  185. package/dist/fetch/plan.d.ts +145 -0
  186. package/dist/fetch/plan.d.ts.map +1 -0
  187. package/dist/fetch/plan.js +258 -0
  188. package/dist/fetch/plan.js.map +1 -0
  189. package/dist/fetch/products.d.ts +62 -0
  190. package/dist/fetch/products.d.ts.map +1 -0
  191. package/dist/fetch/products.js +73 -0
  192. package/dist/fetch/products.js.map +1 -0
  193. package/dist/fetch/profile.d.ts +46 -0
  194. package/dist/fetch/profile.d.ts.map +1 -0
  195. package/dist/fetch/profile.js +105 -0
  196. package/dist/fetch/profile.js.map +1 -0
  197. package/dist/fetch/shapes.d.ts +41 -0
  198. package/dist/fetch/shapes.d.ts.map +1 -0
  199. package/dist/fetch/shapes.js +38 -0
  200. package/dist/fetch/shapes.js.map +1 -0
  201. package/dist/fetch/validate.d.ts +11 -0
  202. package/dist/fetch/validate.d.ts.map +1 -0
  203. package/dist/fetch/validate.js +64 -0
  204. package/dist/fetch/validate.js.map +1 -0
  205. package/dist/fetch/weight.d.ts +109 -0
  206. package/dist/fetch/weight.d.ts.map +1 -0
  207. package/dist/fetch/weight.js +210 -0
  208. package/dist/fetch/weight.js.map +1 -0
  209. package/dist/fetch/xunji-catalog.d.ts +17 -0
  210. package/dist/fetch/xunji-catalog.d.ts.map +1 -0
  211. package/dist/fetch/xunji-catalog.js +57 -0
  212. package/dist/fetch/xunji-catalog.js.map +1 -0
  213. package/dist/index.d.ts +17 -0
  214. package/dist/index.d.ts.map +1 -0
  215. package/dist/index.js +12 -0
  216. package/dist/index.js.map +1 -0
  217. package/dist/kcal.d.ts +21 -0
  218. package/dist/kcal.d.ts.map +1 -0
  219. package/dist/kcal.js +27 -0
  220. package/dist/kcal.js.map +1 -0
  221. package/dist/migrate/migrate.d.ts +71 -0
  222. package/dist/migrate/migrate.d.ts.map +1 -0
  223. package/dist/migrate/migrate.js +450 -0
  224. package/dist/migrate/migrate.js.map +1 -0
  225. package/dist/paths.d.ts +6 -0
  226. package/dist/paths.d.ts.map +1 -0
  227. package/dist/paths.js +34 -0
  228. package/dist/paths.js.map +1 -0
  229. package/dist/render/analysisPlate.d.ts +49 -0
  230. package/dist/render/analysisPlate.d.ts.map +1 -0
  231. package/dist/render/analysisPlate.js +149 -0
  232. package/dist/render/analysisPlate.js.map +1 -0
  233. package/dist/render/bodyPlate.d.ts +55 -0
  234. package/dist/render/bodyPlate.d.ts.map +1 -0
  235. package/dist/render/bodyPlate.js +97 -0
  236. package/dist/render/bodyPlate.js.map +1 -0
  237. package/dist/render/diet.d.ts +47 -0
  238. package/dist/render/diet.d.ts.map +1 -0
  239. package/dist/render/diet.js +94 -0
  240. package/dist/render/diet.js.map +1 -0
  241. package/dist/render/envelope.d.ts +60 -0
  242. package/dist/render/envelope.d.ts.map +1 -0
  243. package/dist/render/envelope.js +111 -0
  244. package/dist/render/envelope.js.map +1 -0
  245. package/dist/render/errors.d.ts +10 -0
  246. package/dist/render/errors.d.ts.map +1 -0
  247. package/dist/render/errors.js +9 -0
  248. package/dist/render/errors.js.map +1 -0
  249. package/dist/render/exercise.d.ts +20 -0
  250. package/dist/render/exercise.d.ts.map +1 -0
  251. package/dist/render/exercise.js +32 -0
  252. package/dist/render/exercise.js.map +1 -0
  253. package/dist/render/goal.d.ts +24 -0
  254. package/dist/render/goal.d.ts.map +1 -0
  255. package/dist/render/goal.js +23 -0
  256. package/dist/render/goal.js.map +1 -0
  257. package/dist/render/goalExtra.d.ts +45 -0
  258. package/dist/render/goalExtra.d.ts.map +1 -0
  259. package/dist/render/goalExtra.js +94 -0
  260. package/dist/render/goalExtra.js.map +1 -0
  261. package/dist/render/goalPlate.d.ts +66 -0
  262. package/dist/render/goalPlate.d.ts.map +1 -0
  263. package/dist/render/goalPlate.js +110 -0
  264. package/dist/render/goalPlate.js.map +1 -0
  265. package/dist/render/health.d.ts +21 -0
  266. package/dist/render/health.d.ts.map +1 -0
  267. package/dist/render/health.js +41 -0
  268. package/dist/render/health.js.map +1 -0
  269. package/dist/render/help.d.ts +18 -0
  270. package/dist/render/help.d.ts.map +1 -0
  271. package/dist/render/help.js +58 -0
  272. package/dist/render/help.js.map +1 -0
  273. package/dist/render/home.d.ts +30 -0
  274. package/dist/render/home.d.ts.map +1 -0
  275. package/dist/render/home.js +73 -0
  276. package/dist/render/home.js.map +1 -0
  277. package/dist/render/html.d.ts +66 -0
  278. package/dist/render/html.d.ts.map +1 -0
  279. package/dist/render/html.js +510 -0
  280. package/dist/render/html.js.map +1 -0
  281. package/dist/render/index.d.ts +47 -0
  282. package/dist/render/index.d.ts.map +1 -0
  283. package/dist/render/index.js +26 -0
  284. package/dist/render/index.js.map +1 -0
  285. package/dist/render/insightPlate.d.ts +55 -0
  286. package/dist/render/insightPlate.d.ts.map +1 -0
  287. package/dist/render/insightPlate.js +97 -0
  288. package/dist/render/insightPlate.js.map +1 -0
  289. package/dist/render/library.d.ts +32 -0
  290. package/dist/render/library.d.ts.map +1 -0
  291. package/dist/render/library.js +54 -0
  292. package/dist/render/library.js.map +1 -0
  293. package/dist/render/photo.d.ts +101 -0
  294. package/dist/render/photo.d.ts.map +1 -0
  295. package/dist/render/photo.js +241 -0
  296. package/dist/render/photo.js.map +1 -0
  297. package/dist/render/planPlate.d.ts +40 -0
  298. package/dist/render/planPlate.d.ts.map +1 -0
  299. package/dist/render/planPlate.js +72 -0
  300. package/dist/render/planPlate.js.map +1 -0
  301. package/dist/render/profilePlate.d.ts +17 -0
  302. package/dist/render/profilePlate.d.ts.map +1 -0
  303. package/dist/render/profilePlate.js +18 -0
  304. package/dist/render/profilePlate.js.map +1 -0
  305. package/dist/render/ranking.d.ts +22 -0
  306. package/dist/render/ranking.d.ts.map +1 -0
  307. package/dist/render/ranking.js +63 -0
  308. package/dist/render/ranking.js.map +1 -0
  309. package/dist/render/receipt.d.ts +71 -0
  310. package/dist/render/receipt.d.ts.map +1 -0
  311. package/dist/render/receipt.js +65 -0
  312. package/dist/render/receipt.js.map +1 -0
  313. package/dist/render/weightPlate.d.ts +54 -0
  314. package/dist/render/weightPlate.d.ts.map +1 -0
  315. package/dist/render/weightPlate.js +110 -0
  316. package/dist/render/weightPlate.js.map +1 -0
  317. package/dist/schema.d.ts +25 -0
  318. package/dist/schema.d.ts.map +1 -0
  319. package/dist/schema.js +281 -0
  320. package/dist/schema.js.map +1 -0
  321. package/dist/triggers/help-lookup.d.ts +28 -0
  322. package/dist/triggers/help-lookup.d.ts.map +1 -0
  323. package/dist/triggers/help-lookup.js +163 -0
  324. package/dist/triggers/help-lookup.js.map +1 -0
  325. package/dist/triggers/index.d.ts +24 -0
  326. package/dist/triggers/index.d.ts.map +1 -0
  327. package/dist/triggers/index.js +78 -0
  328. package/dist/triggers/index.js.map +1 -0
  329. package/dist/triggers/scene-01-home.d.ts +4 -0
  330. package/dist/triggers/scene-01-home.d.ts.map +1 -0
  331. package/dist/triggers/scene-01-home.js +13 -0
  332. package/dist/triggers/scene-01-home.js.map +1 -0
  333. package/dist/triggers/scene-02-diet.d.ts +4 -0
  334. package/dist/triggers/scene-02-diet.d.ts.map +1 -0
  335. package/dist/triggers/scene-02-diet.js +74 -0
  336. package/dist/triggers/scene-02-diet.js.map +1 -0
  337. package/dist/triggers/scene-03-weight.d.ts +4 -0
  338. package/dist/triggers/scene-03-weight.d.ts.map +1 -0
  339. package/dist/triggers/scene-03-weight.js +62 -0
  340. package/dist/triggers/scene-03-weight.js.map +1 -0
  341. package/dist/triggers/scene-04-exercise.d.ts +4 -0
  342. package/dist/triggers/scene-04-exercise.d.ts.map +1 -0
  343. package/dist/triggers/scene-04-exercise.js +43 -0
  344. package/dist/triggers/scene-04-exercise.js.map +1 -0
  345. package/dist/triggers/scene-05-workout.d.ts +4 -0
  346. package/dist/triggers/scene-05-workout.d.ts.map +1 -0
  347. package/dist/triggers/scene-05-workout.js +36 -0
  348. package/dist/triggers/scene-05-workout.js.map +1 -0
  349. package/dist/triggers/scene-06-goal.d.ts +4 -0
  350. package/dist/triggers/scene-06-goal.d.ts.map +1 -0
  351. package/dist/triggers/scene-06-goal.js +29 -0
  352. package/dist/triggers/scene-06-goal.js.map +1 -0
  353. package/dist/triggers/scene-07-profile.d.ts +4 -0
  354. package/dist/triggers/scene-07-profile.d.ts.map +1 -0
  355. package/dist/triggers/scene-07-profile.js +8 -0
  356. package/dist/triggers/scene-07-profile.js.map +1 -0
  357. package/dist/triggers/scene-08-body.d.ts +4 -0
  358. package/dist/triggers/scene-08-body.d.ts.map +1 -0
  359. package/dist/triggers/scene-08-body.js +17 -0
  360. package/dist/triggers/scene-08-body.js.map +1 -0
  361. package/dist/triggers/scene-09-photo.d.ts +4 -0
  362. package/dist/triggers/scene-09-photo.d.ts.map +1 -0
  363. package/dist/triggers/scene-09-photo.js +14 -0
  364. package/dist/triggers/scene-09-photo.js.map +1 -0
  365. package/dist/triggers/scene-10-analysis.d.ts +4 -0
  366. package/dist/triggers/scene-10-analysis.d.ts.map +1 -0
  367. package/dist/triggers/scene-10-analysis.js +180 -0
  368. package/dist/triggers/scene-10-analysis.js.map +1 -0
  369. package/dist/triggers/types.d.ts +66 -0
  370. package/dist/triggers/types.d.ts.map +1 -0
  371. package/dist/triggers/types.js +3 -0
  372. package/dist/triggers/types.js.map +1 -0
  373. package/package.json +35 -0
@@ -0,0 +1,832 @@
1
+ import { WATER_NAME, MEALS, MEAL_WINDOWS, addMeal, updateMeal, deleteMeal, copyMeals, addMealsBatch, updateMealsByDate, deleteMealsByDate, deleteMealsByRange, deleteMealsByType, getDailySummary, } from '../fetch/diet.js';
2
+ import { logWeight, updateWeight, updateWeightByDate, deleteWeight, deleteWeightByDate, deleteWeightRange, batchLogWeight, } from '../fetch/weight.js';
3
+ import { addRecord, updateRecord, updateDay, deleteRecord, deleteDay, deleteRange, batchAdd, copyYesterday } from '../fetch/exercise.js';
4
+ import { addPhotos, deletePhoto, updateTag, tagAdd, tagRemove, getPhotoRow, resolvePhotosDir, daysSinceTagPhoto, parseTags, serializeTags, } from '../fetch/photos.js';
5
+ import { buildAddReceipt, buildDeleteReceipt, buildTagReceipt } from '../render/photo.js';
6
+ import { addProduct, updateProduct, deprecateProduct } from '../fetch/products.js';
7
+ import { setActivityLevel, updateProfile } from '../fetch/profile.js';
8
+ import { getNutritionGoal, setNutritionGoal, updateWaterGoal } from '../fetch/nutritionGoal.js';
9
+ import { pauseAllGoals, resumeAllGoals, setWeightGoal } from '../fetch/goal.js';
10
+ import { CALIPER_FIELDS, ValidationError, addComposition, addMeasurement, deleteComposition, deleteMeasurement, } from '../fetch/body.js';
11
+ import { SOURCE_CHOICES, SOURCE_LABELS } from '../kcal.js';
12
+ import { buildCrudReceipt } from '../render/receipt.js';
13
+ import { CalorieRenderError } from '../render/errors.js';
14
+ import { shiftISODate, todayISO } from '../analysis/utils.js';
15
+ import { isCalorieWriteKey } from './keys.js';
16
+ function fail(code, msg) {
17
+ console.error('ERR ' + code + ': ' + msg);
18
+ process.exit(code);
19
+ }
20
+ function needStr(params, name) {
21
+ const v = params[name];
22
+ if (typeof v !== 'string' || v.length === 0)
23
+ fail(2, '缺参数 ' + name);
24
+ return v;
25
+ }
26
+ function optStr(params, name) {
27
+ const v = params[name];
28
+ if (v === undefined || v === null)
29
+ return undefined;
30
+ if (typeof v !== 'string')
31
+ fail(2, '参数 ' + name + ' 须为字符串');
32
+ return v;
33
+ }
34
+ function needNum(params, name) {
35
+ const v = params[name];
36
+ if (typeof v !== 'number' || !Number.isFinite(v))
37
+ fail(2, '缺参数 ' + name + '(须为有限 number)');
38
+ return v;
39
+ }
40
+ function optNum(params, name) {
41
+ const v = params[name];
42
+ if (v === undefined || v === null)
43
+ return undefined;
44
+ if (typeof v !== 'number' || !Number.isFinite(v))
45
+ fail(2, '参数 ' + name + ' 须为有限 number');
46
+ return v;
47
+ }
48
+ function optInt(params, name) {
49
+ const v = optNum(params, name);
50
+ if (v === undefined)
51
+ return undefined;
52
+ if (!Number.isInteger(v))
53
+ fail(2, '参数 ' + name + ' 须为整数');
54
+ return v;
55
+ }
56
+ function needId(params, name = 'id') {
57
+ const v = params[name];
58
+ if (typeof v !== 'number' || !Number.isInteger(v) || v <= 0)
59
+ fail(2, '缺参数 ' + name + '(正整数记录 id)');
60
+ return v;
61
+ }
62
+ function needArr(params, name) {
63
+ const v = params[name];
64
+ if (!Array.isArray(v) || v.length === 0)
65
+ fail(2, '缺参数 ' + name + '(非空数组)');
66
+ return v;
67
+ }
68
+ function assertISO(v, field) {
69
+ if (!/^\d{4}-\d{2}-\d{2}$/.test(v))
70
+ fail(2, field + ' 非法(须 YYYY-MM-DD):' + v);
71
+ }
72
+ function esc(s) {
73
+ return String(s ?? '').replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
74
+ }
75
+ /** C6 #43 · 写收据 HTML 结构化分项:摘要 + 操作元 + items 逐条(id/日期/状态/原因/明细)。 */
76
+ function receiptHtml(scene, summary, op, recordId, items) {
77
+ const list = (items ?? []).length > 0
78
+ ? '<ul>' + (items ?? []).map((it) => '<li>#' + esc(it.id ?? '') + (it.date ? ' ' + esc(it.date) : '') + ' ' + esc(it.status) + (it.reason ? '(' + esc(it.reason) + ')' : '') + (it.detail ? ' · ' + esc(it.detail) : '') + '</li>').join('') + '</ul>'
79
+ : '';
80
+ return '<section class="ilife-page" data-skill="calorie" data-slot="ilife:calorie:receipt"><h1>' + esc(scene) + '</h1>' +
81
+ '<div class="ilife-item"><b>' + esc(summary) + '</b><div>op=' + esc(op) + (recordId === null ? '' : ' · id=' + esc(recordId)) + '</div>' + list + '</div></section>';
82
+ }
83
+ function out(receipt) {
84
+ return {
85
+ data: { ok: true, message: receipt.summary, receipt },
86
+ html: receiptHtml(receipt.scene, receipt.summary, receipt.op, receipt.recordId, receipt.items),
87
+ };
88
+ }
89
+ const R = (scene, op, summary, wakeWord, source, extra) => buildCrudReceipt({
90
+ scene, action: scene, op, recordId: null, summary, items: [], wakeWord, source,
91
+ ...(extra ?? {}),
92
+ });
93
+ function photosDirOf(params) {
94
+ return resolvePhotosDir(optStr(params, 'photosDir') ?? null);
95
+ }
96
+ const SOURCE_ALIASES = { '家测皮褶钳': 'home_caliper', '医院测': 'hospital', '健身房测': 'gym', '健身房': 'gym', '医院': 'hospital', '皮褶钳': 'home_caliper' };
97
+ function normSource(v) {
98
+ const s = String(v ?? '').trim();
99
+ if (SOURCE_CHOICES.includes(s))
100
+ return s;
101
+ const hit = SOURCE_ALIASES[s];
102
+ if (hit)
103
+ return hit;
104
+ fail(2, '缺参数 source(' + SOURCE_CHOICES.join('/') + ' 或中文 ' + Object.keys(SOURCE_ALIASES).join('/') + ')');
105
+ throw new Error('unreachable');
106
+ }
107
+ function normSex(v) {
108
+ if (v === undefined || v === null)
109
+ return undefined;
110
+ const s = String(v).trim();
111
+ if (s === 'male' || s === 'female')
112
+ return s;
113
+ if (s === '男')
114
+ return 'male';
115
+ if (s === '女')
116
+ return 'female';
117
+ fail(2, '参数 sex 非法(male/female 或 男/女):' + s);
118
+ throw new Error('unreachable');
119
+ }
120
+ const MEASURE_CAMEL = {
121
+ chestCm: 'chest_cm', waistCm: 'waist_cm', abdomenCm: 'abdomen_cm', hipCm: 'hip_cm', shoulderCm: 'shoulder_cm',
122
+ leftThighCm: 'left_thigh_cm', rightThighCm: 'right_thigh_cm', leftCalfCm: 'left_calf_cm', rightCalfCm: 'right_calf_cm',
123
+ leftArmCm: 'left_arm_cm', rightArmCm: 'right_arm_cm', leftForearmCm: 'left_forearm_cm', rightForearmCm: 'right_forearm_cm',
124
+ };
125
+ const EX_CAMEL = {
126
+ type: 'exercise_type', exerciseType: 'exercise_type', calories: 'calories_burned', caloriesBurned: 'calories_burned',
127
+ minutes: 'duration_minutes', durationMinutes: 'duration_minutes', note: 'note', category: 'category', difficulty: 'difficulty',
128
+ distance: 'distance_km', distanceKm: 'distance_km', heartRate: 'avg_heart_rate', avgHeartRate: 'avg_heart_rate',
129
+ maxHeartRate: 'max_heart_rate', steps: 'steps', reps: 'reps', loadKg: 'load_kg', setIndex: 'set_index',
130
+ date: 'date', time: 'time', backfill: 'is_backfill', isBackfill: 'is_backfill',
131
+ };
132
+ function oneExercise(item, i) {
133
+ const g = (k) => item[k];
134
+ const type = g('type') ?? g('exerciseType');
135
+ if (typeof type !== 'string' || !type.trim())
136
+ fail(2, '运动 type 必填' + i);
137
+ const cal = g('calories') ?? g('caloriesBurned');
138
+ if (typeof cal !== 'number' || !Number.isFinite(cal) || cal < 0)
139
+ fail(2, '运动 calories 必填(≥0 number)' + i);
140
+ const date = g('date') ?? todayISO();
141
+ assertISO(date, 'date');
142
+ const numOrNull = (k) => {
143
+ const v = g(k);
144
+ if (v === undefined || v === null)
145
+ return undefined;
146
+ if (typeof v !== 'number' || !Number.isFinite(v))
147
+ fail(2, '运动参数 ' + k + ' 须为 number' + i);
148
+ return v;
149
+ };
150
+ const strOrUndef = (k) => {
151
+ const v = g(k);
152
+ if (v === undefined || v === null)
153
+ return undefined;
154
+ if (typeof v !== 'string')
155
+ fail(2, '运动参数 ' + k + ' 须为字符串' + i);
156
+ return v;
157
+ };
158
+ return {
159
+ date, exerciseType: type.trim(), caloriesBurned: cal,
160
+ minutes: numOrNull('minutes') ?? null, timeStr: strOrUndef('time'), note: strOrUndef('note'),
161
+ reps: numOrNull('reps') ?? null, category: strOrUndef('category') ?? null,
162
+ difficulty: strOrUndef('difficulty') ?? null, distance: numOrNull('distance') ?? numOrNull('distanceKm') ?? null,
163
+ heartRate: numOrNull('heartRate') ?? numOrNull('avgHeartRate') ?? null,
164
+ maxHeartRate: numOrNull('maxHeartRate') ?? null, steps: numOrNull('steps') ?? null,
165
+ setIndex: numOrNull('setIndex') ?? null, loadKg: numOrNull('loadKg') ?? null,
166
+ isBackfill: (g('backfill') ?? g('isBackfill')) === true,
167
+ };
168
+ }
169
+ /** 写分发(唯一出口 cmd_read 内调用;未知键上游已拦,此处再拦一道)。 */
170
+ export function dispatchWrite(key, params, db) {
171
+ if (!isCalorieWriteKey(key))
172
+ fail(3, '未知 calorie 写键:' + key);
173
+ try {
174
+ return dispatchInner(key, params, db);
175
+ }
176
+ catch (e) {
177
+ if (e instanceof ValidationError)
178
+ throw new CalorieRenderError('bad-input', e.message);
179
+ throw e;
180
+ }
181
+ }
182
+ function dispatchInner(key, params, db) {
183
+ switch (key) {
184
+ case 'calorie.diet.add': {
185
+ const foodName = (optStr(params, 'foodName') ?? optStr(params, 'food_name') ?? '');
186
+ if (!foodName.trim())
187
+ fail(2, '缺参数 foodName');
188
+ const calories = needNum(params, 'calories');
189
+ const protein = needNum(params, 'protein');
190
+ for (const [k, v] of [['calories', calories], ['protein', protein]]) {
191
+ if (v < 0)
192
+ fail(2, k + ' 不能为负');
193
+ }
194
+ for (const k of ['carbs', 'fat']) {
195
+ const v = optNum(params, k);
196
+ if (v !== undefined && v < 0)
197
+ fail(2, k + ' 不能为负');
198
+ }
199
+ const grams0 = optNum(params, 'grams');
200
+ if (grams0 !== undefined && grams0 <= 0)
201
+ fail(2, 'grams 必须为正');
202
+ const date = optStr(params, 'date');
203
+ if (date)
204
+ assertISO(date, 'date');
205
+ const meal = optStr(params, 'meal') ?? optStr(params, 'mealOverride');
206
+ if (meal !== undefined && !MEALS.includes(meal))
207
+ fail(2, '--meal 须为 ' + MEALS.join('、'));
208
+ const r = addMeal(db, {
209
+ foodName: foodName.trim(), calories, protein,
210
+ carbs: optNum(params, 'carbs') ?? 0, fat: optNum(params, 'fat') ?? 0,
211
+ grams: optNum(params, 'grams') ?? 100, note: optStr(params, 'note'),
212
+ date, time: optStr(params, 'time'), mealOverride: meal,
213
+ });
214
+ if (r.duplicate) {
215
+ return out(R('记一餐', 'create', String(r.message ?? '重复记录已跳过'), '记一餐', 'food_log (写库回执)', {
216
+ recordId: r.dupId ?? null, noChange: true,
217
+ }));
218
+ }
219
+ const remain = r.remainingCal === null || r.remainingCal === undefined ? '' : ' · 今日剩 ' + r.remainingCal + ' 卡';
220
+ return out(R('记一餐', 'create', '已记一餐:' + r.food_name + ' ' + r.date + ' ' + r.time + '(' + r.meal + ')' + remain, '记一餐', 'food_log (写库回执)', {
221
+ recordId: r.id, items: [{ id: r.id ?? undefined, date: r.date, status: '成功', reason: '', detail: r.food_name }],
222
+ }));
223
+ }
224
+ case 'calorie.diet.update': {
225
+ const id = needId(params);
226
+ const fields = {};
227
+ const name = optStr(params, 'foodName') ?? optStr(params, 'food_name');
228
+ if (name !== undefined)
229
+ fields['food_name'] = name;
230
+ for (const k of ['grams', 'calories', 'protein', 'carbs', 'fat']) {
231
+ const v = optNum(params, k);
232
+ if (v !== undefined)
233
+ fields[k] = v;
234
+ }
235
+ for (const k of ['note', 'date', 'time']) {
236
+ const v = optStr(params, k);
237
+ if (v !== undefined)
238
+ fields[k] = v;
239
+ }
240
+ if (Object.keys(fields).length === 0)
241
+ fail(2, '至少传 1 个待改字段');
242
+ if (typeof fields['date'] === 'string')
243
+ assertISO(fields['date'], 'date');
244
+ const r = updateMeal(db, id, fields);
245
+ return out(R('改饮食记录', 'update', '已更新饮食 #' + id + '(' + (r.changed.length ? r.changed.join('、') : '无实际变化') + ')', '改饮食记录', 'food_log (写库回执)', {
246
+ recordId: id, noChange: r.changed.length === 0,
247
+ items: [{ id, status: '已更新', reason: '', detail: r.changed.join(',') || '无变化' }],
248
+ }));
249
+ }
250
+ case 'calorie.diet.remove': {
251
+ const id = needId(params);
252
+ const r = deleteMeal(db, id);
253
+ return out(R('删饮食记录', 'delete', '已删除饮食 #' + id + '(' + r.food_name + ' ' + r.calories + ' 卡)', '删饮食记录', 'food_log (写库回执)', {
254
+ recordId: id, items: [{ id, status: '已删除', reason: '', detail: r.food_name }],
255
+ }));
256
+ }
257
+ case 'calorie.diet.batch': {
258
+ const items = needArr(params, 'items');
259
+ if (items.length > 200)
260
+ fail(2, 'items 至多 200 条');
261
+ const r = addMealsBatch(db, items.map((e) => {
262
+ const o = (e ?? {});
263
+ const food = o['foodName'] ?? o['food_name'];
264
+ return {
265
+ date: o['date'] === undefined ? undefined : String(o['date']),
266
+ time: o['time'] === undefined ? undefined : String(o['time']),
267
+ food_name: food === undefined ? undefined : String(food),
268
+ grams: o['grams'], calories: o['calories'],
269
+ protein: o['protein'], carbs: o['carbs'],
270
+ fat: o['fat'], note: o['note'] === undefined ? undefined : String(o['note']),
271
+ };
272
+ }));
273
+ return out(R('批量补记饮食', 'create', '批量记饮食:新增 ' + r.added + ',跳过 ' + r.skipped + ',失败 ' + r.failed, '批量补记饮食', 'food_log (写库回执)', {
274
+ noChange: r.added === 0,
275
+ items: r.failures.slice(0, 20).map(([idx, reason]) => ({ status: '失败', reason, detail: '第' + idx + '条' })),
276
+ }));
277
+ }
278
+ case 'calorie.diet.copy': {
279
+ const today = todayISO();
280
+ const from = optStr(params, 'from') ?? optStr(params, 'fromDate') ?? shiftISODate(today, -1);
281
+ const to = optStr(params, 'to') ?? optStr(params, 'toDate') ?? today;
282
+ assertISO(from, 'from');
283
+ assertISO(to, 'to');
284
+ const r = copyMeals(db, from, to);
285
+ if (r.copied + r.skipped === 0)
286
+ throw new CalorieRenderError('missing-data', '来源无饮食记录(' + from + ')');
287
+ return out(R('复制昨日饮食', 'create', '已复制饮食 ' + from + '→' + to + ':复制 ' + r.copied + ',跳过 ' + r.skipped, '复制昨日饮食', 'food_log (写库回执)', {
288
+ noChange: r.copied === 0,
289
+ }));
290
+ }
291
+ case 'calorie.diet.update-by-date': {
292
+ const date = needStr(params, 'date');
293
+ assertISO(date, 'date');
294
+ const fields = {};
295
+ const name = optStr(params, 'foodName') ?? optStr(params, 'food_name');
296
+ if (name !== undefined)
297
+ fields['food_name'] = name;
298
+ for (const k of ['grams', 'calories', 'protein', 'carbs', 'fat', 'note', 'time']) {
299
+ const v = k === 'note' || k === 'time' ? optStr(params, k) : optNum(params, k);
300
+ if (v !== undefined)
301
+ fields[k] = v;
302
+ }
303
+ if (Object.keys(fields).length === 0)
304
+ fail(2, '至少传 1 个待改字段');
305
+ const r = updateMealsByDate(db, date, fields);
306
+ if (r.matched === 0)
307
+ throw new CalorieRenderError('missing-data', '无饮食记录(' + date + ')');
308
+ return out(R('改某日饮食', 'update', '已更新 ' + date + ' 饮食 ' + r.updated + ' 条(' + r.changedFields.join('、') + ')', '改某日饮食', 'food_log (写库回执)', {}));
309
+ }
310
+ case 'calorie.diet.remove-by-date': {
311
+ const date = needStr(params, 'date');
312
+ assertISO(date, 'date');
313
+ const r = deleteMealsByDate(db, date);
314
+ if (r.deleted === 0)
315
+ throw new CalorieRenderError('missing-data', '无饮食记录(' + date + ')');
316
+ return out(R('删某日饮食', 'delete', '已删除 ' + date + ' 饮食 ' + r.deleted + ' 条', '删某日饮食', 'food_log (写库回执)', {}));
317
+ }
318
+ case 'calorie.diet.remove-by-range': {
319
+ const start = needStr(params, 'start');
320
+ const end = needStr(params, 'end');
321
+ assertISO(start, 'start');
322
+ assertISO(end, 'end');
323
+ if (start > end)
324
+ fail(2, 'start 不得晚于 end');
325
+ const r = deleteMealsByRange(db, start, end);
326
+ if (r.deleted === 0)
327
+ throw new CalorieRenderError('missing-data', '无饮食记录(' + start + '~' + end + ')');
328
+ return out(R('批量删饮食', 'delete', '已删除 ' + start + '~' + end + ' 饮食 ' + r.deleted + ' 条', '批量删饮食', 'food_log (写库回执)', {}));
329
+ }
330
+ case 'calorie.diet.remove-by-type': {
331
+ const date = needStr(params, 'date');
332
+ assertISO(date, 'date');
333
+ const mealType = needStr(params, 'mealType');
334
+ if (!Object.prototype.hasOwnProperty.call(MEAL_WINDOWS, mealType))
335
+ fail(2, 'mealType 须为 ' + Object.keys(MEAL_WINDOWS).join('/') + ':' + mealType);
336
+ const r = deleteMealsByType(db, date, mealType);
337
+ if (r.deleted === 0)
338
+ throw new CalorieRenderError('missing-data', date + ' 无' + mealType + '记录');
339
+ return out(R('删一餐', 'delete', '已删除 ' + date + ' ' + mealType + ' ' + r.deleted + ' 条', '删一餐', 'food_log (写库回执)', {}));
340
+ }
341
+ case 'calorie.water.log': {
342
+ const ml = needNum(params, 'ml');
343
+ if (!(ml > 0) || ml > 10000)
344
+ fail(2, 'ml 须为 0..10000 毫升');
345
+ const date = optStr(params, 'date');
346
+ if (date)
347
+ assertISO(date, 'date');
348
+ const r = addMeal(db, {
349
+ foodName: WATER_NAME, calories: 0, protein: 0, grams: ml,
350
+ note: optStr(params, 'note'), date, time: optStr(params, 'time'),
351
+ });
352
+ if (r.duplicate) {
353
+ return out(R('记喝水', 'create', String(r.message ?? '重复记录已跳过'), '记喝水', 'food_log (写库回执)', { noChange: true }));
354
+ }
355
+ const day = date ?? todayISO();
356
+ const sum = getDailySummary(db, day);
357
+ return out(R('记喝水', 'create', '已记喝水 ' + ml + ' ml(' + day + ' 累计 ' + sum.waterMl + ' ml)', '记喝水', 'food_log (写库回执)', {
358
+ recordId: r.id,
359
+ items: [{ id: r.id ?? undefined, date: r.date, status: '成功', reason: '', detail: ml + 'ml' }],
360
+ }));
361
+ }
362
+ case 'calorie.weight.log': {
363
+ const kg = needNum(params, 'kg');
364
+ if (!(kg > 0) || kg > 500)
365
+ fail(2, 'kg 须为 0..500');
366
+ const date = optStr(params, 'date');
367
+ if (date)
368
+ assertISO(date, 'date');
369
+ const r = logWeight(db, kg, optStr(params, 'note') ?? '', date, optStr(params, 'time'));
370
+ const bmiText = r.bmi === null ? 'BMI 待补身高(补档案:calorie-cmd-read calorie.profile.set)' : 'BMI ' + r.bmi;
371
+ return out(R('记体重', 'create', '已记体重 ' + r.kg + ' kg(' + bmiText + ' · ' + r.date + ' ' + r.time + ')', '记体重', 'weight_log (写库回执)', {
372
+ recordId: r.id, items: [{ id: r.id, date: r.date, status: '成功', reason: '', detail: r.kg + 'kg' }],
373
+ }));
374
+ }
375
+ case 'calorie.weight.update': {
376
+ const id = optNum(params, 'id');
377
+ const date = optStr(params, 'date');
378
+ const kg = optNum(params, 'kg');
379
+ const note = optStr(params, 'note');
380
+ if (kg !== undefined && (!(kg > 0) || kg > 500))
381
+ fail(2, 'kg 须为 0..500');
382
+ if (id !== undefined) {
383
+ if (!Number.isInteger(id) || id <= 0)
384
+ fail(2, 'id 须为正整数');
385
+ const r = updateWeight(db, id, kg, note);
386
+ const bmiTextU = r.bmi === null ? 'BMI 待补身高(补档案:calorie-cmd-read calorie.profile.set)' : 'BMI ' + r.bmi;
387
+ return out(R('改体重记录', 'update', '已更新体重 #' + id + ':' + r.oldWeight + '→' + r.newWeight + ' kg(' + bmiTextU + ')', '改体重记录', 'weight_log (写库回执)', {
388
+ recordId: id, items: [{ id, status: '已更新', reason: '' }],
389
+ }));
390
+ }
391
+ if (date !== undefined) {
392
+ assertISO(date, 'date');
393
+ const r = updateWeightByDate(db, date, kg, note);
394
+ return out(R('改某日体重', 'update', '已更新 ' + date + ' 体重 ' + r.hitCount + ' 条', '改某日体重', 'weight_log (写库回执)', {}));
395
+ }
396
+ fail(2, '缺参数 id 或 date(二选一)');
397
+ throw new Error('unreachable');
398
+ }
399
+ case 'calorie.weight.remove': {
400
+ const id = optNum(params, 'id');
401
+ const date = optStr(params, 'date');
402
+ const start = optStr(params, 'start');
403
+ const end = optStr(params, 'end');
404
+ if (id !== undefined) {
405
+ if (!Number.isInteger(id) || id <= 0)
406
+ fail(2, 'id 须为正整数');
407
+ const r = deleteWeight(db, id);
408
+ return out(R('删体重记录', 'delete', '已删除体重 #' + id + '(' + r.date + ' ' + r.weight_kg + ' kg)', '删体重记录', 'weight_log (写库回执)', {
409
+ recordId: id, items: [{ id, status: '已删除', reason: '' }],
410
+ }));
411
+ }
412
+ if (date !== undefined) {
413
+ assertISO(date, 'date');
414
+ const r = deleteWeightByDate(db, date);
415
+ return out(R('删某日体重', 'delete', '已删除 ' + date + ' 体重 ' + r.deletedCount + ' 条', '删某日体重', 'weight_log (写库回执)', {}));
416
+ }
417
+ if (start !== undefined || end !== undefined) {
418
+ if (start === undefined || end === undefined)
419
+ fail(2, '按范围删须同时传 start/end');
420
+ assertISO(start, 'start');
421
+ assertISO(end, 'end');
422
+ if (start > end)
423
+ fail(2, 'start 不得晚于 end');
424
+ const r = deleteWeightRange(db, start, end);
425
+ return out(R('批量删体重', 'delete', '已删除 ' + start + '~' + end + ' 体重 ' + r.deletedCount + ' 条', '批量删体重', 'weight_log (写库回执)', {}));
426
+ }
427
+ fail(2, '缺参数 id/date/start+end(三选一)');
428
+ throw new Error('unreachable');
429
+ }
430
+ case 'calorie.weight.batch': {
431
+ const items = needArr(params, 'items');
432
+ if (items.length > 365)
433
+ fail(2, 'items 至多 365 条');
434
+ const r = batchLogWeight(db, items.map((e) => {
435
+ const o = (e ?? {});
436
+ return { date: o['date'] === undefined ? undefined : String(o['date']), kg: o['kg'] };
437
+ }));
438
+ return out(R('批量补录体重', 'create', '批量记体重:写入 ' + r.wrote + ',跳过 ' + r.skipped + ',失败 ' + r.failed, '批量补录体重', 'weight_log (写库回执)', {
439
+ noChange: r.wrote === 0,
440
+ items: r.items.filter((x) => x.status === '失败').slice(0, 20).map((x) => ({ status: '失败', reason: x.reason, detail: String(x.date) })),
441
+ }));
442
+ }
443
+ case 'calorie.exercise.add': {
444
+ if (params['copyFrom'] !== undefined) {
445
+ if (params['copyFrom'] !== 'yesterday')
446
+ fail(2, 'copyFrom 只支持 yesterday');
447
+ const target = optStr(params, 'date') ?? optStr(params, 'targetDate') ?? todayISO();
448
+ assertISO(target, 'date');
449
+ const r = copyYesterday(db, target);
450
+ if (r.copied + r.skipped === 0)
451
+ throw new CalorieRenderError('missing-data', '昨日无运动记录可复制');
452
+ return out(R('复制昨日运动', 'create', '已复制昨日运动→' + target + ':复制 ' + r.copied + ',跳过 ' + r.skipped, '复制昨日运动', 'exercise_log (写库回执)', {
453
+ noChange: r.copied === 0,
454
+ }));
455
+ }
456
+ if (params['items'] !== undefined) {
457
+ const items = needArr(params, 'items');
458
+ if (items.length > 200)
459
+ fail(2, 'items 至多 200 条');
460
+ const r = batchAdd(db, items.map((e, i) => oneExercise((e ?? {}), '(第' + i + '条)')));
461
+ return out(R('记运动', 'create', '批量记运动:新增 ' + r.added + ' 条', '批量补记运动', 'exercise_log (写库回执)', {
462
+ recordId: r.ids[0] ?? null,
463
+ }));
464
+ }
465
+ const input = oneExercise(params, '');
466
+ const r = addRecord(db, input);
467
+ return out(R('记运动', 'create', '已记运动:' + input.exerciseType + ' ' + input.caloriesBurned + ' 卡' + (input.minutes ? ' · ' + input.minutes + ' 分钟' : '') + '(' + input.date + ')', '记运动', 'exercise_log (写库回执)', {
468
+ recordId: r.id, items: [{ id: r.id, date: input.date, status: '成功', reason: '', detail: input.exerciseType }],
469
+ }));
470
+ }
471
+ case 'calorie.exercise.update': {
472
+ const id = optNum(params, 'id');
473
+ const date = optStr(params, 'date');
474
+ const fields = {};
475
+ for (const [camel, col] of Object.entries(EX_CAMEL)) {
476
+ if (camel === 'type' || camel === 'exerciseType' || camel === 'calories' || camel === 'caloriesBurned')
477
+ continue;
478
+ if (params[camel] !== undefined)
479
+ fields[col] = params[camel];
480
+ }
481
+ const t = optStr(params, 'type') ?? optStr(params, 'exerciseType');
482
+ if (t !== undefined) {
483
+ if (!t.trim())
484
+ fail(2, 'type 不得为空');
485
+ fields['exercise_type'] = t.trim();
486
+ }
487
+ const cal = optNum(params, 'calories') ?? optNum(params, 'caloriesBurned');
488
+ if (cal !== undefined) {
489
+ if (cal < 0)
490
+ fail(2, 'calories 不得为负');
491
+ fields['calories_burned'] = cal;
492
+ }
493
+ for (const k of Object.keys(params)) {
494
+ if (!(k in EX_CAMEL) && k !== 'id' && k !== 'key')
495
+ fail(2, '不支持字段: ' + k);
496
+ }
497
+ if (Object.keys(fields).length === 0)
498
+ fail(2, '至少传 1 个待改字段');
499
+ if (typeof fields['date'] === 'string')
500
+ assertISO(fields['date'], 'date');
501
+ if (id !== undefined) {
502
+ if (!Number.isInteger(id) || id <= 0)
503
+ fail(2, 'id 须为正整数');
504
+ updateRecord(db, id, fields);
505
+ return out(R('改运动记录', 'update', '已更新运动 #' + id + '(' + Object.keys(fields).join('、') + ')', '改运动记录', 'exercise_log (写库回执)', {
506
+ recordId: id, items: [{ id, status: '已更新', reason: '' }],
507
+ }));
508
+ }
509
+ if (date !== undefined) {
510
+ assertISO(date, 'date');
511
+ const r = updateDay(db, date, fields);
512
+ if (r.matched === 0)
513
+ throw new CalorieRenderError('missing-data', '无运动记录(' + date + ')');
514
+ return out(R('改某日运动', 'update', '已更新 ' + date + ' 运动 ' + r.matched + ' 条', '改某日运动', 'exercise_log (写库回执)', {}));
515
+ }
516
+ fail(2, '缺参数 id 或 date(二选一)');
517
+ throw new Error('unreachable');
518
+ }
519
+ case 'calorie.exercise.remove': {
520
+ const id = optNum(params, 'id');
521
+ const date = optStr(params, 'date');
522
+ const from = optStr(params, 'from');
523
+ const to = optStr(params, 'to');
524
+ if (id !== undefined) {
525
+ if (!Number.isInteger(id) || id <= 0)
526
+ fail(2, 'id 须为正整数');
527
+ deleteRecord(db, id);
528
+ return out(R('删运动记录', 'delete', '已删除运动 #' + id, '删运动记录', 'exercise_log (写库回执)', {
529
+ recordId: id, items: [{ id, status: '已删除', reason: '' }],
530
+ }));
531
+ }
532
+ if (date !== undefined) {
533
+ assertISO(date, 'date');
534
+ const n = deleteDay(db, date);
535
+ if (n === 0)
536
+ throw new CalorieRenderError('missing-data', '无运动记录(' + date + ')');
537
+ return out(R('删某日运动', 'delete', '已删除 ' + date + ' 运动 ' + n + ' 条', '删某日运动', 'exercise_log (写库回执)', {}));
538
+ }
539
+ if (from !== undefined || to !== undefined) {
540
+ if (from === undefined || to === undefined)
541
+ fail(2, '按范围删须同时传 from/to');
542
+ assertISO(from, 'from');
543
+ assertISO(to, 'to');
544
+ if (from > to)
545
+ fail(2, 'from 不得晚于 to');
546
+ const n = deleteRange(db, from, to);
547
+ if (n === 0)
548
+ throw new CalorieRenderError('missing-data', '无运动记录(' + from + '~' + to + ')');
549
+ return out(R('批量删运动', 'delete', '已删除 ' + from + '~' + to + ' 运动 ' + n + ' 条', '批量删运动', 'exercise_log (写库回执)', {}));
550
+ }
551
+ fail(2, '缺参数 id/date/from+to(三选一)');
552
+ throw new Error('unreachable');
553
+ }
554
+ case 'calorie.photo.add': {
555
+ const raw = params['srcPaths'] ?? params['srcPath'];
556
+ const srcPaths = (Array.isArray(raw) ? raw : [raw]).filter((s) => typeof s === 'string' && s.length > 0);
557
+ if (srcPaths.length === 0)
558
+ fail(2, '缺参数 srcPaths(照片源文件路径数组)');
559
+ if (srcPaths.length > 20)
560
+ fail(2, 'srcPaths 至多 20 张');
561
+ const tag = needStr(params, 'tag');
562
+ const dir = photosDirOf(params);
563
+ const today = optStr(params, 'date') ?? todayISO();
564
+ assertISO(today, 'date');
565
+ const added = addPhotos(db, dir, { srcPaths, tag, note: optStr(params, 'note'), today, nowTime: optStr(params, 'time') });
566
+ if (added.length === 0)
567
+ throw new CalorieRenderError('missing-data', '照片源文件均不存在,未存入');
568
+ let distance = null;
569
+ try {
570
+ const days = daysSinceTagPhoto(db, tag, today);
571
+ distance = days === null ? null : { tag, days };
572
+ }
573
+ catch {
574
+ distance = null;
575
+ }
576
+ const receipt = buildAddReceipt(added, { tag, note: optStr(params, 'note'), distance, failedCount: srcPaths.length - added.length || undefined });
577
+ return { data: { ok: true, message: receipt.summary, receipt }, html: receiptHtml(receipt.scene, receipt.summary, receipt.op, receipt.recordId, receipt.items) };
578
+ }
579
+ case 'calorie.photo.remove': {
580
+ const id = needId(params);
581
+ const dir = photosDirOf(params);
582
+ const snap = getPhotoRow(db, id);
583
+ if (!snap)
584
+ throw new CalorieRenderError('missing-data', '身材照 #' + id + ' 不存在');
585
+ deletePhoto(db, dir, id);
586
+ const receipt = buildDeleteReceipt(snap);
587
+ return { data: { ok: true, message: receipt.summary, receipt }, html: receiptHtml(receipt.scene, receipt.summary, receipt.op, receipt.recordId, receipt.items) };
588
+ }
589
+ case 'calorie.photo.tag': {
590
+ const id = needId(params);
591
+ const op = needStr(params, 'op');
592
+ if (op !== 'set' && op !== 'add' && op !== 'remove')
593
+ fail(2, 'op 须为 set/add/remove:' + op);
594
+ const row = getPhotoRow(db, id);
595
+ if (!row)
596
+ throw new CalorieRenderError('missing-data', '身材照 #' + id + ' 不存在');
597
+ const before = [...row.tag_list];
598
+ let scene;
599
+ if (op === 'set') {
600
+ const raw = params['tags'] ?? params['tag'];
601
+ const tags = Array.isArray(raw) ? raw.map(String) : parseTags(typeof raw === 'string' ? raw : '');
602
+ if (tags.length === 0)
603
+ fail(2, '缺参数 tags(新标签全量)');
604
+ updateTag(db, id, serializeTags(tags));
605
+ scene = '改照片标签';
606
+ }
607
+ else if (op === 'add') {
608
+ const tag = needStr(params, 'tag');
609
+ tagAdd(db, id, tag);
610
+ scene = '加照片标签';
611
+ }
612
+ else {
613
+ const tag = needStr(params, 'tag');
614
+ if (parseTags(tag).length !== 1)
615
+ fail(2, '删标签一次只删 1 个');
616
+ tagRemove(db, id, tag);
617
+ scene = '删照片标签';
618
+ }
619
+ const after = getPhotoRow(db, id)?.tag_list ?? before;
620
+ const receipt = buildTagReceipt(id, before, after, scene);
621
+ return { data: { ok: true, message: receipt.summary, receipt }, html: receiptHtml(receipt.scene, receipt.summary, receipt.op, receipt.recordId, receipt.items) };
622
+ }
623
+ case 'calorie.product.add': {
624
+ const productName = (optStr(params, 'productName') ?? optStr(params, 'product_name') ?? '');
625
+ if (!productName.trim())
626
+ fail(2, '缺参数 productName');
627
+ const r = addProduct(db, {
628
+ productName: productName.trim(), brand: optStr(params, 'brand'),
629
+ calories: needNum(params, 'calories'), protein: needNum(params, 'protein'), fat: needNum(params, 'fat'),
630
+ saturatedFat: optNum(params, 'saturatedFat'), carbohydrates: needNum(params, 'carbohydrates'),
631
+ sugar: optNum(params, 'sugar'), dietaryFiber: optNum(params, 'dietaryFiber'),
632
+ sodium: needNum(params, 'sodium'), note: optStr(params, 'note'),
633
+ });
634
+ return out(R('存食品', 'create', '已存食品 #' + r.id + '(' + productName.trim() + ')', '存食品', 'nutrition_products (写库回执)', {
635
+ recordId: r.id, items: [{ id: r.id, status: '成功', reason: '', detail: productName.trim() }],
636
+ }));
637
+ }
638
+ case 'calorie.product.update': {
639
+ const id = needId(params);
640
+ const map = {
641
+ productName: 'product_name', brand: 'brand', calories: 'calories', protein: 'protein', fat: 'fat',
642
+ saturatedFat: 'saturated_fat', carbohydrates: 'carbohydrates', sugar: 'sugar',
643
+ dietaryFiber: 'dietary_fiber', sodium: 'sodium', note: 'note', category: 'category',
644
+ };
645
+ const fields = {};
646
+ for (const [camel, col] of Object.entries(map)) {
647
+ if (params[camel] !== undefined)
648
+ fields[col] = params[camel];
649
+ }
650
+ for (const k of Object.keys(params)) {
651
+ if (!(k in map) && k !== 'id' && k !== 'key')
652
+ fail(2, '不支持字段: ' + k);
653
+ }
654
+ if (Object.keys(fields).length === 0)
655
+ fail(2, '至少传 1 个待改字段');
656
+ const r = updateProduct(db, id, fields);
657
+ if (!r.updated)
658
+ throw new CalorieRenderError('missing-data', '食品 #' + id + ' 不存在');
659
+ return out(R('改食品', 'update', '已更新食品 #' + id + '(' + Object.keys(fields).join('、') + ')', '改食品', 'nutrition_products (写库回执)', {
660
+ recordId: id, items: [{ id, status: '已更新', reason: '' }],
661
+ }));
662
+ }
663
+ case 'calorie.product.deprecate': {
664
+ const id = needId(params);
665
+ const r = deprecateProduct(db, id);
666
+ if (!r.ok) {
667
+ if (/not found/.test(String(r.error ?? '')))
668
+ throw new CalorieRenderError('missing-data', '食品 #' + id + ' 不存在');
669
+ fail(2, String(r.error ?? '废弃失败'));
670
+ }
671
+ return out(R('下架食品', 'update', '已下架食品 #' + id + '(' + (r.name ?? '') + ',行保留可恢复)', '下架食品', 'nutrition_products (写库回执)', {
672
+ recordId: id, items: [{ id, status: '已下架', reason: '' }],
673
+ }));
674
+ }
675
+ case 'calorie.profile.set':
676
+ case 'calorie.profile.update': {
677
+ const isSet = key === 'calorie.profile.set';
678
+ const scene = isSet ? '设置档案' : '改档案';
679
+ const wake = isSet ? '设置档案' : '改档案';
680
+ const picked = {};
681
+ if (params['fields'] !== undefined) {
682
+ const f = params['fields'];
683
+ if (typeof f !== 'object' || f === null || Array.isArray(f))
684
+ fail(2, 'fields 须为对象');
685
+ Object.assign(picked, f);
686
+ }
687
+ else if (params['field'] !== undefined) {
688
+ picked[String(needStr(params, 'field'))] = params['value'];
689
+ }
690
+ else {
691
+ for (const k of ['age', 'gender', 'heightCm', 'activityLevel', 'note']) {
692
+ if (params[k] !== undefined)
693
+ picked[k] = params[k];
694
+ }
695
+ }
696
+ for (const k of Object.keys(picked)) {
697
+ if (!['age', 'gender', 'heightCm', 'activityLevel', 'note'].includes(k))
698
+ fail(2, '不支持字段: ' + k);
699
+ }
700
+ if (Object.keys(picked).length === 0)
701
+ fail(2, '至少传 1 个档案字段');
702
+ const r = updateProfile(db, picked);
703
+ const after = r.after;
704
+ return out(R(scene, 'update', '已' + scene + '(身高 ' + (after.height_cm ?? '—') + ' · 年龄 ' + (after.age ?? '—') + ' · 活动量 ' + (after.activity_level ?? '—') + ')', wake, 'user_profile (写库回执)', {
705
+ recordId: 1, noChange: r.changed.length === 0,
706
+ }));
707
+ }
708
+ case 'calorie.profile.activity': {
709
+ const level = optStr(params, 'activityLevel') ?? optStr(params, 'level') ?? '';
710
+ if (!level.trim())
711
+ fail(2, '缺参数 activityLevel');
712
+ const r = setActivityLevel(db, level);
713
+ return out(R('设活动量', 'update', '已设活动量:' + (r.before ?? '—') + '→' + r.after, '设活动量', 'user_profile (写库回执)', {
714
+ recordId: 1, noChange: r.before === r.after,
715
+ }));
716
+ }
717
+ case 'calorie.goal.set': {
718
+ const calorie = needNum(params, 'calorie');
719
+ const protein = needNum(params, 'protein');
720
+ const carbs = needNum(params, 'carbs');
721
+ const fat = needNum(params, 'fat');
722
+ const water = optNum(params, 'water');
723
+ if (!(calorie > 0))
724
+ fail(2, '热量目标必须为正数');
725
+ for (const [k, v] of [['protein', protein], ['carbs', carbs], ['fat', fat]]) {
726
+ if (v < 0)
727
+ fail(2, k + ' 不能为负');
728
+ }
729
+ if (water !== undefined && water < 0)
730
+ fail(2, 'water 不能为负');
731
+ const had = getNutritionGoal(db) !== null;
732
+ const r = setNutritionGoal(db, { calorie, protein, carbs, fat, water });
733
+ const tail = r.consistent ? ' · 宏量自洽' : ' · ⚠宏量换算差 ' + r.diffKcal + ' 卡(>50 建议复核)';
734
+ return out(R('定营养目标', had ? 'update' : 'create', '已定营养目标:' + r.calorieGoal + ' 卡·蛋白 ' + r.proteinGoal + '·碳水 ' + r.carbsGoal + '·脂肪 ' + r.fatGoal + (r.waterGoal === null ? '' : '·饮水 ' + r.waterGoal) + tail, '定营养目标', 'daily_goal (写库回执)', {
735
+ recordId: 1,
736
+ }));
737
+ }
738
+ case 'calorie.goal.water': {
739
+ const water = needNum(params, 'water');
740
+ if (getNutritionGoal(db) === null)
741
+ throw new CalorieRenderError('missing-data', '尚无营养目标行(先定营养目标)');
742
+ const r = updateWaterGoal(db, water);
743
+ return out(R('定饮水目标', 'update', '已定饮水目标:' + (r.oldWaterGoal ?? '—') + '→' + r.newWaterGoal + ' ml', '定饮水目标', 'daily_goal (写库回执)', {
744
+ recordId: 1, noChange: r.oldWaterGoal === r.newWaterGoal,
745
+ }));
746
+ }
747
+ case 'calorie.goal.weight': {
748
+ const kg = needNum(params, 'kg');
749
+ if (!(kg > 0) || kg > 500)
750
+ fail(2, 'kg 须为 0..500');
751
+ const r = setWeightGoal(db, {
752
+ kg, deadline: params['deadline'], startKg: params['startKg'], startDate: params['startDate'],
753
+ });
754
+ return out(R('定体重目标', 'update', '已定体重目标 ' + r.weightGoal + ' kg' + (r.deadline ? '(截止 ' + r.deadline + ')' : '') + (r.startKg !== null ? ' · 起点 ' + r.startKg + ' kg' : ''), '定体重目标', 'daily_goal (写库回执)', {
755
+ recordId: 1,
756
+ }));
757
+ }
758
+ case 'calorie.goal.pause': {
759
+ const r = pauseAllGoals(db);
760
+ return out(R('暂停所有目标', 'update', '已暂停所有目标(记录照常,仅目标暂停)', '暂停所有目标', 'daily_goal (写库回执)', {
761
+ recordId: r.id,
762
+ }));
763
+ }
764
+ case 'calorie.goal.resume': {
765
+ const r = resumeAllGoals(db);
766
+ return out(R('重启所有目标', 'update', '已重启所有目标(恢复正常)', '重启所有目标', 'daily_goal (写库回执)', {
767
+ recordId: r.id,
768
+ }));
769
+ }
770
+ case 'calorie.body.composition-add': {
771
+ const date = optStr(params, 'date') ?? todayISO();
772
+ assertISO(date, 'date');
773
+ const source = normSource(params['source']);
774
+ const input = {
775
+ date, source, bodyFatPct: params['bodyFatPct'], note: optStr(params, 'note'),
776
+ age: optInt(params, 'age'), sex: normSex(params['sex']),
777
+ };
778
+ for (const f of CALIPER_FIELDS) {
779
+ const v = optNum(params, f);
780
+ if (v !== undefined)
781
+ input[f] = v;
782
+ }
783
+ if (input['bodyFatPct'] === undefined)
784
+ fail(2, '缺参数 bodyFatPct(皮褶→体脂自动换算未移植,直传实测值)');
785
+ const r = addComposition(db, input);
786
+ const label = SOURCE_LABELS[source] ?? source;
787
+ return out(R('记体脂', 'create', '已记体脂:' + date + ' ' + label + ' ' + r.bodyFatPct + '%', '记体脂', 'body_composition (写库回执)', {
788
+ recordId: r.id, items: [{ id: r.id, date, status: '成功', reason: '', detail: r.bodyFatPct + '%' }],
789
+ }));
790
+ }
791
+ case 'calorie.body.composition-remove': {
792
+ const id = needId(params);
793
+ deleteComposition(db, id);
794
+ return out(R('删体脂', 'delete', '已删除体脂记录 #' + id + '(软删除,可恢复)', '删体脂', 'body_composition (写库回执)', {
795
+ recordId: id, items: [{ id, status: '已删除', reason: '' }],
796
+ }));
797
+ }
798
+ case 'calorie.body.measure-add': {
799
+ const date = optStr(params, 'date') ?? todayISO();
800
+ assertISO(date, 'date');
801
+ const input = { date, note: optStr(params, 'note') };
802
+ for (const [camel, col] of Object.entries(MEASURE_CAMEL)) {
803
+ const v = optNum(params, camel);
804
+ if (v !== undefined)
805
+ input[col] = v;
806
+ }
807
+ for (const k of Object.keys(params)) {
808
+ if (!(k in MEASURE_CAMEL) && k !== 'date' && k !== 'note' && k !== 'key')
809
+ fail(2, '不支持字段: ' + k);
810
+ }
811
+ const r = addMeasurement(db, input);
812
+ const filledCn = r.filled.map((f) => {
813
+ const camel = Object.keys(MEASURE_CAMEL).find((c) => MEASURE_CAMEL[c] === f) ?? f;
814
+ return camel + ' ' + String(input[f]);
815
+ }).join('、');
816
+ return out(R('记围度', 'create', '已记围度:' + date + '(' + filledCn + ')', '记围度', 'body_measurements (写库回执)', {
817
+ recordId: r.id, items: [{ id: r.id, date, status: '成功', reason: '', detail: filledCn }],
818
+ }));
819
+ }
820
+ case 'calorie.body.measure-remove': {
821
+ const id = needId(params);
822
+ deleteMeasurement(db, id);
823
+ return out(R('删围度', 'delete', '已删除围度记录 #' + id + '(软删除,可恢复)', '删围度', 'body_measurements (写库回执)', {
824
+ recordId: id, items: [{ id, status: '已删除', reason: '' }],
825
+ }));
826
+ }
827
+ default:
828
+ fail(3, '未知 calorie 写键:' + key);
829
+ throw new Error('unreachable');
830
+ }
831
+ }
832
+ //# sourceMappingURL=write.js.map