demand_feedback.py 2.8 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879
  1. from __future__ import annotations
  2. from typing import Literal
  3. from pydantic import BaseModel, Field, field_validator, model_validator
  4. FeedbackTargetType = Literal["demand", "video", "hit_content"]
  5. FeedbackAction = Literal["support", "oppose", "correct", "supplement"]
  6. REASON_CODES_BY_TARGET: dict[str, set[str]] = {
  7. "demand": {
  8. "not_a_demand",
  9. "wrong_grade",
  10. "wrong_category",
  11. "unclear_wording",
  12. "wrong_reason",
  13. "missing_demand",
  14. "other",
  15. },
  16. "video": {
  17. "irrelevant",
  18. "weak_relevance",
  19. "unavailable",
  20. "duplicate",
  21. "wrong_info",
  22. "missing_video",
  23. "other",
  24. },
  25. "hit_content": {
  26. "not_hit",
  27. "wrong_point_type",
  28. "inaccurate_wording",
  29. "wrong_reason",
  30. "duplicate",
  31. "missing_content",
  32. "other",
  33. },
  34. }
  35. class CreateDemandFeedbackBody(BaseModel):
  36. client_request_id: str = Field(min_length=8, max_length=64)
  37. target_type: FeedbackTargetType
  38. demand_grade_id: int = Field(gt=0)
  39. video_id: str | None = Field(default=None, max_length=64)
  40. demand_video_expansion_id: int | None = Field(default=None, gt=0)
  41. feedback_action: FeedbackAction
  42. reason_code: str | None = Field(default=None, max_length=64)
  43. content: str | None = Field(default=None, max_length=1000)
  44. @field_validator("client_request_id", "video_id", "reason_code", "content")
  45. @classmethod
  46. def strip_text(cls, value: str | None) -> str | None:
  47. if value is None:
  48. return None
  49. stripped = value.strip()
  50. return stripped or None
  51. @model_validator(mode="after")
  52. def validate_target_and_content(self) -> "CreateDemandFeedbackBody":
  53. if self.target_type == "demand":
  54. if self.video_id is not None or self.demand_video_expansion_id is not None:
  55. raise ValueError("需求反馈不能包含视频或命中内容 ID")
  56. elif self.target_type == "video":
  57. if not self.video_id or self.demand_video_expansion_id is not None:
  58. raise ValueError("视频反馈必须且只能包含视频 ID")
  59. elif not self.video_id or self.demand_video_expansion_id is None:
  60. raise ValueError("命中内容反馈必须包含视频 ID 和命中内容 ID")
  61. if self.feedback_action == "oppose" and not self.reason_code:
  62. raise ValueError("不认可反馈必须选择问题原因")
  63. if self.feedback_action in {"correct", "supplement"} and not self.content:
  64. raise ValueError("纠错或补充反馈必须填写说明")
  65. if (
  66. self.reason_code is not None
  67. and self.reason_code not in REASON_CODES_BY_TARGET[self.target_type]
  68. ):
  69. raise ValueError("问题原因不适用于当前反馈对象")
  70. return self