| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266 |
- from fastapi import APIRouter, Request, Depends, Form
- from fastapi.responses import HTMLResponse
- from sqlalchemy.orm import Session
- from sqlalchemy import text
- from collections import defaultdict
- from database import SessionLocal, redis_client
- from services.template_service import build_template
- from services.price_service import apply_adjust
- from fastapi.templating import Jinja2Templates
- import json
- import uuid
- router = APIRouter(prefix="/estimate", tags=["estimate"])
- templates = Jinja2Templates(directory="templates")
- # ================= DB =================
- def get_db():
- db = SessionLocal()
- try:
- yield db
- finally:
- db.close()
- # ================= 获取所有机型 =================
- @router.get("/simulate", response_class=HTMLResponse)
- def simulate(db: Session = Depends(get_db)):
- # 获取所有有模板的机型
- machines = db.execute(text("""
- SELECT machine_id, name
- FROM t_machine
- WHERE machine_id IN (SELECT DISTINCT machine_id FROM machine_temp)
- """)).fetchall()
- html = "<h2>估价模拟</h2>"
- html += '<form method="get" action="/estimate/simulate_one">'
- # 展示机型选择
- html += '<label>选择机型:</label><br>'
- html += '<select name="machine_id">'
- for machine in machines:
- html += f'<option value="{machine.machine_id}">{machine.name}</option>'
- html += '</select><br>'
- html += '<button type="submit">选择机型</button></form>'
- return html
- # ================= 获取机型模板和选项 =================
- @router.get("/simulate_one_del", response_class=HTMLResponse)
- def simulate(
- machine_id: int,
- db: Session = Depends(get_db)
- ):
- # 获取机型对应的模板
- row = db.execute(text("""
- SELECT estimate_packet
- FROM machine_temp
- WHERE machine_id=:mid
- """), {"mid": machine_id}).fetchone()
- tpl = json.loads(row.estimate_packet)
- html = f"<h2>估价模拟 - {tpl['templateId']}</h2>"
- html += '<form method="post" action="/estimate/simulate">'
- for step in tpl["template"]:
- html += f"<h3>{step['stepName']}</h3>"
- for p in step["properties"]:
- html += f"<b>{p['name']}</b><br>"
- for v in p["values"]:
- html += f"""
- <label>
- <input type="checkbox" name="option_ids" value="{v['valueId']}">
- {v['valueText']}
- </label><br>
- """
- html += f"""
- <input type="hidden" name="machine_id" value="{machine_id}">
- <br>
- <button type="submit">开始估价</button>
- </form>
- """
- return html
- @router.get("/simulate_one", response_class=HTMLResponse)
- def simulate_one_del2(
- request: Request,
- machine_id: int,
- db: Session = Depends(get_db)
- ):
- row = db.execute(text("""
- SELECT m.name AS machine_name,
- t.estimate_packet
- FROM machine_temp t
- JOIN t_machine m ON t.machine_id = m.machine_id
- WHERE t.machine_id = :mid
- """), {"mid": machine_id}).fetchone()
- if not row:
- return HTMLResponse("未找到机型模板", status_code=404)
-
- # print("*************row****************")
- # print(row)
- tpl = json.loads(row.estimate_packet)
- return templates.TemplateResponse(
- "simulate_one.html",
- {
- "request": request,
- "machine_id": machine_id,
- "machine_name": row.machine_name,
- "tpl": tpl
- }
- )
- # ================= 提交估价并计算价格 =================
- @router.post("/simulate_del", response_class=HTMLResponse)
- def simulate_calc(
- machine_id: int = Form(...),
- option_ids: list[str] = Form([]),
- db: Session = Depends(get_db)
- ):
- # 获取基准价格
- base_price_row = db.execute(text("""
- SELECT base_price
- FROM machine_base_price
- WHERE machine_id=:mid
- """), {"mid": machine_id}).fetchone()
- # base_price = base_price_row.base_price
- base_price = base_price_row.base_price if base_price_row else 1000.0
- # 获取选项因素
- factors = db.execute(text("""
- SELECT option_id, factor, absolute_deduct
- FROM price_option_factor
- WHERE option_id IN :ids
- """), {"ids": tuple(option_ids)}).fetchall()
- # 计算价格
- price = base_price
- for f in factors:
- if f.factor:
- price *= (1-float(f.factor))
- if f.absolute_deduct:
- price -= float(f.absolute_deduct)
- # 逐步应用调节因子
- # 1. 获取全局调节因子
- global_adjust = db.execute(text("""
- SELECT factor FROM price_adjust_factor
- WHERE level='global'
- """)).fetchone()
- if global_adjust:
- price *= float(global_adjust.factor)
- # 2. 获取品牌调节因子
- brand_adjust = db.execute(text("""
- SELECT factor FROM price_adjust_factor
- WHERE level='brand'
- AND ref_id=(
- SELECT brand_id FROM t_machine WHERE machine_id=:mid
- )
- """), {"mid": machine_id}).fetchone()
- if brand_adjust:
- price *= float(brand_adjust.factor)
- # 3. 获取机型调节因子
- machine_adjust = db.execute(text("""
- SELECT factor FROM price_adjust_factor
- WHERE level='machine'
- AND ref_id=:mid
- """), {"mid": machine_id}).fetchone()
- if machine_adjust:
- price *= float(machine_adjust.factor)
- html = f"""
- <h2>估价结果</h2>
- 选择项:{",".join(option_ids)}<br>
- 估价:{round(price, 2)} 元<br>
- <a href="/simulate?machine_id={machine_id}">返回</a>
- """
- return html
- @router.post("/simulate_de3", response_class=HTMLResponse)
- def simulate_calc(
- request: Request,
- machine_id: int = Form(...),
- option_ids: list[str] = Form([]),
- db: Session = Depends(get_db)
- ):
- # 机型名称
- machine = db.execute(text("""
- SELECT name, brand_name
- FROM t_machine
- WHERE machine_id=:mid
- """), {"mid": machine_id}).fetchone()
- # 模板
- row = db.execute(text("""
- SELECT estimate_packet
- FROM machine_temp
- WHERE machine_id=:mid
- """), {"mid": machine_id}).fetchone()
- tpl = json.loads(row.estimate_packet)
- # ========== 基准价 ==========
- base_price_row = db.execute(text("""
- SELECT base_price
- FROM machine_base_price
- WHERE machine_id=:mid
- LIMIT 1
- """), {"mid": machine_id}).fetchone()
- base_price = float(base_price_row.base_price) if base_price_row else 1000.0
- price = base_price
- detail_rows = []
- # ========== 选项扣减 ==========
- if option_ids:
- factors = db.execute(text("""
- SELECT
- pf.option_id,
- pf.factor,
- pf.absolute_deduct,
- ro.option_name
- FROM price_option_factor pf
- LEFT JOIN release_option ro
- ON pf.option_id = ro.option_id
- WHERE pf.option_id IN :ids
- """), {"ids": tuple(option_ids)}).fetchall()
- else:
- factors = []
- for f in factors:
- before = price
- if f.factor is not None:
- price *= float(f.factor)
- if f.absolute_deduct is not None:
- price -= float(f.absolute_deduct)
- detail_rows.append({
- "type": "option",
- "name": f.option_name or str(f.option_id),
- "option_id": f.option_id,
- "factor": f.factor,
- "absolute_deduct": f.absolute_deduct,
- "before": round(before, 2),
- "after": round(price, 2)
- })
- # ========== 全局调节 ==========
- global_adjust = db.execute(text("""
- SELECT factor
- FROM price_adjust_factor
- WHERE level='global'
- LIMIT 1
- """)).fetchone()
- if global_adjust:
- before = price
- price *= float(global_adjust.factor)
- detail_rows.append({
- "type": "adjust",
- "name": "全局调节",
- "factor": global_adjust.factor,
- "before": round(before, 2),
- "after": round(price, 2)
- })
- # ========== 品牌调节 ==========
- brand_adjust = db.execute(text("""
- SELECT factor
- FROM price_adjust_factor
- WHERE level='brand'
- AND ref_id=(
- SELECT brand_id FROM t_machine WHERE machine_id=:mid
- )
- LIMIT 1
- """), {"mid": machine_id}).fetchone()
- if brand_adjust:
- before = price
- price *= float(brand_adjust.factor)
- detail_rows.append({
- "type": "adjust",
- "name": "品牌调节",
- "factor": brand_adjust.factor,
- "before": round(before, 2),
- "after": round(price, 2)
- })
- # ========== 机型调节 ==========
- machine_adjust = db.execute(text("""
- SELECT factor
- FROM price_adjust_factor
- WHERE level='machine'
- AND ref_id=:mid
- LIMIT 1
- """), {"mid": machine_id}).fetchone()
- if machine_adjust:
- before = price
- price *= float(machine_adjust.factor)
- detail_rows.append({
- "type": "adjust",
- "name": "机型调节",
- "factor": machine_adjust.factor,
- "before": round(before, 2),
- "after": round(price, 2)
- })
- final_price = round(price, 2)
- return templates.TemplateResponse(
- "simulate_one.html",
- {
- "request": request,
- "tpl": tpl,
- "machine": machine,
- "machine_id": machine_id,
- "selected_option_ids": option_ids,
- "base_price": round(base_price, 2),
- "detail_rows": detail_rows,
- "final_price": final_price
- }
- )
- @router.get("/simulate_one", response_class=HTMLResponse)
- def simulate_one(
- request: Request,
- machine_id: int,
- db: Session = Depends(get_db)
- ):
- row = db.execute(text("""
- SELECT m.name,
- t.estimate_packet
- FROM machine_temp t
- JOIN t_machine m ON t.machine_id = m.machine_id
- WHERE t.machine_id = :mid
- """), {"mid": machine_id}).fetchone()
- tpl = json.loads(row.estimate_packet)
- return templates.TemplateResponse(
- "simulate_one.html",
- {
- "request": request,
- "machine_id": machine_id,
- "machine_name": row.name,
- "tpl": tpl,
- "result": None
- }
- )
- # ================= 估价 =================
- @router.post("/simulate666", response_class=HTMLResponse)
- async def simulate_calc(
- request: Request,
- machine_id: int = Form(...),
- db: Session = Depends(get_db)
- ):
- form = await request.form()
- # ---------------- 收集 option ----------------
- option_ids = []
- for k, v in form.multi_items():
- if k.startswith("option_"):
- option_ids.append(str(v))
- # ---------------- 重新取模板与机型名 ----------------
- row = db.execute(text("""
- SELECT m.name,
- t.estimate_packet
- FROM machine_temp t
- JOIN t_machine m ON t.machine_id = m.machine_id
- WHERE t.machine_id = :mid
- """), {"mid": machine_id}).fetchone()
- if not row:
- return HTMLResponse("机型模板不存在")
- tpl = json.loads(row.estimate_packet)
- # ---------------- 构造 valueId -> valueText 映射 ----------------
- value_name_map = {}
- for step in tpl["template"]:
- for p in step["properties"]:
- for v in p["values"]:
- value_name_map[str(v["valueId"])] = v["valueText"]
- # ---------------- 基准价 ----------------
- base_row = db.execute(text("""
- SELECT base_price
- FROM machine_base_price
- WHERE machine_id=:mid
- LIMIT 1
- """), {"mid": machine_id}).fetchone()
- base_price = float(base_row.base_price) if base_row else 1000.0
- # ---------------- 选项因子 ----------------
- detail_rows = []
- if option_ids:
- rows = db.execute(text("""
- SELECT option_id, factor, absolute_deduct
- FROM price_option_factor
- WHERE option_id IN :ids
- """), {"ids": tuple(option_ids)}).fetchall()
- else:
- rows = []
- price = base_price
- for r in rows:
- before = price
- if r.factor is not None:
- price = price * float(r.factor)
- if r.absolute_deduct is not None:
- price = price - float(r.absolute_deduct)
- detail_rows.append({
- "option_id": r.option_id,
- "option_name": value_name_map.get(str(r.option_id), str(r.option_id)),
- "factor": r.factor,
- "absolute": r.absolute_deduct,
- "before": round(before, 2),
- "after": round(price, 2)
- })
- # ---------------- 调节因子 ----------------
- # 全局
- g = db.execute(text("""
- SELECT factor FROM price_adjust_factor
- WHERE level='global'
- LIMIT 1
- """)).fetchone()
- if g:
- before = price
- price = price * float(g.factor)
- detail_rows.append({
- "option_id": "GLOBAL",
- "option_name": "全局调节",
- "factor": g.factor,
- "absolute": None,
- "before": round(before, 2),
- "after": round(price, 2)
- })
- # 品牌
- b = db.execute(text("""
- SELECT factor FROM price_adjust_factor
- WHERE level='brand'
- AND ref_id=(
- SELECT brand_id
- FROM t_machine
- WHERE machine_id=:mid
- )
- LIMIT 1
- """), {"mid": machine_id}).fetchone()
- if b:
- before = price
- price = price * float(b.factor)
- detail_rows.append({
- "option_id": "BRAND",
- "option_name": "品牌调节",
- "factor": b.factor,
- "absolute": None,
- "before": round(before, 2),
- "after": round(price, 2)
- })
- # 机型
- m = db.execute(text("""
- SELECT factor FROM price_adjust_factor
- WHERE level='machine'
- AND ref_id=:mid
- LIMIT 1
- """), {"mid": machine_id}).fetchone()
- if m:
- before = price
- price = price * float(m.factor)
- detail_rows.append({
- "option_id": "MACHINE",
- "option_name": "机型调节",
- "factor": m.factor,
- "absolute": None,
- "before": round(before, 2),
- "after": round(price, 2)
- })
- return templates.TemplateResponse(
- "simulate_one.html",
- {
- "request": request,
- "machine_id": machine_id,
- "machine_name": row.name,
- "tpl": tpl,
- "result": {
- "base_price": round(base_price, 2),
- "final_price": round(price, 2),
- "details": detail_rows,
- "selected": option_ids
- }
- }
- )
- @router.post("/simulate7", response_class=HTMLResponse)
- async def simulate_calc(
- request: Request,
- machine_id: int = Form(...),
- db: Session = Depends(get_db)
- ):
- form = await request.form()
- # 收集所有 option
- option_ids = []
- for k, v in form.multi_items():
- if k.startswith("option_"):
- option_ids.append(str(v))
- # ---------------- 重新取模板与机型名 ----------------
- row = db.execute(text("""
- SELECT m.name,
- t.estimate_packet
- FROM machine_temp t
- JOIN t_machine m ON t.machine_id = m.machine_id
- WHERE t.machine_id = :mid
- """), {"mid": machine_id}).fetchone()
- if not row:
- return HTMLResponse("机型模板不存在")
- tpl = json.loads(row.estimate_packet)
- # ---------------- 构造 valueId -> valueText 映射 ----------------
- value_name_map = {}
- for step in tpl["template"]:
- for p in step["properties"]:
- for v in p["values"]:
- value_name_map[str(v["valueId"])] = v["valueText"]
- # ---------------- 基准价 ----------------
- base_row = db.execute(text("""
- SELECT base_price
- FROM machine_base_price
- WHERE machine_id=:mid
- LIMIT 1
- """), {"mid": machine_id}).fetchone()
- base_price_input = form.get("base_price")
- if base_price_input and str(base_price_input).strip():
- base_price = float(base_price_input)
- else:
- base_price = float(base_row.base_price) if base_row else 1
- # ---------------- 选项因子 ----------------
- detail_rows = []
- if option_ids:
- rows = db.execute(text("""
- SELECT option_id, factor, absolute_deduct
- FROM price_option_factor
- WHERE option_id IN :ids
- """), {"ids": tuple(option_ids)}).fetchall()
- else:
- rows = []
- price = base_price
- total_deduct_rate = 0
- detail_rows.append({
- "option_id": "GLOBAL",
- "option_name": "-- 以下是扣减比例,累加",
- "factor": 0,
- "absolute": None,
- "before": None,
- "after": None
- })
- for r in rows:
- before = price
- if r.factor:
- total_deduct_rate += float(r.factor)
- # price = price * (1-float(r.factor))
- # if r.absolute_deduct:
- # price = price - float(r.absolute_deduct)
- detail_rows.append({
- "option_id": r.option_id,
- "option_name": value_name_map.get(str(r.option_id), str(r.option_id)),
- "factor": r.factor,
- "absolute": r.absolute_deduct,
- "before": round(before, 2),
- "after": round(price, 2)
- })
- if total_deduct_rate:
- price = price * (1 - total_deduct_rate)
- price = max(price, 0.0)
- # ---------------- 调节因子 ----------------
- detail_rows.append({
- "option_id": "GLOBAL",
- "option_name": "-- 以下是调节因子,累乘",
- "factor": 0,
- "absolute": None,
- "before": None,
- "after": None
- })
- # 全局
- g = db.execute(text("""
- SELECT factor FROM price_adjust_factor
- WHERE level='global'
- LIMIT 1
- """)).fetchone()
- if g:
- before = price
- price = price * float(g.factor)
- detail_rows.append({
- "option_id": "GLOBAL",
- "option_name": "全局调节系数",
- "factor": g.factor,
- "absolute": None,
- "before": round(before, 2),
- "after": round(price, 2)
- })
- # 品牌
- b = db.execute(text("""
- SELECT factor FROM price_adjust_factor
- WHERE level='brand'
- AND ref_id=(
- SELECT brand_id
- FROM t_machine
- WHERE machine_id=:mid
- )
- LIMIT 1
- """), {"mid": machine_id}).fetchone()
- if b:
- before = price
- price = price * float(b.factor)
- detail_rows.append({
- "option_id": "BRAND",
- "option_name": "品牌调节系数",
- "factor": b.factor,
- "absolute": None,
- "before": round(before, 2),
- "after": round(price, 2)
- })
- # 机型
- m = db.execute(text("""
- SELECT factor FROM price_adjust_factor
- WHERE level='machine'
- AND ref_id=:mid
- LIMIT 1
- """), {"mid": machine_id}).fetchone()
- if m:
- before = price
- price = price * float(m.factor)
- detail_rows.append({
- "option_id": "MACHINE",
- "option_name": "机型调节系数",
- "factor": m.factor,
- "absolute": None,
- "before": round(before, 2),
- "after": round(price, 2)
- })
- # 重新取模板与机型名
- row = db.execute(text("""
- SELECT m.name,
- t.estimate_packet
- FROM machine_temp t
- JOIN t_machine m ON t.machine_id = m.machine_id
- WHERE t.machine_id = :mid
- """), {"mid": machine_id}).fetchone()
- tpl = json.loads(row.estimate_packet)
- return templates.TemplateResponse(
- "simulate_one.html",
- {
- "request": request,
- "machine_id": machine_id,
- "machine_name": row.name,
- "tpl": tpl,
- "result": {
- "base_price": round(base_price, 2),
- "final_price": round(price, 2),
- "details": detail_rows,
- "selected": option_ids
- }
- }
- )
- @router.post("/simulate", response_class=HTMLResponse)
- async def simulate_calc(
- request: Request,
- machine_id: int = Form(...),
- db: Session = Depends(get_db)
- ):
- form = await request.form()
- option_ids = []
- for k, v in form.multi_items():
- if k.startswith("option_"):
- option_ids.append(int(v))
- # ---------------- 重新取模板与机型名 ----------------
- row = db.execute(text("""
- SELECT m.name,
- t.estimate_packet
- FROM machine_temp t
- JOIN t_machine m ON t.machine_id = m.machine_id
- WHERE t.machine_id = :mid
- """), {"mid": machine_id}).fetchone()
- if not row:
- return HTMLResponse("机型模板不存在")
- tpl = json.loads(row.estimate_packet)
- # ---------------- 构造 valueId -> valueText 映射 ----------------
- value_name_map = {}
- for step in tpl["template"]:
- for p in step["properties"]:
- for v in p["values"]:
- value_name_map[str(v["valueId"])] = v["valueText"]
- # ---------------- 基准价 ----------------
- base_row = db.execute(text("""
- SELECT base_price
- FROM machine_base_price
- WHERE machine_id=:mid
- LIMIT 1
- """), {"mid": machine_id}).fetchone()
- base_price_input = form.get("base_price")
- if base_price_input and str(base_price_input).strip():
- base_price = float(base_price_input)
- else:
- base_price = float(base_row.base_price) if base_row else 1
- # base_price = float(base_row.base_price) if base_row else 1000.0
- # ---------------- 读取选项扣减规则 ----------------
- rows = []
- if option_ids:
- rows = db.execute(text("""
- SELECT
- p.option_id,
- p.factor,
- p.absolute_deduct,
- p.group_code,
- p.severity_level,
- p.sub_weight,
- p.is_special,
- p.repair_level,
- g.cap_ratio,
- g.group_weight
- FROM price_option_factor p
- LEFT JOIN price_damage_group g
- ON p.group_code = g.group_code
- WHERE p.option_id IN :ids
- """), {"ids": tuple(option_ids)}).fetchall()
- # ---------------- 覆盖关系 ----------------
- overrides = db.execute(text("""
- SELECT
- trigger_group_code,
- target_group_code,
- override_type,
- override_value
- FROM price_group_override
- """)).fetchall()
- override_map = defaultdict(list)
- for o in overrides:
- override_map[o.trigger_group_code].append(o)
- # ---------------- 分组 ----------------
- groups = defaultdict(list)
- specials = []
- repairs = []
- for r in rows:
- if r.repair_level and r.repair_level > 0:
- repairs.append(r)
- continue
- if r.is_special:
- specials.append(r)
- continue
- groups[r.group_code].append(r)
- # ---------------- 覆盖处理 ----------------
- hit_groups = set(groups.keys())
- skip_groups = set()
- weight_override = {}
- for g in hit_groups:
- for o in override_map.get(g, []):
- if o.override_type == "skip":
- skip_groups.add(o.target_group_code)
- elif o.override_type == "weight":
- weight_override[o.target_group_code] = float(o.override_value)
- # ---------------- 分组扣减 ----------------
- total_ratio = 0.0
- detail_rows = []
- for group_code, items in groups.items():
- if group_code in skip_groups:
- continue
- items = sorted(
- items,
- key=lambda x: (
- float(x.factor or 0),
- x.severity_level or 0
- ),
- reverse=True
- )
- main = items[0]
- group_deduct = float(main.factor or 0)
- for sub in items[1:]:
- group_deduct += float(sub.factor or 0) * float(sub.sub_weight or 0.3)
- cap_ratio = float(main.cap_ratio or 1.0)
- cap = float(main.factor or 0) * cap_ratio
- group_deduct = min(group_deduct, cap)
- group_weight = float(main.group_weight or 1.0)
- if group_code in weight_override:
- group_weight = weight_override[group_code]
- final_group_deduct = group_deduct * group_weight
- total_ratio += final_group_deduct
- detail_rows.append({
- "option_name": f"--{group_code} 组扣减",
- "factor": round(final_group_deduct, 4)
- })
- for it in items:
- detail_rows.append({
- # "option_name": it.option_id,
- "option_name": value_name_map.get(str(it.option_id), str(it.option_id)),
- "factor": it.factor
- })
- # ---------------- special 处理 ----------------
- special_ratio = 0.0
- for s in specials:
- special_ratio += float(s.factor or 0)
- detail_rows.append({
- "option_name": f"特殊项:{s.option_id}",
- "factor": s.factor
- })
- # 特殊项封顶(示例:30%)
- special_ratio = min(special_ratio, 0.30)
- total_ratio += special_ratio
- # ---------------- 维修分级 ----------------
- repair_ratio = 0.0
- max_repair_level = 0
- for r in repairs:
- repair_ratio += float(r.factor or 0)
- max_repair_level = max(max_repair_level, r.repair_level or 0)
- detail_rows.append({
- "option_name": f"维修项:{r.option_id}",
- "factor": r.factor
- })
- # 维修封顶(示例)
- if max_repair_level >= 3:
- repair_ratio = max(repair_ratio, 0.30)
- elif max_repair_level == 2:
- repair_ratio = min(repair_ratio, 0.20)
- else:
- repair_ratio = min(repair_ratio, 0.10)
- total_ratio += repair_ratio
- # ---------------- 最终价格 ----------------
- total_ratio = min(total_ratio, 0.90)
- final_price = base_price * (1 - total_ratio)
- # ---------------- 返回 ----------------
- row = db.execute(text("""
- SELECT m.name, t.estimate_packet
- FROM machine_temp t
- JOIN t_machine m ON t.machine_id = m.machine_id
- WHERE t.machine_id = :mid
- """), {"mid": machine_id}).fetchone()
- # import json
- tpl = json.loads(row.estimate_packet)
- return templates.TemplateResponse(
- "simulate_one.html",
- {
- "request": request,
- "machine_id": machine_id,
- "machine_name": row.name,
- "tpl": tpl,
- "result": {
- "base_price": round(base_price, 2),
- "final_price": round(final_price, 2),
- "total_ratio": round(total_ratio, 4),
- "details": detail_rows,
- "selected": [str(i) for i in option_ids]
- }
- }
- )
- SPECIAL_DISCOUNT_RATIO = 0.70 # 特殊项触发后,对剩余价格打7折
- REPAIR_LEVEL_CAP = {
- 3: 0.60, # 核心维修,最多吃掉基础价60%
- 2: 0.35, # 重要维修
- 1: 0.15, # 次要维修
- }
- def calculate_price(db, base_price: float, selected_option_ids: list[int]):
- if not selected_option_ids:
- return {
- "base_price": base_price,
- "final_price": base_price,
- "details": []
- }
- # -------------------------------------------------
- # 1. 读取 option 规则
- # -------------------------------------------------
- rows = db.execute(text("""
- SELECT
- f.option_id,
- f.factor,
- f.absolute_deduct,
- f.group_code,
- f.severity_level,
- f.sub_weight,
- f.is_special,
- f.repair_level,
- o.option_name
- FROM price_option_factor f
- LEFT JOIN release_option o
- ON o.option_id = f.option_id
- WHERE f.option_id IN :ids
- """), {
- "ids": tuple(selected_option_ids)
- }).mappings().all()
- # -------------------------------------------------
- # 2. 分组
- # -------------------------------------------------
- group_map = defaultdict(list)
- special_items = []
- repair_items = []
- for r in rows:
- r = dict(r)
- if r["is_special"]:
- special_items.append(r)
- if r["repair_level"] and r["repair_level"] > 0:
- repair_items.append(r)
- group_code = r["group_code"] or "default"
- group_map[group_code].append(r)
- # -------------------------------------------------
- # 3. 读取分组配置
- # -------------------------------------------------
- group_rows = db.execute(text("""
- SELECT
- group_code,
- cap_ratio,
- group_weight
- FROM price_damage_group
- """)).mappings().all()
- group_conf = {r["group_code"]: r for r in group_rows}
- # -------------------------------------------------
- # 4. 读取组联动
- # -------------------------------------------------
- override_rows = db.execute(text("""
- SELECT
- trigger_group_code,
- target_group_code,
- override_type,
- override_value
- FROM price_group_override
- """)).mappings().all()
- overrides = defaultdict(list)
- for r in override_rows:
- overrides[r["trigger_group_code"]].append(r)
- # -------------------------------------------------
- # 5. 普通损伤组计算(不含repair组)
- # -------------------------------------------------
- group_result = {}
- for group_code, items in group_map.items():
- if group_code == "repair":
- continue
- effective = [
- i for i in items
- if (i["factor"] or 0) > 0 or (i["absolute_deduct"] or 0) > 0
- ]
- if not effective:
- continue
- effective.sort(
- key=lambda x: (
- x["severity_level"] or 0,
- x["factor"] or 0
- ),
- reverse=True
- )
- main = effective[0]
- minors = effective[1:]
- main_deduct = base_price * (main["factor"] or 0) + (main["absolute_deduct"] or 0)
- minor_sum = 0
- for m in minors:
- w = m["sub_weight"] if m["sub_weight"] is not None else 0.3
- minor_sum += base_price * (m["factor"] or 0) * w
- raw = main_deduct + minor_sum
- conf = group_conf.get(group_code)
- if conf:
- cap = main_deduct * float(conf["cap_ratio"])
- deduct = min(raw, cap)
- deduct *= float(conf["group_weight"])
- else:
- deduct = raw
- group_result[group_code] = {
- "deduct": deduct,
- "main": main,
- "has_effect": deduct > 0
- }
- # -------------------------------------------------
- # 6. repair 组分级模型
- # -------------------------------------------------
- repair_deduct = 0
- repair_detail = []
- if repair_items:
- by_level = defaultdict(list)
- for r in repair_items:
- by_level[r["repair_level"]].append(r)
- for level, items in by_level.items():
- items = [
- i for i in items
- if (i["factor"] or 0) > 0 or (i["absolute_deduct"] or 0) > 0
- ]
- if not items:
- continue
- # 同级只取最严重
- items.sort(key=lambda x: x["factor"] or 0, reverse=True)
- main = items[0]
- d = base_price * (main["factor"] or 0) + (main["absolute_deduct"] or 0)
- cap_ratio = REPAIR_LEVEL_CAP.get(level, 1.0)
- d = min(d, base_price * cap_ratio)
- repair_deduct += d
- repair_detail.append({
- "repair_level": level,
- "option_name": main["option_name"],
- "deduct": round(d, 2)
- })
- group_result["repair"] = {
- "deduct": repair_deduct,
- "main": None,
- "has_effect": repair_deduct > 0,
- "detail": repair_detail
- }
- # -------------------------------------------------
- # 7. 组联动
- # -------------------------------------------------
- disabled_groups = set()
- group_weight_override = {}
- for trigger, rules in overrides.items():
- if trigger not in group_result:
- continue
- if not group_result[trigger]["has_effect"]:
- continue
- for rule in rules:
- target = rule["target_group_code"]
- if rule["override_type"] == "skip":
- disabled_groups.add(target)
- elif rule["override_type"] == "weight":
- group_weight_override[target] = float(rule["override_value"])
- # -------------------------------------------------
- # 8. 汇总普通扣减
- # -------------------------------------------------
- total_deduct = 0
- details = []
- for group_code, g in group_result.items():
- if group_code in disabled_groups:
- continue
- d = g["deduct"]
- if group_code in group_weight_override:
- d *= group_weight_override[group_code]
- total_deduct += d
- if group_code == "repair":
- details.append({
- "group": "repair",
- "detail": g["detail"],
- "group_deduct": round(d, 2)
- })
- else:
- details.append({
- "group": group_code,
- "option_name": g["main"]["option_name"],
- "group_deduct": round(d, 2)
- })
- price_after_damage = base_price - total_deduct
- if price_after_damage < 0:
- price_after_damage = 0
- # -------------------------------------------------
- # 9. 特殊项整体折价
- # -------------------------------------------------
- special_applied = []
- if special_items:
- # 你后面可以在这里按 option 决定不同折扣
- price_after_damage *= SPECIAL_DISCOUNT_RATIO
- for s in special_items:
- special_applied.append(s["option_name"])
- final_price = round(price_after_damage, 2)
- return {
- "base_price": round(base_price, 2),
- "final_price": final_price,
- "damage_deduct": round(total_deduct, 2),
- "special_discount_applied": special_applied,
- "details": details
- }
- # curl -sS -X POST "http://127.0.0.1:8002/api/product/bid" -H "Content-Type: application/json" -H "X-API-Key: dHiw0yduUhnpQRV9z30EGpJD8fAne1CJ" -d '{"task_id":2,"task_types":["bid"]}'
|