""" 项目数据推送相关schema """ from typing import List, Optional from fastapi import HTTPException from pydantic import Field, BaseModel, field_validator from ..common.push_system import BaseUpdateRequestSchema from ..common.base import BaseResponseWithID from ..common.datetime import get_current_date from ..common.validators import DateStr from .enums import ProgressType, KeyConstraints, DBTCSCORE class ProjectResponseBase(BaseResponseWithID): """ 项目基础信息, 复制自project_model 不进行继承,通过检查器确保字段和模型一致 """ # 基础信息-不允许更改 id : str = Field(..., description="项目ID", examples=["uuid"]) project_type: str = Field("", description="项目类型") region_name: List[str] = Field(default_factory=list, description="项目所属区域名称", examples=[["华东", "江苏"]]) region_path: List[str] = Field(default_factory=list, description="项目所属区域路径", examples=[["110000", "120000", "120001"]]) province: str = Field("", description="省份(region_path第一个)", examples=["110000"]) is_domestic :bool=Field(True, description="是否是国内项目") company_id: str = Field("", description="所属公司ID", examples=["company123"]) company_name: str = Field("", description="所属公司名称", examples=["国电投江苏公司"]) syxs: Optional[str] = Field(None, description="升压站型式") sub_type: Optional[str] = Field(None, description="光伏类别") scgc_type: Optional[str] = Field(None, description="送出工程建设方式") # 基础信息-允许更改 address: str = Field("", description="项目地址", examples=["某省某市某区"]) code: str = Field("", description="项目编码", examples=["XM2025001"]) name: str = Field(..., description="项目名称", examples=["XX新能源项目"]) short_name: str = Field("", description="项目简称", examples=["XX项目"]) manager_name: str = Field("", description="负责人姓名", examples=["张三"]) manager_phone: str = Field("", description="负责人电话", examples=["13800000000"]) # 容量信息 authorization_date: Optional[DateStr] = Field(None, description="核准日期", examples=["2024-01-01"]) authorized_capacity: float = Field(0.0, description="核准容量(万千瓦)", examples=[10.0]) # 投决 investment_decision_capacity: Optional[float] = Field(None, description="投资决策容量", examples=[8.0]) investment_decision_date: Optional[DateStr] = Field(None, description="投资决策日期", examples=["2024-02-01"]) # 开工 plan_construction_capacity: float = Field(0.0, description="本年度计划开工容量", examples=[5.0]) plan_construction_date: Optional[DateStr] = Field(None, description="本年度计划开工日期", examples=["2024-03-01"]) actual_construction_capacity: float = Field(0.0, description="本年度实际开工容量", examples=[4.0]) actual_construction_date: Optional[DateStr] = Field(None, description="本年度实际开工日期", examples=["2024-04-01"]) accumulated_construction_capacity: float = Field(0.0, description="累计开工容量", examples=[12.0]) constructing_capacity: float = Field(0.0, description="在建容量", examples=[6.0]) # 投产 ensure_production_capacity: float = Field(0.0, description="本年度确保投产容量", examples=[3.0]) challenge_production_capacity: float = Field(0.0, description="本年度揭榜投产容量", examples=[2.0]) climb_production_capacity: float = Field(0.0, description="本年度登高投产容量", examples=[1.0]) plan_production_date: Optional[DateStr] = Field(None, description="计划投产日期", examples=["2024-08-01"]) plan_full_production_date: Optional[DateStr] = Field(None, description="计划全部投产日期", examples=["2024-09-01"]) actual_production_date: Optional[DateStr] = Field(None, description="实际投产日期", examples=["2024-08-15"]) actual_full_production_date: Optional[DateStr] = Field(None, description="实际全投日期", examples=["2024-09-10"]) production_capacity: Optional[float] = Field(0.0, description="本年度投产容量", examples=[2.0]) production_date: Optional[DateStr] = Field(None, description="本年度首批投产日期", examples=["2024-09-01"]) total_production_capacity: Optional[float] = Field(0.0, description="全部投产容量", examples=[2.0]) # 投资 dynamic_investment: float = Field(0.0, description="动态总投资", examples=[100000.0]) complete_investment: float = Field(0.0, description="完成投资", examples=[80000.0]) # 其他信息 progress_type: ProgressType | None = Field(None, description="推进分类", examples=[ProgressType.A]) key_constraints: KeyConstraints = Field(KeyConstraints.NONE, description="关键制约", examples=[KeyConstraints.NONE]) key_constraints_remark: Optional[str] = Field("", description="关键制约备注", examples=["无"]) status: Optional[str] = Field(None, description="项目状态") apply_file_ids: List[str] = Field(default_factory=list, description="申请表附件ID列表", examples=[[]]) # 拓展信息 dbtc_score_list: Optional[list] = Field(default_factory=list, description='达标投产评分列表') # 这里项目用的是list而不是List class ProjectResponseBaseList(BaseModel): page: int page_size: int total: int list: List[ProjectResponseBase] = [] # ==================== Update ==================== class KeyConstraintsRemarkMixin: """关键制约备注校验混入类""" @field_validator("key_constraints_remark") def validate_key_constraints_remark(cls, v): if v is None: return v if len(v) > 500: raise HTTPException(detail="关键制约备注不能超过500个字符", status_code=400) return v class AuthorizationDateMixin: """关键制约备注校验混入类""" @field_validator("authorization_date") def validate_actual_date(cls, v): if v is None: return v if v > get_current_date(): raise HTTPException(detail='核准/备案日期不能大于今天', status_code=400) return v class ProjectBaseInfoUpdate(BaseModel, KeyConstraintsRemarkMixin, AuthorizationDateMixin): """ 项目基本信息 PROJECT_BASE_INFO """ # 基础信息-允许更改 id: str = Field(..., description="项目id", examples=["uuid"]) address: str | None = Field(None, description="项目地址", examples=["某省某市某区"]) code: str | None = Field(None, description="项目编码", examples=["XM2025001"]) name: str = Field(..., description="项目名称", examples=["XX新能源项目"]) short_name: str | None = Field(None, description="项目简称", examples=["XX项目"]) manager_name: str | None = Field(None, description="负责人姓名", examples=["张三"]) manager_phone: str | None = Field(None, description="负责人电话", examples=["13800000000"]) # 其他信息 progress_type: ProgressType = Field(..., description="推进分类", examples=[ProgressType.A]) key_constraints: KeyConstraints | None = Field(None, description="关键制约", examples=[KeyConstraints.NONE]) key_constraints_remark: str | None = Field(None, description="关键制约备注", examples=["无"]) apply_file_ids: List[str] | None = Field(None, description="申请表附件ID列表", examples=[[]]) # 拓展信息 dbtc_score_list: list[DBTCSCORE] | None = Field(None, description='达标投产评分列表') # 这里项目用的是list而不是List # 核准 authorization_date: DateStr | None = Field(None, description="核准日期", examples=["2024-01-01"]) authorized_capacity: float | None = Field(None, description="核准容量(万千瓦)", examples=[10.0]) class _ProductionPlanNode(BaseModel): year: int = Field(..., description="计划年份", examples=[2024]) capacity: float = Field(..., description="确保投产容量", examples=[3.0]) class ProjectEnsureCapacity(BaseModel): """ PROJECT_ENSURE_CAPACITY 填写项目确保投产容量列表,系统根据列表自动计算ensure_production_capacity字段 """ # 确保投产容量 ensure_production_capacity_list : list[_ProductionPlanNode] = Field(..., description="确保投产容量列表(每次必须填写全部)") class ProjectClimbCapacity(BaseModel): """ PROJECT_CLIMB_CAPACITY 填写项目登高投产容量列表,系统根据列表自动计算climb_production_capacity字段 """ # 登高投产容量 climb_production_capacity_list : list[_ProductionPlanNode] = Field(..., description="登高投产容量列表(每次必须填写全部)") class ProjectChallengeCapacity(BaseModel): """ PROJECT_CHALLENGE_CAPACITY 填写项目揭榜投产容量列表,系统根据列表自动计算challenge_production_capacity字段 """ # 揭榜投产容量 challenge_production_capacity_list : list[_ProductionPlanNode] = Field(..., description="揭榜投产容量列表(每次必须填写全部)") class _ActualNodeData(BaseModel): capacity: float = Field(..., description="实际节点容量", examples=[4.0]) date: DateStr = Field(..., description="实际节点日期", examples=["2024-04-01"]) class ProjectActualConstruction(BaseModel): """ PROJECT_ACTUAL_CONSTRUCTION 项目实际开工节点 系统根据实际开工节点自动计算以下字段: actual_construction_capacity 本年度实际开工容量 actual_construction_date 本年度实际开工日期 accumulated_construction_capacity 累计开工容量 constructing_capacity 在建容量 """ actual_construction_list : list[_ActualNodeData] | None = Field(None, description="实际开工节点列表(每次必须填写全部)") class _PlanNodeData(BaseModel): capacity: float = Field(..., description="计划节点容量", examples=[5.0]) date: DateStr = Field(..., description="本年度计日期", examples=["2024-03-01"]) @field_validator("capacity") def validate_capacity(cls, v: float) -> float: if v < 0: raise HTTPException(status_code=400, detail="节点容量必须大于等于0") return v class ProjectPlanConstruction(BaseModel): """ PROJECT_PLAN_CONSTRUCTION 项目计划开工节点 系统根据计划开工节点自动计算: plan_construction_capacity 本年度计划开工容量 plan_construction_date 本年度计划开工日期 """ plan_construction_list : list[_PlanNodeData] | None = Field(None, description="计划开工节点列表(每次必须填写全部)") class ProjectInvestmentDecision(BaseModel): """ PROJECT_INVESTMENT_DECISION 投资决策容量和日期 """ investment_decision_capacity: float = Field(..., description="投资决策容量", examples=[8.0]) investment_decision_date: DateStr = Field(..., description="投资决策日期", examples=["2024-02-01"]) @field_validator("investment_decision_date") def validate_investment_decision_date(cls, v): if v is None: return v if v > get_current_date(): raise HTTPException(detail='投资决策日期不能大于今天', status_code=400) return v @field_validator("investment_decision_capacity") def validate_investment_decision_capacity(cls, v: float) -> float: if v < 0: raise HTTPException(status_code=400, detail="投资决策容量必须大于等于0") return v class ProjectKeyConstraints(BaseModel, KeyConstraintsRemarkMixin): """ PROJECT_KEY_CONSTRAINTS 关键制约 """ key_constraints: KeyConstraints | None = Field(None, description="关键制约", examples=[KeyConstraints.NONE]) key_constraints_remark: str | None = Field(None, description="关键制约备注", examples=["无"]) class _ActualProductionNode(BaseModel): capacity: float = Field(..., description="实际投产容量", examples=[2.0]) date: DateStr = Field(..., description="实际投产日期", examples=["2024-08-15"]) @field_validator("capacity") def validate_capacity(cls, v: float) -> float: if v < 0: raise HTTPException(status_code=400, detail="实际投产容量必须大于等于0") return v @field_validator("date") def validate_date(cls, v): if v is None: return v if v > get_current_date(): raise HTTPException(detail='实际投产日期不能大于今天', status_code=400) return v class ProjectProductionCapacity(BaseModel): """ PROJECT_PRODUCTION_CAPACITY 填写项目投产数据 每个项目在各年份或一年内可能有多次实际投产 系统会根据投产节点列表数据,自动计算 production_capacity 本年度投产容量 production_date 本年度首批投产日期 total_production_capacity 全部投产容量 """ plan_production_date: DateStr | None = Field(None, description="集团公司计划投产日期", examples=["2024-08-01"]) plan_full_production_date: DateStr | None = Field(None, description="集团公司计划全部投产日期", examples=["2024-09-01"]) ic_plan_first_production_date: DateStr | None = Field(None, description="区域公司计划首批投产日期(只有新能源需要)",examples=["2024-08-01"]) ic_plan_full_production_date: DateStr | None = Field(None, description="区域公司计划全投日期(只有新能源需要)",examples=["2024-09-01"]) actual_production_date: DateStr | None = Field(None, description="实际投产日期(只有新能源需要)", examples=["2024-08-15"]) actual_full_production_date: DateStr | None = Field(None, description="实际全投日期(只有新能源需要)", examples=["2024-09-10"]) actual_production_list : list[_ActualProductionNode] | None = Field(None, description="实际投产节点列表(每次必须填写全部)(只有新能源需要)") @field_validator("actual_production_date") def validate_actual_production_date(cls, v): if v is None: return v if v > get_current_date(): raise HTTPException(detail='实际投产日期不能大于今天', status_code=400) return v @field_validator("actual_full_production_date") def validate_actual_full_production_date(cls, v): if v is None: return v if v > get_current_date(): raise HTTPException(detail='实际全投日期不能大于今天', status_code=400) return v class ProjectCompleteInvestment(BaseModel): """ PROJECT_COMPLETE_INVESTMENT 完成投资投资 """ complete_investment: float = Field(..., description="完成投资", examples=[80000.0]) @field_validator("complete_investment") def validate_complete_investment(cls, v: float) -> float: if v < 0: raise HTTPException(status_code=400,detail="完成投资必须大于等于0") return v class ProjectSettlementInvestment(BaseModel): """ PROJECT_SETTLEMENT_INVESTMENT 合同结算投资情况 只有抽蓄-水电-煤电-气电项目类型可操作 """ implementation_estimate: float = Field(..., description="执行概算", examples=[80000.0]) static_total_investment: float = Field(..., description="静态总投资", examples=[80000.0]) completed_investment: float = Field(..., description="完成投资", examples=[80000.0]) dynamic_investment: float = Field(..., description="动态总投资", examples=[80000.0]) @field_validator("implementation_estimate") def validate_implementation_estimate(cls, v: float) -> float: if v < 0: raise HTTPException(status_code=400,detail="执行概算必须大于等于0") return v @field_validator("static_total_investment") def validate_static_total_investment(cls, v: float) -> float: if v < 0: raise HTTPException(status_code=400, detail="静态总投资必须大于等于0") return v @field_validator("completed_investment") def validate_completed_investment(cls, v: float) -> float: if v < 0: raise HTTPException(status_code=400, detail="完成投资必须大于等于0") return v @field_validator("dynamic_investment") def validate_dynamic_investment(cls, v: float) -> float: if v < 0: raise HTTPException(status_code=400, detail="动态总投资必须大于等于0") return v class ProjectDynamicInvestment(BaseModel): """ PROJECT_DYNAMIC_INVESTMENT """ # 动态总投资 dynamic_investment: float | None = Field(None, description="动态总投资", examples=[100000.0]) @field_validator("dynamic_investment") def validate_dynamic_investment(cls, v: float) -> float: if v < 0: raise HTTPException(status_code=400, detail="动态总投资必须大于等于0") return v # ======================= 水电抽蓄专用 ============================= # ==================== 枚举说明 用于文档自动生成 ====================