Merge branch 'main' of https://git.1024tool.vip/zfc/guzhi
* 'main' of https://git.1024tool.vip/zfc/guzhi: 调整估值与交易逻辑 feat: 更新邮件客户端和评估状态处理逻辑
This commit is contained in:
commit
9d08b3d2cb
@ -116,9 +116,13 @@ async def create_with_receipt(payload: AppCreateInvoiceWithReceipt, current_user
|
|||||||
wechat=getattr(current_user, "alias", None),
|
wechat=getattr(current_user, "alias", None),
|
||||||
)
|
)
|
||||||
inv = await invoice_controller.create(inv_data)
|
inv = await invoice_controller.create(inv_data)
|
||||||
receipt = await invoice_controller.create_receipt(inv.id, PaymentReceiptCreate(url=payload.receipt_url, note=payload.note))
|
if payload.receipt_url:
|
||||||
detail = await invoice_controller.get_receipt_by_id(receipt.id)
|
receipt = await invoice_controller.create_receipt(inv.id, PaymentReceiptCreate(url=payload.receipt_url, note=payload.note))
|
||||||
return Success(data=detail, msg="创建并上传成功")
|
detail = await invoice_controller.get_receipt_by_id(receipt.id)
|
||||||
|
return Success(data=detail, msg="创建并上传成功")
|
||||||
|
else:
|
||||||
|
out = await invoice_controller.get_out(inv.id)
|
||||||
|
return Success(data=out.model_dump() if out else {}, msg="创建成功,未上传凭证")
|
||||||
@app_invoices_router.get("/headers/list", summary="我的抬头列表(分页)", response_model=PageResponse[InvoiceHeaderOut])
|
@app_invoices_router.get("/headers/list", summary="我的抬头列表(分页)", response_model=PageResponse[InvoiceHeaderOut])
|
||||||
async def get_my_headers_paged(page: int = Query(1, ge=1), page_size: int = Query(10, ge=1, le=100), current_user: AppUser = Depends(get_current_app_user)):
|
async def get_my_headers_paged(page: int = Query(1, ge=1), page_size: int = Query(10, ge=1, le=100), current_user: AppUser = Depends(get_current_app_user)):
|
||||||
qs = invoice_controller.model_header.filter(app_user_id=current_user.id) if hasattr(invoice_controller, "model_header") else None
|
qs = invoice_controller.model_header.filter(app_user_id=current_user.id) if hasattr(invoice_controller, "model_header") else None
|
||||||
|
|||||||
@ -17,6 +17,8 @@ async def list_app_users(
|
|||||||
phone: Optional[str] = Query(None),
|
phone: Optional[str] = Query(None),
|
||||||
wechat: Optional[str] = Query(None),
|
wechat: Optional[str] = Query(None),
|
||||||
id: Optional[str] = Query(None),
|
id: Optional[str] = Query(None),
|
||||||
|
created_start: Optional[str] = Query(None),
|
||||||
|
created_end: Optional[str] = Query(None),
|
||||||
created_at: Optional[List[int]] = Query(None),
|
created_at: Optional[List[int]] = Query(None),
|
||||||
page: int = Query(1, ge=1),
|
page: int = Query(1, ge=1),
|
||||||
page_size: int = Query(10, ge=1, le=100),
|
page_size: int = Query(10, ge=1, le=100),
|
||||||
@ -28,7 +30,28 @@ async def list_app_users(
|
|||||||
qs = qs.filter(phone__icontains=phone)
|
qs = qs.filter(phone__icontains=phone)
|
||||||
if wechat:
|
if wechat:
|
||||||
qs = qs.filter(alias__icontains=wechat)
|
qs = qs.filter(alias__icontains=wechat)
|
||||||
if created_at and len(created_at) == 2:
|
if created_start or created_end:
|
||||||
|
def _parse_dt(s: Optional[str]):
|
||||||
|
if not s:
|
||||||
|
return None
|
||||||
|
s = s.replace('+', ' ').strip()
|
||||||
|
try:
|
||||||
|
return datetime.strptime(s, "%Y-%m-%d %H:%M:%S")
|
||||||
|
except Exception:
|
||||||
|
try:
|
||||||
|
ms = float(s)
|
||||||
|
return datetime.fromtimestamp(ms / 1000)
|
||||||
|
except Exception:
|
||||||
|
return None
|
||||||
|
start_dt = _parse_dt(created_start)
|
||||||
|
end_dt = _parse_dt(created_end)
|
||||||
|
if start_dt and end_dt:
|
||||||
|
qs = qs.filter(created_at__gte=start_dt, created_at__lte=end_dt)
|
||||||
|
elif start_dt:
|
||||||
|
qs = qs.filter(created_at__gte=start_dt)
|
||||||
|
elif end_dt:
|
||||||
|
qs = qs.filter(created_at__lte=end_dt)
|
||||||
|
elif created_at and len(created_at) == 2:
|
||||||
start_dt = datetime.fromtimestamp(created_at[0] / 1000)
|
start_dt = datetime.fromtimestamp(created_at[0] / 1000)
|
||||||
end_dt = datetime.fromtimestamp(created_at[1] / 1000)
|
end_dt = datetime.fromtimestamp(created_at[1] / 1000)
|
||||||
qs = qs.filter(created_at__gte=start_dt, created_at__lte=end_dt)
|
qs = qs.filter(created_at__gte=start_dt, created_at__lte=end_dt)
|
||||||
|
|||||||
@ -141,8 +141,7 @@ async def get_quota(current_user: AppUser = Depends(get_current_app_user)):
|
|||||||
- 若后续接入配额系统,可从数据库中读取真实值
|
- 若后续接入配额系统,可从数据库中读取真实值
|
||||||
"""
|
"""
|
||||||
remaining_count = current_user.remaining_quota
|
remaining_count = current_user.remaining_quota
|
||||||
user_type = "体验用户"
|
return Success(data={"remaining_count": remaining_count})
|
||||||
return Success(data={"remaining_count": remaining_count, "user_type": user_type})
|
|
||||||
|
|
||||||
|
|
||||||
@router.put("/profile", response_model=BasicResponse[dict], summary="更新用户信息")
|
@router.put("/profile", response_model=BasicResponse[dict], summary="更新用户信息")
|
||||||
|
|||||||
@ -232,6 +232,7 @@ async def _perform_valuation_calculation(user_id: int, data: UserValuationCreate
|
|||||||
market_value_c=calculation_result.get('market_value_c'),
|
market_value_c=calculation_result.get('market_value_c'),
|
||||||
final_value_ab=calculation_result.get('final_value_ab'),
|
final_value_ab=calculation_result.get('final_value_ab'),
|
||||||
calculation_result=calculation_result,
|
calculation_result=calculation_result,
|
||||||
|
status='pending',
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
logger.info(
|
logger.info(
|
||||||
@ -468,7 +469,6 @@ async def calculate_valuation(
|
|||||||
operator_name=user.alias or user.username or user.phone or "",
|
operator_name=user.alias or user.username or user.phone or "",
|
||||||
before_count=before,
|
before_count=before,
|
||||||
after_count=before - 1,
|
after_count=before - 1,
|
||||||
op_type="consume",
|
|
||||||
remark="发起估值"
|
remark="发起估值"
|
||||||
)
|
)
|
||||||
except Exception:
|
except Exception:
|
||||||
|
|||||||
@ -74,6 +74,15 @@ async def send_email(payload: SendEmailRequest = Body(...)):
|
|||||||
|
|
||||||
attachments = []
|
attachments = []
|
||||||
urls = []
|
urls = []
|
||||||
|
try:
|
||||||
|
domain = payload.email.split("@")[-1]
|
||||||
|
import dns.resolver
|
||||||
|
try:
|
||||||
|
dns.resolver.resolve(domain, "MX")
|
||||||
|
except Exception:
|
||||||
|
dns.resolver.resolve(domain, "A")
|
||||||
|
except Exception:
|
||||||
|
raise HTTPException(status_code=400, detail="收件方地址域名不可用或未正确解析")
|
||||||
if payload.file_urls:
|
if payload.file_urls:
|
||||||
urls.extend([u.strip().strip('`') for u in payload.file_urls if isinstance(u, str)])
|
urls.extend([u.strip().strip('`') for u in payload.file_urls if isinstance(u, str)])
|
||||||
if urls:
|
if urls:
|
||||||
@ -94,7 +103,7 @@ async def send_email(payload: SendEmailRequest = Body(...)):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
result = {"status": "FAIL", "error": str(e)}
|
result = {"status": "FAIL", "error": str(e)}
|
||||||
|
|
||||||
body_summary = payload.body[:500]
|
body_summary = payload.body
|
||||||
status = result.get("status")
|
status = result.get("status")
|
||||||
error = result.get("error")
|
error = result.get("error")
|
||||||
first_name = attachments[0][1] if attachments else None
|
first_name = attachments[0][1] if attachments else None
|
||||||
@ -113,14 +122,22 @@ async def send_email(payload: SendEmailRequest = Body(...)):
|
|||||||
else:
|
else:
|
||||||
logger.error("transactions.email_send_fail email={} err={}", payload.email, error)
|
logger.error("transactions.email_send_fail email={} err={}", payload.email, error)
|
||||||
|
|
||||||
if status == "OK" and data.receipt_id:
|
if status == "OK" and payload.receipt_id:
|
||||||
try:
|
try:
|
||||||
r = await PaymentReceipt.filter(id=data.receipt_id).first()
|
r = await PaymentReceipt.filter(id=payload.receipt_id).first()
|
||||||
if r:
|
if r:
|
||||||
r.extra = (r.extra or {}) | data.model_dump()
|
r.extra = (r.extra or {}) | payload.model_dump()
|
||||||
await r.save()
|
await r.save()
|
||||||
|
try:
|
||||||
|
inv = await r.invoice
|
||||||
|
if inv:
|
||||||
|
inv.status = "invoiced"
|
||||||
|
await inv.save()
|
||||||
|
logger.info("transactions.invoice_mark_invoiced receipt_id={} invoice_id={}", payload.receipt_id, inv.id)
|
||||||
|
except Exception as e2:
|
||||||
|
logger.warning("transactions.invoice_mark_invoiced_fail receipt_id={} err={}", payload.receipt_id, str(e2))
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error("transactions.email_extra_save_fail id={} err={}", data.receipt_id, str(e))
|
logger.error("transactions.email_extra_save_fail id={} err={}", payload.receipt_id, str(e))
|
||||||
|
|
||||||
return Success(data={"status": status, "log_id": log.id, "error": error}, msg="发送成功" if status == "OK" else "发送失败")
|
return Success(data={"status": status, "log_id": log.id, "error": error}, msg="发送成功" if status == "OK" else "发送失败")
|
||||||
|
|
||||||
|
|||||||
@ -120,7 +120,8 @@ async def get_valuations(
|
|||||||
audited_start: Optional[str] = Query(None, description="审核时间开始(证书修改时间,毫秒或ISO)"),
|
audited_start: Optional[str] = Query(None, description="审核时间开始(证书修改时间,毫秒或ISO)"),
|
||||||
audited_end: Optional[str] = Query(None, description="审核时间结束(证书修改时间,毫秒或ISO)"),
|
audited_end: Optional[str] = Query(None, description="审核时间结束(证书修改时间,毫秒或ISO)"),
|
||||||
page: int = Query(1, ge=1, description="页码"),
|
page: int = Query(1, ge=1, description="页码"),
|
||||||
size: int = Query(10, ge=1, le=100, description="每页数量")
|
size: int = Query(10, ge=1, le=100, description="每页数量"),
|
||||||
|
page_size: Optional[int] = Query(None, alias="page_size", ge=1, le=100, description="每页数量")
|
||||||
):
|
):
|
||||||
"""获取估值评估列表,支持筛选和分页"""
|
"""获取估值评估列表,支持筛选和分页"""
|
||||||
query = ValuationAssessmentQuery(
|
query = ValuationAssessmentQuery(
|
||||||
@ -136,7 +137,7 @@ async def get_valuations(
|
|||||||
audited_start=audited_start,
|
audited_start=audited_start,
|
||||||
audited_end=audited_end,
|
audited_end=audited_end,
|
||||||
page=page,
|
page=page,
|
||||||
size=size
|
size=page_size if page_size is not None else size
|
||||||
)
|
)
|
||||||
result = await valuation_controller.get_list(query)
|
result = await valuation_controller.get_list(query)
|
||||||
import json
|
import json
|
||||||
@ -155,10 +156,11 @@ async def get_valuations(
|
|||||||
async def search_valuations(
|
async def search_valuations(
|
||||||
keyword: str = Query(..., description="搜索关键词"),
|
keyword: str = Query(..., description="搜索关键词"),
|
||||||
page: int = Query(1, ge=1, description="页码"),
|
page: int = Query(1, ge=1, description="页码"),
|
||||||
size: int = Query(10, ge=1, le=100, description="每页数量")
|
size: int = Query(10, ge=1, le=100, description="每页数量"),
|
||||||
|
page_size: Optional[int] = Query(None, alias="page_size", ge=1, le=100, description="每页数量")
|
||||||
):
|
):
|
||||||
"""根据关键词搜索估值评估记录"""
|
"""根据关键词搜索估值评估记录"""
|
||||||
result = await valuation_controller.search(keyword, page, size)
|
result = await valuation_controller.search(keyword, page, page_size if page_size is not None else size)
|
||||||
import json
|
import json
|
||||||
items = [json.loads(item.model_dump_json()) for item in result.items]
|
items = [json.loads(item.model_dump_json()) for item in result.items]
|
||||||
return SuccessExtra(
|
return SuccessExtra(
|
||||||
|
|||||||
@ -490,6 +490,11 @@ class ValuationController:
|
|||||||
update_data['audited_at'] = datetime.now()
|
update_data['audited_at'] = datetime.now()
|
||||||
await valuation.update_from_dict(update_data)
|
await valuation.update_from_dict(update_data)
|
||||||
await valuation.save()
|
await valuation.save()
|
||||||
|
from datetime import datetime
|
||||||
|
valuation.status = update_data.get("status", "pending")
|
||||||
|
if not getattr(valuation, "audited_at", None):
|
||||||
|
valuation.audited_at = datetime.now()
|
||||||
|
await valuation.save()
|
||||||
|
|
||||||
out = ValuationAssessmentOut.model_validate(valuation)
|
out = ValuationAssessmentOut.model_validate(valuation)
|
||||||
return await self._attach_user_phone(out)
|
return await self._attach_user_phone(out)
|
||||||
@ -643,7 +648,7 @@ class ValuationController:
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
update_data = {"status": "approved", "audited_at": datetime.now()}
|
update_data = {"status": "pending", "audited_at": datetime.now()}
|
||||||
if admin_notes:
|
if admin_notes:
|
||||||
update_data["admin_notes"] = admin_notes
|
update_data["admin_notes"] = admin_notes
|
||||||
|
|
||||||
|
|||||||
@ -82,7 +82,7 @@ class ValuationAssessment(Model):
|
|||||||
|
|
||||||
# 系统字段
|
# 系统字段
|
||||||
user = fields.ForeignKeyField("models.AppUser", related_name="valuations", description="提交用户")
|
user = fields.ForeignKeyField("models.AppUser", related_name="valuations", description="提交用户")
|
||||||
status = fields.CharField(max_length=20, default="success", description="评估状态: pending(待审核), success(已通过), fail(已拒绝)")
|
status = fields.CharField(max_length=20, default="pending", description="评估状态: pending(待审核), success(已通过), rejected(已拒绝)")
|
||||||
admin_notes = fields.TextField(null=True, description="管理员备注")
|
admin_notes = fields.TextField(null=True, description="管理员备注")
|
||||||
created_at = fields.DatetimeField(auto_now_add=True, description="创建时间")
|
created_at = fields.DatetimeField(auto_now_add=True, description="创建时间")
|
||||||
updated_at = fields.DatetimeField(auto_now=True, description="更新时间")
|
updated_at = fields.DatetimeField(auto_now=True, description="更新时间")
|
||||||
|
|||||||
@ -126,7 +126,7 @@ class AppCreateInvoiceWithReceipt(BaseModel):
|
|||||||
invoice_type: Optional[str] = Field(None, pattern=r"^(special|normal)$")
|
invoice_type: Optional[str] = Field(None, pattern=r"^(special|normal)$")
|
||||||
# 兼容前端索引字段:"0"→normal,"1"→special
|
# 兼容前端索引字段:"0"→normal,"1"→special
|
||||||
invoiceTypeIndex: Optional[str] = None
|
invoiceTypeIndex: Optional[str] = None
|
||||||
receipt_url: str = Field(..., min_length=1, max_length=512)
|
receipt_url: Optional[str] = Field(None, max_length=512)
|
||||||
note: Optional[str] = Field(None, max_length=256)
|
note: Optional[str] = Field(None, max_length=256)
|
||||||
|
|
||||||
@field_validator('ticket_type', mode='before')
|
@field_validator('ticket_type', mode='before')
|
||||||
@ -143,7 +143,7 @@ class AppCreateInvoiceWithReceipt(BaseModel):
|
|||||||
s = v.strip()
|
s = v.strip()
|
||||||
if s.startswith('`') and s.endswith('`'):
|
if s.startswith('`') and s.endswith('`'):
|
||||||
s = s[1:-1].strip()
|
s = s[1:-1].strip()
|
||||||
return s
|
return s or None
|
||||||
return v
|
return v
|
||||||
|
|
||||||
@model_validator(mode='after')
|
@model_validator(mode='after')
|
||||||
|
|||||||
@ -1,10 +1,10 @@
|
|||||||
from pydantic import BaseModel, Field
|
from pydantic import BaseModel, Field, EmailStr
|
||||||
from typing import Optional, List, Union
|
from typing import Optional, List, Union
|
||||||
|
|
||||||
|
|
||||||
class SendEmailRequest(BaseModel):
|
class SendEmailRequest(BaseModel):
|
||||||
receipt_id: Optional[int] = Field(None, description="付款凭证ID")
|
receipt_id: Optional[int] = Field(None, description="付款凭证ID")
|
||||||
email: str = Field(..., description="邮箱地址")
|
email: EmailStr = Field(..., description="邮箱地址")
|
||||||
subject: Optional[str] = Field(None, description="邮件主题")
|
subject: Optional[str] = Field(None, description="邮件主题")
|
||||||
body: str = Field(..., description="文案内容")
|
body: str = Field(..., description="文案内容")
|
||||||
file_urls: Optional[List[str]] = Field(None, description="附件URL列表")
|
file_urls: Optional[List[str]] = Field(None, description="附件URL列表")
|
||||||
|
|||||||
@ -362,7 +362,7 @@ class ValuationAssessmentQuery(BaseModel):
|
|||||||
institution: Optional[str] = Field(None, description="所属机构")
|
institution: Optional[str] = Field(None, description="所属机构")
|
||||||
industry: Optional[str] = Field(None, description="所属行业")
|
industry: Optional[str] = Field(None, description="所属行业")
|
||||||
heritage_level: Optional[str] = Field(None, description="非遗等级")
|
heritage_level: Optional[str] = Field(None, description="非遗等级")
|
||||||
status: Optional[str] = Field(None, description="评估状态: pending(待审核), approved(已通过), rejected(已拒绝)")
|
status: Optional[str] = Field(None, description="评估状态: pending(待审核), success(已通过), rejected(已拒绝)")
|
||||||
is_active: Optional[bool] = Field(None, description="是否激活")
|
is_active: Optional[bool] = Field(None, description="是否激活")
|
||||||
phone: Optional[str] = Field(None, description="手机号模糊查询")
|
phone: Optional[str] = Field(None, description="手机号模糊查询")
|
||||||
submitted_start: Optional[str] = Field(None, description="提交时间开始(毫秒时间戳或ISO字符串)")
|
submitted_start: Optional[str] = Field(None, description="提交时间开始(毫秒时间戳或ISO字符串)")
|
||||||
|
|||||||
@ -51,6 +51,8 @@ class EmailClient:
|
|||||||
server.quit()
|
server.quit()
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
if isinstance(e, smtplib.SMTPRecipientsRefused):
|
||||||
|
return {"status": "FAIL", "error": "收件方地址不存在或暂时不可用"}
|
||||||
return {"status": "FAIL", "error": str(e)}
|
return {"status": "FAIL", "error": str(e)}
|
||||||
|
|
||||||
def send_many(self, to_email: str, subject: Optional[str], body: str, attachments: Optional[List[Tuple[bytes, str]]] = None) -> dict:
|
def send_many(self, to_email: str, subject: Optional[str], body: str, attachments: Optional[List[Tuple[bytes, str]]] = None) -> dict:
|
||||||
|
|||||||
@ -86,7 +86,7 @@ class SMSClient:
|
|||||||
返回体映射字典
|
返回体映射字典
|
||||||
"""
|
"""
|
||||||
key = settings.ALIYUN_SMS_TEMPLATE_PARAM_CODE_KEY or "code"
|
key = settings.ALIYUN_SMS_TEMPLATE_PARAM_CODE_KEY or "code"
|
||||||
template = settings.ALIYUN_SMS_TEMPLATE_CODE_VERIFY or "SMS_498190229"
|
template = settings.ALIYUN_SMS_TEMPLATE_CODE_VERIFY
|
||||||
logger.info("sms.send_code using key={} template={} phone={}", key, template, phone)
|
logger.info("sms.send_code using key={} template={} phone={}", key, template, phone)
|
||||||
return self.send_by_template(phone, template, {key: code})
|
return self.send_by_template(phone, template, {key: code})
|
||||||
|
|
||||||
@ -99,7 +99,7 @@ class SMSClient:
|
|||||||
Returns:
|
Returns:
|
||||||
返回体映射字典
|
返回体映射字典
|
||||||
"""
|
"""
|
||||||
template = settings.ALIYUN_SMS_TEMPLATE_CODE_REPORT or "SMS_498140213"
|
template = settings.ALIYUN_SMS_TEMPLATE_CODE_REPORT
|
||||||
logger.info("sms.send_report using template={} phone={}", template, phone)
|
logger.info("sms.send_report using template={} phone={}", template, phone)
|
||||||
return self.send_by_template(phone, template, {})
|
return self.send_by_template(phone, template, {})
|
||||||
|
|
||||||
|
|||||||
@ -100,18 +100,18 @@ class Settings(BaseSettings):
|
|||||||
ALIYUN_SMS_SIGN_NAME: typing.Optional[str] = "成都文化产权交易所"
|
ALIYUN_SMS_SIGN_NAME: typing.Optional[str] = "成都文化产权交易所"
|
||||||
ALIYUN_SMS_ENDPOINT: str = "dysmsapi.aliyuncs.com"
|
ALIYUN_SMS_ENDPOINT: str = "dysmsapi.aliyuncs.com"
|
||||||
ALIYUN_SMS_TEMPLATE_CODE_VERIFY: typing.Optional[str] = "SMS_498140213"
|
ALIYUN_SMS_TEMPLATE_CODE_VERIFY: typing.Optional[str] = "SMS_498140213"
|
||||||
ALIYUN_SMS_TEMPLATE_CODE_REPORT: typing.Optional[str] = "SMS_49190229"
|
ALIYUN_SMS_TEMPLATE_CODE_REPORT: typing.Optional[str] = "SMS_498190229"
|
||||||
SMS_CODE_DIGITS: int = 6
|
SMS_CODE_DIGITS: int = 6
|
||||||
SMS_DEBUG_LOG_CODE: bool = True
|
SMS_DEBUG_LOG_CODE: bool = True
|
||||||
ALIYUN_USE_DEFAULT_CREDENTIALS: bool = False
|
ALIYUN_USE_DEFAULT_CREDENTIALS: bool = False
|
||||||
ALIYUN_SMS_TEMPLATE_PARAM_CODE_KEY: typing.Optional[str] = "code"
|
ALIYUN_SMS_TEMPLATE_PARAM_CODE_KEY: typing.Optional[str] = "code"
|
||||||
|
|
||||||
SMTP_HOST: typing.Optional[str] = None
|
SMTP_HOST: typing.Optional[str] = "smtp.qiye.aliyun.com"
|
||||||
SMTP_PORT: typing.Optional[int] = None
|
SMTP_PORT: typing.Optional[int] = 465
|
||||||
SMTP_USERNAME: typing.Optional[str] = None
|
SMTP_USERNAME: typing.Optional[str] = "value@cdcee.net"
|
||||||
SMTP_PASSWORD: typing.Optional[str] = None
|
SMTP_PASSWORD: typing.Optional[str] = "PPXbILdGlRCn2VOx"
|
||||||
SMTP_TLS: bool = True
|
SMTP_TLS: bool = False
|
||||||
SMTP_FROM: typing.Optional[str] = None
|
SMTP_FROM: typing.Optional[str] = "value@cdcee.net"
|
||||||
|
|
||||||
|
|
||||||
settings = Settings()
|
settings = Settings()
|
||||||
|
|||||||
@ -106,7 +106,7 @@ class BasicValueB11Calculator:
|
|||||||
# 使用两个增长率的平均值
|
# 使用两个增长率的平均值
|
||||||
avg_growth_rate = (growth_rate_1 + growth_rate_2) / 2
|
avg_growth_rate = (growth_rate_1 + growth_rate_2) / 2
|
||||||
|
|
||||||
return avg_growth_rate
|
return max(avg_growth_rate, 0.0)
|
||||||
|
|
||||||
def calculate_legal_strength_l(self,
|
def calculate_legal_strength_l(self,
|
||||||
patent_score: float,
|
patent_score: float,
|
||||||
@ -379,4 +379,4 @@ if __name__ == "__main__":
|
|||||||
# print(f"增长率: {growth_rate*100}%")
|
# print(f"增长率: {growth_rate*100}%")
|
||||||
# print(f"(1+14%)^5 = {growth_factor:.4f}")
|
# print(f"(1+14%)^5 = {growth_factor:.4f}")
|
||||||
# print(f"2333 × {growth_factor:.4f} = {initial_value * growth_factor:.2f}")
|
# print(f"2333 × {growth_factor:.4f} = {initial_value * growth_factor:.2f}")
|
||||||
# print(f"再除以5: {initial_value * growth_factor:.2f} ÷ 5 = {result:.2f}")
|
# print(f"再除以5: {initial_value * growth_factor:.2f} ÷ 5 = {result:.2f}")
|
||||||
|
|||||||
@ -48,6 +48,20 @@ class TrafficFactorB12Calculator:
|
|||||||
|
|
||||||
traffic_factor = (math.log(search_index_s1 / industry_average_s2) * 0.3 +
|
traffic_factor = (math.log(search_index_s1 / industry_average_s2) * 0.3 +
|
||||||
social_media_spread_s3 * 0.7)
|
social_media_spread_s3 * 0.7)
|
||||||
|
|
||||||
|
"""
|
||||||
|
为什么需要
|
||||||
|
|
||||||
|
- 经济价值 B1 的公式是 B1 = B11 × (1 + B12) × B13 (app/utils/calculation_engine/economic_value_b1/economic_value_b1.py:34-45)。
|
||||||
|
- 如果 B12 < -1 ,则 (1 + B12) 会变成负数,导致 B1 翻成负值并把模型估值 B(final_value_ab/model_value_b.py:48-50)拉到巨负。
|
||||||
|
- 通过设置 B12 ≥ -0.9 ,确保 (1 + B12) ≥ 0.1 ,即乘数始终为正且不至于过小。
|
||||||
|
直观示例
|
||||||
|
|
||||||
|
- 原始计算得到 B12 = -1.8 (例如 ln(S1/S2) 很大负、社交传播度 S3 又很低),则 (1 + B12) = -0.8 ,会让 B1 变负。
|
||||||
|
- 裁剪后 B12 = -0.9 ,则 (1 + B12) = 0.1 , B1 保持为正,避免最终估值出现大幅负值。
|
||||||
|
"""
|
||||||
|
if traffic_factor < -0.9:
|
||||||
|
traffic_factor = -0.9
|
||||||
|
|
||||||
return traffic_factor
|
return traffic_factor
|
||||||
|
|
||||||
@ -346,4 +360,4 @@ if __name__ == "__main__":
|
|||||||
print(f"覆盖人群指数: {coverage_index:.4f}")
|
print(f"覆盖人群指数: {coverage_index:.4f}")
|
||||||
print(f"转化效率: {conversion_efficiency:.4f}")
|
print(f"转化效率: {conversion_efficiency:.4f}")
|
||||||
print(f"社交媒体传播度S3: {social_media_spread_s3:.4f}")
|
print(f"社交媒体传播度S3: {social_media_spread_s3:.4f}")
|
||||||
print(f"流量因子B12: {traffic_factor:.4f}")
|
print(f"流量因子B12: {traffic_factor:.4f}")
|
||||||
|
|||||||
BIN
requirements.txt
BIN
requirements.txt
Binary file not shown.
0
smtp_test_output.txt
Normal file
0
smtp_test_output.txt
Normal file
4
估值字段.txt
4
估值字段.txt
@ -37,8 +37,8 @@
|
|||||||
|
|
||||||
|
|
||||||
export DOCKER_DEFAULT_PLATFORM=linux/amd64
|
export DOCKER_DEFAULT_PLATFORM=linux/amd64
|
||||||
docker build -t zfc931912343/guzhi-fastapi-admin:v1.8 .
|
docker build -t zfc931912343/guzhi-fastapi-admin:v2.1 .
|
||||||
docker push zfc931912343/guzhi-fastapi-admin:v1.8
|
docker push zfc931912343/guzhi-fastapi-admin:v2.1
|
||||||
|
|
||||||
|
|
||||||
# 运行容器
|
# 运行容器
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user