feat: add new folder
This commit is contained in:
86
gig-poc/apps/api/app/domain/models.py
Normal file
86
gig-poc/apps/api/app/domain/models.py
Normal 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())
|
||||
171
gig-poc/apps/api/app/domain/schemas.py
Normal file
171
gig-poc/apps/api/app/domain/schemas.py
Normal 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
|
||||
Reference in New Issue
Block a user