feat: add new folder

This commit is contained in:
Daniel
2026-03-30 20:49:40 +08:00
commit c7788fdd92
64 changed files with 19910 additions and 0 deletions

View File

@@ -0,0 +1,86 @@
from datetime import datetime
from uuid import uuid4
from sqlalchemy import DateTime, Float, ForeignKey, Integer, String, Text, func
from sqlalchemy.dialects.postgresql import JSONB
from sqlalchemy.orm import Mapped, mapped_column, relationship
from app.db.base import Base
class Job(Base):
__tablename__ = "jobs"
id: Mapped[str] = mapped_column(String(64), primary_key=True, default=lambda: f"job_{uuid4().hex[:12]}")
title: Mapped[str] = mapped_column(String(255))
category: Mapped[str] = mapped_column(String(128))
description: Mapped[str] = mapped_column(Text)
city: Mapped[str] = mapped_column(String(64))
region: Mapped[str] = mapped_column(String(64))
location_detail: Mapped[str] = mapped_column(String(255))
start_time: Mapped[datetime] = mapped_column(DateTime(timezone=True))
duration_hours: Mapped[float] = mapped_column(Float)
headcount: Mapped[int] = mapped_column(Integer)
salary_type: Mapped[str] = mapped_column(String(32))
salary_amount: Mapped[float] = mapped_column(Float)
salary_currency: Mapped[str] = mapped_column(String(16), default="CNY")
work_mode: Mapped[str] = mapped_column(String(64))
tags_json: Mapped[list[str]] = mapped_column(JSONB, default=list)
confidence: Mapped[float] = mapped_column(Float)
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
skills: Mapped[list["JobSkill"]] = relationship(back_populates="job", cascade="all, delete-orphan")
class JobSkill(Base):
__tablename__ = "job_skills"
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
job_id: Mapped[str] = mapped_column(ForeignKey("jobs.id", ondelete="CASCADE"), index=True)
skill_name: Mapped[str] = mapped_column(String(128), index=True)
weight: Mapped[float] = mapped_column(Float, default=1.0)
is_required: Mapped[bool] = mapped_column(default=True)
job: Mapped[Job] = relationship(back_populates="skills")
class Worker(Base):
__tablename__ = "workers"
id: Mapped[str] = mapped_column(String(64), primary_key=True, default=lambda: f"worker_{uuid4().hex[:12]}")
name: Mapped[str] = mapped_column(String(128))
description: Mapped[str] = mapped_column(Text)
cities_json: Mapped[list[str]] = mapped_column(JSONB, default=list)
regions_json: Mapped[list[str]] = mapped_column(JSONB, default=list)
availability_json: Mapped[list[str]] = mapped_column(JSONB, default=list)
experience_tags_json: Mapped[list[str]] = mapped_column(JSONB, default=list)
reliability_score: Mapped[float] = mapped_column(Float, default=0.7)
profile_completion: Mapped[float] = mapped_column(Float, default=0.6)
confidence: Mapped[float] = mapped_column(Float, default=0.8)
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
skills: Mapped[list["WorkerSkill"]] = relationship(back_populates="worker", cascade="all, delete-orphan")
class WorkerSkill(Base):
__tablename__ = "worker_skills"
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
worker_id: Mapped[str] = mapped_column(ForeignKey("workers.id", ondelete="CASCADE"), index=True)
skill_name: Mapped[str] = mapped_column(String(128), index=True)
score: Mapped[float] = mapped_column(Float, default=0.7)
worker: Mapped[Worker] = relationship(back_populates="skills")
class MatchRecord(Base):
__tablename__ = "matches"
id: Mapped[str] = mapped_column(String(64), primary_key=True, default=lambda: f"match_{uuid4().hex[:12]}")
source_type: Mapped[str] = mapped_column(String(32), index=True)
source_id: Mapped[str] = mapped_column(String(64), index=True)
target_id: Mapped[str] = mapped_column(String(64), index=True)
match_score: Mapped[float] = mapped_column(Float)
breakdown_json: Mapped[dict] = mapped_column(JSONB)
reasons_json: Mapped[list[str]] = mapped_column(JSONB)
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())

View File

@@ -0,0 +1,171 @@
from __future__ import annotations
from datetime import datetime
from enum import Enum
from pydantic import BaseModel, 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 = SalaryType.daily
amount: float = 0
currency: str = "CNY"
class SkillScore(BaseModel):
name: str
score: float = Field(ge=0, le=1)
class JobCard(BaseModel):
job_id: str
title: str
category: str
description: str
skills: list[str] = Field(default_factory=list)
city: str
region: str
location_detail: str
start_time: datetime
duration_hours: float = Field(gt=0)
headcount: int = Field(gt=0)
salary: Salary
work_mode: str
tags: list[str] = Field(default_factory=list)
confidence: float = Field(ge=0, le=1)
class WorkerCard(BaseModel):
worker_id: str
name: str
description: str
skills: list[SkillScore] = Field(default_factory=list)
cities: list[str] = Field(default_factory=list)
regions: list[str] = Field(default_factory=list)
availability: list[str] = Field(default_factory=list)
experience_tags: list[str] = Field(default_factory=list)
reliability_score: float = Field(ge=0, le=1)
profile_completion: float = Field(ge=0, le=1)
confidence: float = Field(ge=0, le=1)
class MatchBreakdown(BaseModel):
skill_score: float = Field(ge=0, le=1)
region_score: float = Field(ge=0, le=1)
time_score: float = Field(ge=0, le=1)
experience_score: float = Field(ge=0, le=1)
reliability_score: float = Field(ge=0, le=1)
class MatchResult(BaseModel):
match_id: str
source_type: SourceType
source_id: str
target_id: str
match_score: float = Field(ge=0, le=1)
breakdown: MatchBreakdown
reasons: list[str] = Field(default_factory=list, min_length=3)
class ExtractTextRequest(BaseModel):
text: str = Field(min_length=5)
class IngestJobRequest(BaseModel):
job: JobCard
class IngestWorkerRequest(BaseModel):
worker: WorkerCard
class MatchWorkersRequest(BaseModel):
job_id: str | None = None
job: JobCard | None = None
top_n: int = Field(default=10, ge=1, le=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 = None
worker: WorkerCard | None = None
top_n: int = Field(default=10, ge=1, le=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
data: JobCard | WorkerCard | None = None
errors: list[str] = Field(default_factory=list)
missing_fields: list[str] = Field(default_factory=list)
class BootstrapResponse(BaseModel):
jobs: int
workers: int
skills: int
categories: int
regions: int
class HealthStatus(BaseModel):
service: str
database: str
rag: str
timestamp: datetime
class ListResponse(BaseModel):
items: list[dict]
total: int
class MatchResponse(BaseModel):
items: list[MatchResult]
class ExplainResponse(BaseModel):
match: MatchResult
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