prompt_builder.go 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606
  1. package utils
  2. import (
  3. "bytes"
  4. "encoding/json"
  5. "fmt"
  6. "regexp"
  7. "strings"
  8. )
  9. // ExamPromptQuestionType represents the structure of question type configuration passed from frontend
  10. type ExamPromptQuestionType struct {
  11. Name string `json:"name"`
  12. RomanNumeral string `json:"romanNumeral"`
  13. QuestionCount int `json:"questionCount"`
  14. ScorePerQuestion int `json:"scorePerQuestion"`
  15. }
  16. // ExamPromptRequest represents the payload required to build an exam prompt
  17. type ExamPromptRequest struct {
  18. Mode string `json:"mode"`
  19. Client string `json:"client"`
  20. ProjectType string `json:"projectType"`
  21. ExamTitle string `json:"examTitle"`
  22. TotalScore int `json:"totalScore"`
  23. QuestionTypes []ExamPromptQuestionType `json:"questionTypes"`
  24. PPTContent string `json:"pptContent"`
  25. }
  26. // SingleQuestionPromptRequest represents the payload required to build a single-question prompt
  27. type SingleQuestionPromptRequest struct {
  28. Client string `json:"client"`
  29. SectionType string `json:"sectionType"`
  30. QuestionIndex int `json:"questionIndex"`
  31. ProjectType string `json:"projectType"`
  32. QuestionType string `json:"questionType"`
  33. ScorePerQuestion int `json:"scorePerQuestion"`
  34. CurrentQuestion json.RawMessage `json:"currentQuestion"`
  35. }
  36. type questionStats struct {
  37. Score int
  38. Count int
  39. }
  40. // BuildExamPrompt generates the exam prompt text based on mode (ai/ppt) and client (pc/mobile)
  41. func BuildExamPrompt(req ExamPromptRequest) (string, error) {
  42. mode := strings.ToLower(strings.TrimSpace(req.Mode))
  43. if mode == "" {
  44. mode = "ai"
  45. }
  46. client := strings.ToLower(strings.TrimSpace(req.Client))
  47. if client == "" {
  48. client = "pc"
  49. }
  50. switch client {
  51. case "mobile":
  52. if mode == "ppt" {
  53. return buildMobilePPTExamPrompt(req), nil
  54. }
  55. return buildMobileAIExamPrompt(req), nil
  56. default:
  57. if mode == "ppt" {
  58. return buildPcPPTExamPrompt(req), nil
  59. }
  60. return buildPcAIExamPrompt(req), nil
  61. }
  62. }
  63. // BuildSingleQuestionPrompt builds prompts for regenerating a single question
  64. func BuildSingleQuestionPrompt(req SingleQuestionPromptRequest) (string, error) {
  65. client := strings.ToLower(strings.TrimSpace(req.Client))
  66. if client == "" {
  67. client = "pc"
  68. }
  69. sectionType := normalizeSectionType(req.SectionType)
  70. projectType := fallbackString(req.ProjectType, "工程")
  71. questionType := fallbackString(req.QuestionType, questionTypeFromSection(sectionType))
  72. scorePerQuestion := safePositive(req.ScorePerQuestion, 1)
  73. questionIndex := req.QuestionIndex + 1
  74. currentQuestion := formatCurrentQuestionJSON(req.CurrentQuestion)
  75. switch client {
  76. case "mobile":
  77. return buildMobileSingleQuestionPrompt(projectType, questionType, sectionType, questionIndex, scorePerQuestion, currentQuestion), nil
  78. default:
  79. return buildPcSingleQuestionPrompt(projectType, questionType, sectionType, questionIndex, scorePerQuestion, currentQuestion), nil
  80. }
  81. }
  82. func buildPcAIExamPrompt(req ExamPromptRequest) string {
  83. projectType := fallbackString(req.ProjectType, "工程")
  84. examTitle := fallbackString(req.ExamTitle, "考试试卷")
  85. questionConfig := buildPcQuestionConfig(req.QuestionTypes)
  86. jsonTemplate := buildPcJSONTemplate(req, examTitle)
  87. return fmt.Sprintf(`请为%[1]s工程生成一份名为"%[2]s"的考试试卷:
  88. 试卷总分:%[3]d分
  89. 题型配置:
  90. %[4]s
  91. 请严格按照以下JSON格式返回试卷内容,不要包含任何其他文字:
  92. %[5]s
  93. 注意:
  94. 1. 所有题目内容必须与%[1]s工程相关
  95. 2. 题目难度适中,符合考试要求
  96. 3. 严格按照JSON格式返回,不要有多余字符
  97. 4. 单选题和判断题的选项要合理
  98. 5. 多选题至少要有2个正确答案
  99. 6. 简答题要提供清晰的答题要点
  100. 7. 必须为每道题设置正确答案:
  101. - 单选题:selectedAnswer字段填写正确的选项(A/B/C/D)
  102. - 判断题:selectedAnswer字段填写"正确"或"错误"
  103. - 多选题:selectedAnswers数组包含所有正确答案选项
  104. 8. 简答题答案字数不超过500字
  105. `, projectType, examTitle, safeTotalScore(req.TotalScore), questionConfig, jsonTemplate)
  106. }
  107. func buildPcPPTExamPrompt(req ExamPromptRequest) string {
  108. contentText := sanitizePPTContent(req.PPTContent)
  109. if contentText == "" {
  110. contentText = fallbackString(req.ExamTitle, "PPT内容")
  111. }
  112. examTitle := fallbackString(req.ExamTitle, "考试试卷")
  113. questionConfig := buildPcQuestionConfig(req.QuestionTypes)
  114. jsonTemplate := buildPcJSONTemplate(req, examTitle)
  115. return fmt.Sprintf(`请根据标题为%[1]s生成一份考试试卷:
  116. 试卷总分:%[2]d分
  117. 题型配置:
  118. %[3]s
  119. 请严格按照以下JSON格式返回试卷内容,不要包含任何其他文字:
  120. %[4]s
  121. 注意:
  122. 1. 所有题目内容必须基于提供的内容生成,与%[1]s工程相关
  123. 2. 题目难度适中,符合考试要求
  124. 3. 严格按照JSON格式返回,不要有多余字符
  125. 4. 单选题和判断题的选项要合理
  126. 5. 多选题至少要有2个正确答案
  127. 6. 简答题要提供清晰的答题要点
  128. 7. 必须为每道题设置正确答案:
  129. - 单选题:selectedAnswer字段填写正确的选项(A/B/C/D)
  130. - 判断题:selectedAnswer字段填写"正确"或"错误"
  131. - 多选题:selectedAnswers数组包含所有正确答案选项
  132. 8. 简答题答案字数不超过500字
  133. 9. 确保题目内容与提供的内容高度相关
  134. `, contentText, safeTotalScore(req.TotalScore), questionConfig, jsonTemplate)
  135. }
  136. func buildMobileAIExamPrompt(req ExamPromptRequest) string {
  137. projectType := fallbackString(req.ProjectType, "工程")
  138. examTitle := fallbackString(req.ExamTitle, "考试试卷")
  139. questionConfig := buildMobileQuestionConfig(req.QuestionTypes)
  140. jsonTemplate := buildMobileJSONTemplate(req, examTitle)
  141. return fmt.Sprintf(`请生成一份%[1]s工程施工技术考核试卷:
  142. 试卷要求:
  143. - 试卷名称:%[2]s
  144. - 试卷总分:%[3]d分
  145. - 题型配置:
  146. %[4]s
  147. 内容要求:
  148. 1. %[1]s工程施工技术的核心知识点
  149. 2. 施工工艺流程和质量控制要点
  150. 3. 安全操作规程和事故预防措施
  151. 4. 相关法规标准和验收规范
  152. 请严格按照以下JSON格式返回试卷内容,不要包含任何其他文字:
  153. 请在questions数组中生成具体、完整的题目内容!
  154. - 单选题需要:text题目内容、options选项数组[{"key":"A","text":"选项1"},{"key":"B","text":"选项2"},{"key":"C","text":"选项3"},{"key":"D","text":"选项4"}]、selectedAnswer正确答案
  155. - 判断题需要:text题目内容、selectedAnswer正确答案(正确/错误)
  156. - 多选题需要:text题目内容、options选项数组[{"key":"A","text":"选项1"},{"key":"B","text":"选项2"},{"key":"C","text":"选项3"},{"key":"D","text":"选项4"}]、selectedAnswers正确答案数组
  157. - 简答题需要:text题目内容、outline {keyFactors:关键要点, measures:具体措施}
  158. %[5]s`, projectType, examTitle, safeTotalScore(req.TotalScore), questionConfig, jsonTemplate)
  159. }
  160. func buildMobilePPTExamPrompt(req ExamPromptRequest) string {
  161. examTitle := fallbackString(req.ExamTitle, "考试试卷")
  162. pptContent := fallbackString(strings.TrimSpace(req.PPTContent), "PPT内容")
  163. questionConfig := buildMobileQuestionConfig(req.QuestionTypes)
  164. jsonTemplate := buildMobileJSONTemplate(req, examTitle+" - PPT培训考试")
  165. return fmt.Sprintf(`请基于以下PPT培训内容生成考试试卷:
  166. 培训主题:%[1]s
  167. PPT内容:%[2]s
  168. 请生成一份包含以下题型的考试试卷:
  169. %[3]s
  170. 试卷总分:%[4]d分
  171. 要求:
  172. 1. 重点考查PPT中的核心知识点
  173. 2. 涵盖主要概念、工艺流程、操作规程等关键内容
  174. 3. 题目难度适中,具有实际应用价值
  175. 4. 确保答案准确性和专业性
  176. 请严格按照以下JSON格式返回试卷内容,不要包含任何其他文字:
  177. 请在questions数组中生成具体、完整的题目内容!
  178. - 单选题需要:text题目内容、options选项数组[{"key":"A","text":"选项1"},{"key":"B","text":"选项2"},{"key":"C","text":"选项3"},{"key":"D","text":"选项4"}]、selectedAnswer正确答案
  179. - 判断题需要:text题目内容、selectedAnswer正确答案(正确/错误)
  180. - 多选题需要:text题目内容、options选项数组[{"key":"A","text":"选项1"},{"key":"B","text":"选项2"},{"key":"C","text":"选项3"},{"key":"D","text":"选项4"}]、selectedAnswers正确答案数组
  181. - 简答题需要:text题目内容、outline {keyFactors:关键要点, measures:具体措施}
  182. %[5]s`, examTitle, pptContent, questionConfig, safeTotalScore(req.TotalScore), jsonTemplate)
  183. }
  184. func buildPcQuestionConfig(questionTypes []ExamPromptQuestionType) string {
  185. if len(questionTypes) == 0 {
  186. return "暂无题型配置\n"
  187. }
  188. var builder strings.Builder
  189. for _, qt := range questionTypes {
  190. roman := strings.TrimSpace(qt.RomanNumeral)
  191. if roman == "" {
  192. roman = "•"
  193. }
  194. score := safePositive(qt.ScorePerQuestion, 1)
  195. count := safePositive(qt.QuestionCount, 1)
  196. builder.WriteString(fmt.Sprintf("%s、%s:%d题,每题%d分,共%d分\n", roman, qt.Name, count, score, count*score))
  197. }
  198. return builder.String()
  199. }
  200. func buildMobileQuestionConfig(questionTypes []ExamPromptQuestionType) string {
  201. if len(questionTypes) == 0 {
  202. return "暂无题型配置\n"
  203. }
  204. var builder strings.Builder
  205. for _, qt := range questionTypes {
  206. score := safePositive(qt.ScorePerQuestion, 1)
  207. count := safePositive(qt.QuestionCount, 1)
  208. builder.WriteString(fmt.Sprintf("%s:%d题,每题%d分,共%d分\n", qt.Name, count, score, count*score))
  209. }
  210. return builder.String()
  211. }
  212. func buildPcJSONTemplate(req ExamPromptRequest, title string) string {
  213. totalQuestions := sumQuestionCount(req.QuestionTypes)
  214. single := statsByName(req.QuestionTypes, "单选题", 2, 15)
  215. judge := statsByName(req.QuestionTypes, "判断题", 2, 10)
  216. multiple := statsByName(req.QuestionTypes, "多选题", 3, 10)
  217. short := statsByName(req.QuestionTypes, "简答题", 10, 2)
  218. return fmt.Sprintf(`{
  219. "title": "%[1]s",
  220. "totalScore": %[2]d,
  221. "totalQuestions": %[3]d,
  222. "singleChoice": {
  223. "scorePerQuestion": %[4]d,
  224. "totalScore": %[5]d,
  225. "count": %[6]d,
  226. "questions": [
  227. {
  228. "text": "题目内容",
  229. "options": [
  230. {"key": "A", "text": "选项A内容"},
  231. {"key": "B", "text": "选项B内容"},
  232. {"key": "C", "text": "选项C内容"},
  233. {"key": "D", "text": "选项D内容"}
  234. ],
  235. "selectedAnswer": "正确答案选项(A/B/C/D)"
  236. }
  237. ]
  238. },
  239. "judge": {
  240. "scorePerQuestion": %[7]d,
  241. "totalScore": %[8]d,
  242. "count": %[9]d,
  243. "questions": [
  244. {
  245. "text": "题目内容",
  246. "selectedAnswer": "正确答案(正确/错误)"
  247. }
  248. ]
  249. },
  250. "multiple": {
  251. "scorePerQuestion": %[10]d,
  252. "totalScore": %[11]d,
  253. "count": %[12]d,
  254. "questions": [
  255. {
  256. "text": "题目内容",
  257. "options": [
  258. {"key": "A", "text": "选项A内容"},
  259. {"key": "B", "text": "选项B内容"},
  260. {"key": "C", "text": "选项C内容"},
  261. {"key": "D", "text": "选项D内容"}
  262. ],
  263. "selectedAnswers": ["正确答案选项1", "正确答案选项2"]
  264. }
  265. ]
  266. },
  267. "short": {
  268. "scorePerQuestion": %[13]d,
  269. "totalScore": %[14]d,
  270. "count": %[15]d,
  271. "questions": [
  272. {
  273. "text": "题目内容",
  274. "outline": {
  275. "keyFactors": "答题要点、关键因素、示例答案"
  276. }
  277. }
  278. ]
  279. }
  280. }`, title, safeTotalScore(req.TotalScore), totalQuestions,
  281. single.Score, single.Score*single.Count, single.Count,
  282. judge.Score, judge.Score*judge.Count, judge.Count,
  283. multiple.Score, multiple.Score*multiple.Count, multiple.Count,
  284. short.Score, short.Score*short.Count, short.Count)
  285. }
  286. func buildMobileJSONTemplate(req ExamPromptRequest, title string) string {
  287. totalQuestions := sumQuestionCount(req.QuestionTypes)
  288. q0 := statsByIndex(req.QuestionTypes, 0, 2, 8)
  289. q1 := statsByIndex(req.QuestionTypes, 1, 2, 5)
  290. q2 := statsByIndex(req.QuestionTypes, 2, 3, 5)
  291. q3 := statsByIndex(req.QuestionTypes, 3, 10, 2)
  292. return fmt.Sprintf(`{
  293. "title": "%[1]s",
  294. "totalScore": %[2]d,
  295. "totalQuestions": %[3]d,
  296. "singleChoice": {
  297. "scorePerQuestion": %[4]d,
  298. "totalScore": %[5]d,
  299. "count": %[6]d,
  300. "questions": []
  301. },
  302. "judge": {
  303. "scorePerQuestion": %[7]d,
  304. "totalScore": %[8]d,
  305. "count": %[9]d,
  306. "questions": []
  307. },
  308. "multiple": {
  309. "scorePerQuestion": %[10]d,
  310. "totalScore": %[11]d,
  311. "count": %[12]d,
  312. "questions": []
  313. },
  314. "short": {
  315. "scorePerQuestion": %[13]d,
  316. "totalScore": %[14]d,
  317. "count": %[15]d,
  318. "questions": []
  319. }
  320. }`, title, safeTotalScore(req.TotalScore), totalQuestions,
  321. q0.Score, q0.Score*q0.Count, q0.Count,
  322. q1.Score, q1.Score*q1.Count, q1.Count,
  323. q2.Score, q2.Score*q2.Count, q2.Count,
  324. q3.Score, q3.Score*q3.Count, q3.Count)
  325. }
  326. func statsByName(questionTypes []ExamPromptQuestionType, name string, defaultScore, defaultCount int) questionStats {
  327. for _, qt := range questionTypes {
  328. if strings.TrimSpace(qt.Name) == name {
  329. return questionStats{
  330. Score: safePositive(qt.ScorePerQuestion, defaultScore),
  331. Count: safePositive(qt.QuestionCount, defaultCount),
  332. }
  333. }
  334. }
  335. return questionStats{Score: defaultScore, Count: defaultCount}
  336. }
  337. func statsByIndex(questionTypes []ExamPromptQuestionType, index int, defaultScore, defaultCount int) questionStats {
  338. if index >= 0 && index < len(questionTypes) {
  339. qt := questionTypes[index]
  340. return questionStats{
  341. Score: safePositive(qt.ScorePerQuestion, defaultScore),
  342. Count: safePositive(qt.QuestionCount, defaultCount),
  343. }
  344. }
  345. return questionStats{Score: defaultScore, Count: defaultCount}
  346. }
  347. func sumQuestionCount(questionTypes []ExamPromptQuestionType) int {
  348. total := 0
  349. for _, qt := range questionTypes {
  350. total += safePositive(qt.QuestionCount, 0)
  351. }
  352. return total
  353. }
  354. func buildPcSingleQuestionPrompt(projectType, questionType, sectionType string, questionIndex, score int, currentQuestion string) string {
  355. format := buildPcSingleQuestionFormat(sectionType)
  356. return fmt.Sprintf(`请基于以下%[1]s工程的%[2]s题目,重新生成一道相似主题的题目,要求如下:
  357. 当前题目参考:
  358. %[3]s
  359. 题目类型:%[2]s
  360. 每题分值:%[4]d分
  361. 题目序号:第%[5]d题
  362. 请严格按照以下JSON格式返回,不要包含任何其他文字:
  363. %[6]s
  364. 注意:
  365. 1. 新题目必须与当前题目保持相似的主题和难度
  366. 2. 题目内容必须与%[1]s工程相关
  367. 3. 题目难度适中,符合考试要求
  368. 4. 严格按照JSON格式返回,不要有多余字符
  369. 5. 单选题和判断题的选项要合理
  370. 6. 多选题至少要有2个正确答案
  371. 7. 简答题要提供清晰的答题要点
  372. 8. 必须为每道题设置正确答案:
  373. - 单选题:selectedAnswer字段填写正确的选项(A/B/C/D)
  374. - 判断题:selectedAnswer字段填写"正确"或"错误"
  375. - 多选题:selectedAnswers数组包含所有正确答案选项
  376. 9. 简答题答案字数不超过500字
  377. 10. 新题目应该是当前题目的变体,保持主题一致性但内容要有所变化`, projectType, questionType, currentQuestion, score, questionIndex, format)
  378. }
  379. func buildMobileSingleQuestionPrompt(projectType, questionType, sectionType string, questionIndex, score int, currentQuestion string) string {
  380. format := buildMobileSingleQuestionFormat(sectionType)
  381. return fmt.Sprintf(`请基于以下%[1]s工程的%[2]s题目,重新生成一道相似主题的题目:
  382. 当前题目参考:
  383. %[3]s
  384. 题目类型:%[2]s
  385. 每题分值:%[4]d分
  386. 题目序号:第%[5]d题
  387. 请严格按照以下JSON格式返回,不要包含任何其他文字:
  388. %[6]s`, projectType, questionType, currentQuestion, score, questionIndex, format)
  389. }
  390. func buildPcSingleQuestionFormat(sectionType string) string {
  391. switch sectionType {
  392. case "single":
  393. return `{
  394. "text": "题目内容",
  395. "options": [
  396. {"key": "A", "text": "选项A内容"},
  397. {"key": "B", "text": "选项B内容"},
  398. {"key": "C", "text": "选项C内容"},
  399. {"key": "D", "text": "选项D内容"}
  400. ],
  401. "selectedAnswer": "正确答案选项(A/B/C/D)"
  402. }`
  403. case "judge":
  404. return `{
  405. "text": "题目内容",
  406. "selectedAnswer": "正确答案(正确/错误)"
  407. }`
  408. case "multiple":
  409. return `{
  410. "text": "题目内容",
  411. "options": [
  412. {"key": "A", "text": "选项A内容"},
  413. {"key": "B", "text": "选项B内容"},
  414. {"key": "C", "text": "选项C内容"},
  415. {"key": "D", "text": "选项D内容"}
  416. ],
  417. "selectedAnswers": ["正确答案选项1", "正确答案选项2"]
  418. }`
  419. case "short":
  420. return `{
  421. "text": "题目内容",
  422. "outline": {
  423. "keyFactors": "答题要点、关键因素、示例答案"
  424. }
  425. }`
  426. default:
  427. return `{
  428. "text": "题目内容"
  429. }`
  430. }
  431. }
  432. func buildMobileSingleQuestionFormat(sectionType string) string {
  433. switch sectionType {
  434. case "single":
  435. return `{
  436. "text": "题目内容",
  437. "options": [
  438. {"key": "A", "text": "选项A"},
  439. {"key": "B", "text": "选项B"},
  440. {"key": "C", "text": "选项C"},
  441. {"key": "D", "text": "选项D"}
  442. ],
  443. "selectedAnswer": "A"
  444. }`
  445. case "judge":
  446. return `{
  447. "text": "题目内容",
  448. "selectedAnswer": "正确"
  449. }`
  450. case "multiple":
  451. return `{
  452. "text": "题目内容",
  453. "options": [
  454. {"key": "A", "text": "选项A"},
  455. {"key": "B", "text": "选项B"},
  456. {"key": "C", "text": "选项C"},
  457. {"key": "D", "text": "选项D"}
  458. ],
  459. "selectedAnswers": ["A", "B"]
  460. }`
  461. case "short":
  462. return `{
  463. "text": "题目内容",
  464. "outline": {
  465. "keyFactors": "关键要点内容",
  466. "measures": "具体措施内容"
  467. }
  468. }`
  469. default:
  470. return `{
  471. "text": "题目内容"
  472. }`
  473. }
  474. }
  475. func formatCurrentQuestionJSON(raw json.RawMessage) string {
  476. if len(raw) == 0 {
  477. return "无"
  478. }
  479. var buf bytes.Buffer
  480. if err := json.Indent(&buf, raw, "", " "); err != nil {
  481. return string(raw)
  482. }
  483. return buf.String()
  484. }
  485. func questionTypeFromSection(sectionType string) string {
  486. switch sectionType {
  487. case "single":
  488. return "单选题"
  489. case "judge":
  490. return "判断题"
  491. case "multiple":
  492. return "多选题"
  493. case "short":
  494. return "简答题"
  495. default:
  496. return "题目"
  497. }
  498. }
  499. func normalizeSectionType(sectionType string) string {
  500. switch strings.ToLower(strings.TrimSpace(sectionType)) {
  501. case "single", "judge", "multiple", "short":
  502. return strings.ToLower(strings.TrimSpace(sectionType))
  503. default:
  504. return "single"
  505. }
  506. }
  507. func sanitizePPTContent(content string) string {
  508. text := strings.TrimSpace(content)
  509. if text == "" {
  510. return ""
  511. }
  512. if strings.Contains(text, "PPT文件信息") {
  513. re := regexp.MustCompile(`(?s)提取的文本内容:\n(.*?)(\n\n|$)`)
  514. if matches := re.FindStringSubmatch(text); len(matches) > 1 {
  515. text = strings.TrimSpace(matches[1])
  516. }
  517. }
  518. return text
  519. }
  520. func fallbackString(value string, fallback string) string {
  521. if strings.TrimSpace(value) == "" {
  522. return fallback
  523. }
  524. return value
  525. }
  526. func safePositive(value int, fallback int) int {
  527. if value <= 0 {
  528. return fallback
  529. }
  530. return value
  531. }
  532. func safeTotalScore(score int) int {
  533. if score <= 0 {
  534. return 100
  535. }
  536. return score
  537. }