save_fuel_rate.py 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203
  1. class SaveFuelRateTool:
  2. name = 'save_fuel_rate'
  3. route_path = '/mcp/tools/saveFuelRate'
  4. backend = 'base'
  5. def __init__(self, api_client=None):
  6. self.api_client = api_client
  7. def metadata(self):
  8. return {
  9. 'name': self.name,
  10. 'description': (
  11. '使用场景:按计费条件新增燃油系数,或编辑一条草稿燃油系数。'
  12. '新增时不要传燃油代码。编辑时必须传query_fuel_rate_list返回的燃油代码,并且只能提交一条费率。'
  13. '必须带燃油类型、开始生效时间,以及至少一条费率和计费条件。'
  14. '结束生效时间不传时按9999-12-31长期有效。'
  15. '燃油类型为1或销售燃油、2或成本燃油。编辑时不能修改燃油类型。'
  16. '只能编辑草稿,且只能编辑计费条件配置。'
  17. '计费条件和条件值都可以填代码或名称,必须完整匹配,不能按包含搜索。'
  18. '销售燃油可选物流产品、物流产品分类、仓库、偏远地址库。'
  19. '成本燃油可选物流渠道、物流渠道分类、仓库、偏远地址库。'
  20. '偏远地址库可以填PY编号或地址库名称。'
  21. '同一条件组第一条不要填关系;后面的关系不传时按and。同一组不能同时使用and和or。'
  22. '新增和编辑后都是草稿,不会立即参与计费。生效请使用enable_fuel_rate。'
  23. '禁止使用:失效、删除、复制、手填不存在的燃油代码。'
  24. '参数只用于工具内部调用;最终回答只能展示中文业务名称和燃油代码。'
  25. ),
  26. 'input_schema': {
  27. 'type': 'object',
  28. 'properties': {
  29. 'price_type': {
  30. 'type': 'string',
  31. 'minLength': 1,
  32. 'maxLength': 20,
  33. 'description': '燃油类型。1或销售燃油,2或成本燃油。',
  34. },
  35. 'start_date': {
  36. 'type': 'string',
  37. 'minLength': 8,
  38. 'maxLength': 30,
  39. 'description': '开始生效时间,格式YYYY-MM-DD。',
  40. },
  41. 'end_date': {
  42. 'type': 'string',
  43. 'minLength': 8,
  44. 'maxLength': 30,
  45. 'description': '结束生效时间,格式YYYY-MM-DD。不传表示9999-12-31。',
  46. },
  47. 'fuel_no': {
  48. 'type': 'string',
  49. 'minLength': 1,
  50. 'maxLength': 20,
  51. 'description': '要编辑的燃油代码,格式为RY加数字。新增时不要传。',
  52. },
  53. 'items': {
  54. 'type': 'array',
  55. 'minItems': 1,
  56. 'maxItems': 20,
  57. 'items': {
  58. 'type': 'object',
  59. 'properties': {
  60. 'fuel_ratio': {
  61. 'type': 'string',
  62. 'minLength': 1,
  63. 'maxLength': 10,
  64. 'description': '燃油费率,0到1,最多4位小数。',
  65. },
  66. 'conditions': {
  67. 'type': 'array',
  68. 'minItems': 1,
  69. 'maxItems': 30,
  70. 'items': {
  71. 'type': 'object',
  72. 'properties': {
  73. 'condition': {
  74. 'type': 'string',
  75. 'minLength': 1,
  76. 'maxLength': 50,
  77. 'description': '计费条件代码或名称,必须完整匹配。',
  78. },
  79. 'group': {
  80. 'type': 'string',
  81. 'minLength': 1,
  82. 'maxLength': 20,
  83. 'description': '条件组编号,同组条件写同一个组号。',
  84. },
  85. 'condition_value': {
  86. 'type': 'string',
  87. 'minLength': 1,
  88. 'maxLength': 100,
  89. 'description': '条件值代码或名称,必须完整匹配。多选用英文逗号分隔。',
  90. },
  91. 'condition_relation': {
  92. 'type': 'string',
  93. 'enum': ['and', 'or'],
  94. 'description': '与同组上一条的关系。同组第一条不要传。',
  95. },
  96. },
  97. 'required': ['condition', 'group', 'condition_value'],
  98. 'additionalProperties': False,
  99. },
  100. },
  101. },
  102. 'required': ['fuel_ratio', 'conditions'],
  103. 'additionalProperties': False,
  104. },
  105. },
  106. },
  107. 'required': ['price_type', 'start_date', 'items'],
  108. 'additionalProperties': False,
  109. },
  110. }
  111. def call(
  112. self,
  113. price_type,
  114. start_date,
  115. items,
  116. end_date=None,
  117. fuel_no=None,
  118. request_id='rq_save_fuel_rate',
  119. ):
  120. if self.api_client is None:
  121. raise RuntimeError('api client is required for save_fuel_rate')
  122. built_items = _items(items)
  123. payload = {
  124. 'price_type': _choice(price_type, ('1', '2', '销售燃油', '成本燃油'), 'price_type'),
  125. 'start_date': _date(start_date, 'start_date'),
  126. 'items': built_items,
  127. }
  128. if end_date is not None:
  129. payload['end_date'] = _date(end_date, 'end_date')
  130. if fuel_no is not None:
  131. if not isinstance(fuel_no, str) or not fuel_no.strip() or len(fuel_no.strip()) > 20:
  132. raise ValueError('fuel_no is invalid')
  133. if len(built_items) != 1:
  134. raise ValueError('items is invalid')
  135. payload['fuel_no'] = fuel_no.strip()
  136. return self.api_client.call_tool(self.name, self.route_path, payload, request_id)
  137. def _choice(value, choices, key):
  138. if isinstance(value, bool):
  139. raise ValueError(key + ' is invalid')
  140. if isinstance(value, int):
  141. value = str(value)
  142. if not isinstance(value, str) or value.strip() not in choices:
  143. raise ValueError(key + ' is invalid')
  144. return value.strip()
  145. def _date(value, key):
  146. if not isinstance(value, str) or not value.strip() or len(value.strip()) > 30:
  147. raise ValueError(key + ' is invalid')
  148. return value.strip()
  149. def _items(items):
  150. if not isinstance(items, list) or not items or len(items) > 20:
  151. raise ValueError('items is invalid')
  152. rows = []
  153. for item in items:
  154. if not isinstance(item, dict) or set(item) - {'fuel_ratio', 'conditions'}:
  155. raise ValueError('items is invalid')
  156. ratio = item.get('fuel_ratio')
  157. if isinstance(ratio, bool) or isinstance(ratio, int) or isinstance(ratio, float):
  158. ratio = '{0:.4f}'.format(ratio)
  159. if not isinstance(ratio, str) or not ratio.strip() or len(ratio.strip()) > 10:
  160. raise ValueError('fuel_ratio is invalid')
  161. rows.append({
  162. 'fuel_ratio': ratio.strip(),
  163. 'conditions': _conditions(item.get('conditions')),
  164. })
  165. return rows
  166. def _conditions(conditions):
  167. if not isinstance(conditions, list) or not conditions or len(conditions) > 30:
  168. raise ValueError('conditions is invalid')
  169. rows = []
  170. allowed = {'condition', 'group', 'condition_value', 'condition_relation'}
  171. for condition in conditions:
  172. if not isinstance(condition, dict) or set(condition) - allowed:
  173. raise ValueError('conditions is invalid')
  174. row = {
  175. 'condition': _text(condition.get('condition'), 50, 'condition'),
  176. 'group': _text(condition.get('group'), 20, 'group'),
  177. 'condition_value': _text(condition.get('condition_value'), 100, 'condition_value'),
  178. }
  179. relation = condition.get('condition_relation')
  180. if relation is not None:
  181. if relation not in ('and', 'or'):
  182. raise ValueError('condition_relation is invalid')
  183. row['condition_relation'] = relation
  184. rows.append(row)
  185. return rows
  186. def _text(value, max_length, key):
  187. if not isinstance(value, str) or not value.strip() or len(value.strip()) > max_length:
  188. raise ValueError(key + ' is invalid')
  189. return value.strip()