Files
Airtep/gig-poc/apps/api/app/domain/schemas.py
2026-04-07 20:15:19 +08:00

237 lines
9.6 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
from __future__ import annotations
from datetime import datetime, timedelta, timezone
from enum import Enum
from pydantic import BaseModel, ConfigDict, Field, field_validator, model_validator
class SalaryType(str, Enum):
daily = "daily"
hourly = "hourly"
monthly = "monthly"
task = "task"
class SourceType(str, Enum):
job_to_worker = "job_to_worker"
worker_to_job = "worker_to_job"
class Salary(BaseModel):
type: SalaryType = Field(default=SalaryType.daily, description="薪资类型daily/hourly/monthly/task")
amount: float = Field(default=0, description="薪资金额")
currency: str = Field(default="CNY", description="货币类型,默认 CNY")
class SkillScore(BaseModel):
name: str = Field(description="技能名称")
score: float = Field(ge=0, le=1, description="技能熟练度,范围 0~1")
class JobCard(BaseModel):
job_id: str = Field(description="岗位唯一 ID")
title: str = Field(description="岗位标题")
category: str = Field(description="岗位类别")
description: str = Field(description="岗位描述")
skills: list[str] = Field(default_factory=list, description="岗位技能要求列表")
city: str = Field(description="城市")
region: str = Field(description="区域")
location_detail: str = Field(description="详细地点描述")
start_time: datetime = Field(description="岗位开始时间ISO-8601")
duration_hours: float = Field(gt=0, description="工时(小时),必须大于 0")
headcount: int = Field(gt=0, description="招聘人数,必须大于 0")
salary: Salary = Field(description="薪资信息")
work_mode: str = Field(description="工作模式,如兼职、全职、活动")
tags: list[str] = Field(default_factory=list, description="业务标签列表")
confidence: float = Field(ge=0, le=1, description="数据置信度,范围 0~1")
@field_validator("start_time", mode="after")
@classmethod
def normalize_start_time(cls, value: datetime) -> datetime:
shanghai_tz = timezone(timedelta(hours=8))
if value.tzinfo is None:
value = value.replace(tzinfo=shanghai_tz)
else:
value = value.astimezone(shanghai_tz)
return value.replace(second=0, microsecond=0)
class WorkerCard(BaseModel):
worker_id: str = Field(description="工人唯一 ID")
name: str = Field(description="工人姓名或昵称")
description: str = Field(description="工人自我描述")
skills: list[SkillScore] = Field(default_factory=list, description="技能及熟练度列表")
cities: list[str] = Field(default_factory=list, description="可接单城市列表")
regions: list[str] = Field(default_factory=list, description="可接单区域列表")
availability: list[str] = Field(default_factory=list, description="可上岗时间描述")
experience_tags: list[str] = Field(default_factory=list, description="经验标签列表")
reliability_score: float = Field(ge=0, le=1, description="履约可靠性分,范围 0~1")
profile_completion: float = Field(ge=0, le=1, description="档案完善度,范围 0~1")
confidence: float = Field(ge=0, le=1, description="数据置信度,范围 0~1")
class MatchBreakdown(BaseModel):
skill_score: float = Field(ge=0, le=1, description="技能匹配分,范围 0~1")
region_score: float = Field(ge=0, le=1, description="地域匹配分,范围 0~1")
time_score: float = Field(ge=0, le=1, description="时间匹配分,范围 0~1")
experience_score: float = Field(ge=0, le=1, description="经验匹配分,范围 0~1")
reliability_score: float = Field(ge=0, le=1, description="可靠性匹配分,范围 0~1")
class MatchResult(BaseModel):
match_id: str = Field(description="匹配记录 ID")
source_type: SourceType = Field(description="匹配方向job_to_worker 或 worker_to_job")
source_id: str = Field(description="源实体 ID")
target_id: str = Field(description="目标实体 ID")
match_score: float = Field(ge=0, le=1, description="综合匹配分,范围 0~1")
breakdown: MatchBreakdown = Field(description="多维打分拆解")
reasons: list[str] = Field(default_factory=list, min_length=3, description="匹配理由,至少 3 条")
class ExtractTextRequest(BaseModel):
text: str = Field(min_length=5, description="待抽取的自然语言文本,最少 5 个字符")
model_config = ConfigDict(
json_schema_extra={
"example": {
"text": "明天下午南山会展中心需要2个签到协助5小时150/人,女生优先",
}
}
)
class IngestJobRequest(BaseModel):
job: JobCard = Field(description="岗位卡片对象")
class IngestWorkerRequest(BaseModel):
worker: WorkerCard = Field(description="工人卡片对象")
class MatchWorkersRequest(BaseModel):
job_id: str | None = Field(default=None, description="岗位 ID与 job 二选一)")
job: JobCard | None = Field(default=None, description="内联岗位对象(与 job_id 二选一)")
top_n: int = Field(default=10, ge=1, le=50, description="返回条数,范围 1~50")
@model_validator(mode="after")
def validate_source(self) -> "MatchWorkersRequest":
if not self.job_id and not self.job:
raise ValueError("job_id 或 job 至少需要提供一个")
return self
class MatchJobsRequest(BaseModel):
worker_id: str | None = Field(default=None, description="工人 ID与 worker 二选一)")
worker: WorkerCard | None = Field(default=None, description="内联工人对象(与 worker_id 二选一)")
top_n: int = Field(default=10, ge=1, le=50, description="返回条数,范围 1~50")
@model_validator(mode="after")
def validate_source(self) -> "MatchJobsRequest":
if not self.worker_id and not self.worker:
raise ValueError("worker_id 或 worker 至少需要提供一个")
return self
class ExtractResponse(BaseModel):
success: bool = Field(description="抽取是否成功")
data: JobCard | WorkerCard | None = Field(default=None, description="抽取结果对象,可能为空")
errors: list[str] = Field(default_factory=list, description="错误信息列表")
missing_fields: list[str] = Field(default_factory=list, description="缺失字段列表")
class BootstrapResponse(BaseModel):
jobs: int = Field(description="导入岗位数量")
workers: int = Field(description="导入工人数量")
skills: int = Field(description="技能词条数量")
categories: int = Field(description="类目数量")
regions: int = Field(description="区域数量")
class HealthStatus(BaseModel):
service: str = Field(description="服务状态,通常为 ok")
database: str = Field(description="数据库状态ok 或 error")
rag: str = Field(description="RAG 组件状态ok 或 error")
timestamp: datetime = Field(description="服务端当前时间")
class ListResponse(BaseModel):
items: list[dict] = Field(description="列表项")
total: int = Field(description="总数")
class MatchResponse(BaseModel):
items: list[MatchResult] = Field(description="匹配结果列表")
class ExplainResponse(BaseModel):
match: MatchResult = Field(description="单条匹配结果详情")
class MatchFeedbackRequest(BaseModel):
match_id: str = Field(description="匹配记录 ID")
accepted: bool = Field(description="反馈是否接受该推荐")
class MatchWeightResponse(BaseModel):
weights: dict[str, float] = Field(description="当前生效的排序权重")
learning_enabled: bool = Field(description="是否开启在线学习")
class AIObservabilityResponse(BaseModel):
metrics: dict[str, float | int] = Field(description="AI 调用观测指标")
class IngestAsyncResponse(BaseModel):
task_id: str = Field(description="异步任务 ID")
status: str = Field(description="任务状态")
class QueueStatusResponse(BaseModel):
queued: int = Field(description="当前队列中任务数量")
processed: int = Field(description="历史处理成功数量")
failed: int = Field(description="历史处理失败数量")
class MatchAsyncWorkersRequest(BaseModel):
job_id: str = Field(description="岗位 ID")
top_n: int = Field(default=10, ge=1, le=50, description="返回条数,范围 1~50")
class MatchAsyncJobsRequest(BaseModel):
worker_id: str = Field(description="工人 ID")
top_n: int = Field(default=10, ge=1, le=50, description="返回条数,范围 1~50")
class MatchAsyncResponse(BaseModel):
task_id: str = Field(description="异步任务 ID")
status: str = Field(description="任务状态")
items: list[MatchResult] | None = Field(default=None, description="任务完成后返回的匹配结果")
class SystemOpsResponse(BaseModel):
traffic: dict[str, float | int] = Field(description="全局流量护栏与错误窗口指标")
cache: dict[str, float | int | str] = Field(description="缓存命中与大小")
ingest_queue: QueueStatusResponse = Field(description="异步入库队列状态")
match_queue: QueueStatusResponse = Field(description="异步匹配队列状态")
class PromptOutput(BaseModel):
content: dict
raw_text: str
class QueryFilters(BaseModel):
entity_type: str
city: str | None = None
region: str | None = None
categories: list[str] = Field(default_factory=list)
tags: list[str] = Field(default_factory=list)
skills: list[str] = Field(default_factory=list)
@field_validator("entity_type")
@classmethod
def validate_entity_type(cls, value: str) -> str:
if value not in {"job", "worker"}:
raise ValueError("entity_type must be job or worker")
return value