"""K909 wheel selection brief and support/drive comparison, 2026-10-14.

Reads previous calculations. Does not rebuild or release the old CAD.
"""
import hashlib
import json
import math
import sys
from pathlib import Path
from urllib.parse import parse_qs, urlparse

HERE = Path(__file__).resolve().parent
ROOT = HERE.parents[2]
OLD = HERE.parent / "pneumatic_rebaseline_v0_32"
BASE = HERE.parent / "common_support_v0_31"
G = 9.80665
sys.stdout.reconfigure(encoding="utf-8")


def read(p):
    return json.loads(p.read_text(encoding="utf-8-sig"))


def write(name, obj):
    (HERE / name).write_text(json.dumps(obj, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")


baseline = read(OLD / "calculation.json")
for name, sha in baseline["input_sha256"].items():
    assert hashlib.sha256((BASE / name).read_bytes()).hexdigest() == sha
mass = baseline["baseline_reference"]["total_mass_kg"]
diameter, width, n, fx, reserve = 200, 50, 100, 100, 1.5
listed_pressure = 50 * 0.006894757293168
rpm = (60 / 3.6) * 60 / (math.pi * diameter / 1000)
torque = fx * diameter / 2000
support_cases = []
for mu in [0.1, 0.15, 0.2, 0.3, 0.4]:
    required_mass = (n + reserve * abs(fx) / mu) / G
    support_cases.append(dict(assumed_foot_mu=mu, required_total_mass_kg=required_mass,
                              extra_ballast_over_old_reference_kg=max(0, required_mass - mass)))
drive_cases = []
for ratio in [1, 1.25, 1.5, 1.8]:
    motor_t = torque / (ratio * 0.9)
    drive_cases.append(dict(motor_rpm_to_wheel_rpm_ratio=ratio, wheel_rpm=rpm, motor_rpm=ratio * rpm,
                            required_wheel_torque_Nm=torque, assumed_transmission_efficiency=0.9,
                            required_motor_torque_Nm=motor_t,
                            within_9_8Nm_and_3000rpm_reference=motor_t <= 9.8 and rpm * ratio <= 3000))
thermal = []
for cold in [-20, -30]:
    p = (listed_pressure + 0.101325) * (cold + 273.15) / (20 + 273.15) - 0.101325
    thermal.append(dict(room_C=20, room_gauge_MPa=listed_pressure, cold_C=cold,
                        ideal_fixed_volume_cold_gauge_MPa=p))

# Publish only task-specific summaries, not account/home-page content or URL tokens.
runtime = ROOT / ".device-runtime" / "taobao-wheel-v0_33"
logs = HERE / "taobao" if (HERE / "taobao").exists() else runtime
taobao_summary = []
for name in ["read_20050.result.json", "read_k909_only.result.json"]:
    if not (logs / name).exists():
        continue
    r = read(logs / name).get("result", {})
    text = r.get("content", "")
    hits = [line for line in text.splitlines()
            if ("K909" in line.upper() or "200X50" in line.upper()) and ("轮胎" in line or "整轮" in line)]
    taobao_summary.append(dict(query=parse_qs(urlparse(r.get("url", "")).query).get("q", []),
                               page_title=r.get("title"), relevant_listing_titles=hits,
                               exact_SKU_price_verified=False,
                               K909_200x50_stock_verified=False,
                               evidence_scope="search-card title only; no purchase or merchant message"))
write("domestic_search_summary.json", taobao_summary)

brief = dict(
    version="v0.33", date="2026-10-14", status="recommended nominal wheel basis; hub interface pending",
    user_direction=dict(pneumatic_wheel=True, pressure_0_345_MPa_acceptable=True,
                        removable_ballast_and_positive_restraint_to_be_compared=True),
    wheel=dict(manufacturer="Kenda", pattern="K909", manufacturer_part_no="109090301B0",
               size_label="8x2 / 200x50", nominal_design_diameter_mm=diameter,
               nominal_design_tread_width_mm=width, construction="BLK 4P TT", listed_pressure_psi=50,
               listed_pressure_MPa=listed_pressure, needs_inner_tube=True,
               actual_inflated_outer_dimensions_mm=None, complete_wheel_cold_speed_pressure_rating=None,
               actual_wheel_rim_mass_kg=None, confirmed_domestic_exact_SKU=None),
    public_rim_example=dict(part="WHL-220F", material_description="aluminium alloy split rim",
                            stated_fit="200x50 / 8x2 tyre and tube", axle_diameter_mm=10,
                            bearings="2 x 6200Z", bearing_spacer_mm=24, hub_width_mm=1.875 * 25.4,
                            selected_for_machine=False,
                            reason="free-running 10mm axle wheel; not plug-compatible with existing driven 20mm shaft"),
    local_layout=dict(old_solid_wheel_mm=[125, 25], proposed_nominal_tyre_mm=[200, 50],
                      larger_wheel_requires_local_layout_change=True,
                      no_final_new_frame_dimensions=True,
                      design_reservation_candidate_mm=[220, 70],
                      reservation_is_manufacturer_dimension=False,
                      reservation_does_not_include_complete_hub_valve_or_drive_package=True),
    support=dict(old_reference_total_mass_kg=mass, test_normal_load_N=n, longitudinal_force_assumption_N=fx,
                  force_reserve_assumption=reserve, actual_foot_mu=None, actual_machine_mass_kg=None,
                  cases=support_cases, ballast_mount="low, retained, symmetric, detachable blocks on base",
                  positive_restraint_mount="machine base upstream of measurement chain",
                  confirmed_chamber_structural_anchor=None,
                  restraint_must_react_to_independent_structural_support=True,
                  thin_chamber_liner_is_not_confirmed_anchor=True),
    drive=dict(target_tread_speed_kmh=60, wheel_rpm=rpm, wheel_torque_at_Fx100_Nm=torque,
                ideal_friction_power_W=fx * 60 / 3.6, reference_motor_rating_Nm=9.8,
                reference_motor_rated_rpm=3000, comparison=drive_cases,
                preferred_screening_ratio=1.5, actual_cold_torque_and_losses_verified=False),
    pressure_temperature=dict(definition="record measured gauge pressure together with tyre temperature",
                              fixed_volume_ideal_gas_examples=thermal,
                              cold_pressure_values_are_not_fill_instructions=True,
                              warmup_pressure_release_interface_to_be_provided=True),
    sources=[
        dict(id="S1", role="manufacturer tyre specification",
             url="https://wheelchair.kendatire.com/en-us/find-a-tire/wheelchair/powerchair-scooter/k909/",
             facts="109090301B0; 8x2; 200x50; BLK 4P TT; Slick; PSI50"),
        dict(id="S2", role="supplier's own rim listing, example only",
             url="https://cart.electricscooterparts.com/front-mag-rim-for-200x50-tires",
             facts="WHL-220F; split aluminium rim; 10mm axle; two6200Z; spacer24mm; hub1-7/8inch"),
        dict(id="S3", role="supplier's own driven rim listing, alternative architecture example",
             url="https://electricscooterparts.com/electricscooterwheels.html",
             facts="WHL-220R uses72T HTD5M15 drive cog,10mm axle and3inch hub width; not adopted"),
    ],
    previous_draft_pressure_goal_0_5_MPa="superseded for the recommended first K909 wheel",
    CAD_manufacturing_release=False,
)
write("wheel_brief.json", brief)

rows = "".join(f"<tr><td>{x['assumed_foot_mu']:.2f}</td><td>{x['required_total_mass_kg']:.1f} kg</td><td>{x['extra_ballast_over_old_reference_kg']:.1f} kg</td></tr>" for x in support_cases)
drive_rows = "".join(f"<tr><td>{x['motor_rpm_to_wheel_rpm_ratio']:.2f}</td><td>{x['motor_rpm']:.0f}</td><td>{x['required_motor_torque_Nm']:.2f}</td></tr>" for x in drive_cases)
doc = f"""<!doctype html><html lang='zh-CN'><meta charset='utf-8'><meta name='viewport' content='width=device-width,initial-scale=1'>
<title>K909充气轮组与配重固定方案 v0.33</title>
<style>body{{margin:0;background:#f2f5f7;color:#24313c;font:16px/1.75 'Microsoft YaHei',sans-serif}}main{{max-width:940px;margin:auto;padding:26px}}section{{padding:24px;background:white;border-radius:12px;margin:18px 0}}h1{{font-size:28px;line-height:1.4}}h2{{font-size:21px}}table{{width:100%;border-collapse:collapse;font-size:15px}}td,th{{border-bottom:1px solid #dce4e8;padding:10px;text-align:left}}th{{background:#eef4f7}}a{{color:#176d87}}.small{{color:#5b6c78;font-size:14px}}.scroll{{overflow:auto}}img{{max-width:100%;height:auto}}@media(max-width:600px){{main{{padding:12px}}section{{padding:16px}}h1{{font-size:23px}}}}</style>
<main><p class='small'>设备线 · 2026-10-14 · 轮胎规格与固定方式调研 · v0.33</p>
<h1>先按 K909 200×50 充气轮胎重定轮组</h1>
<section><p>用户已接受0.345 MPa这一压力量级。本轮推荐 K909 的8×2／200×50有内胎规格作为设计基准，原0.5 MPa选型目标随之调整。轮胎名义规格先明确；轮辋、驱动连接和最终整机尺寸随后闭合。</p>
<table><tr><th>项目</th><th>推荐设计基准</th></tr><tr><td>品牌／花纹</td><td>建大 Kenda K909</td></tr><tr><td>原厂料号</td><td>109090301B0</td></tr><tr><td>规格</td><td>8×2／200×50；名义外径200 mm、胎宽50 mm</td></tr><tr><td>结构</td><td>BLK 4P TT，4PR、有内胎；需配套轮辋和内胎</td></tr><tr><td>原厂压力栏</td><td>50 psi = {listed_pressure:.6f} MPa，约0.345 MPa</td></tr><tr><td>首版轮载</td><td>沿用50—100 N设计目标，150 N仍为扩展</td></tr></table>
<p class='small'>上述轮胎资料来自<a href='{brief['sources'][0]['url']}'>建大原厂K909规格表</a>。名义200×50不是充气后实测装配尺寸，也不是轮辋接口图。PSI栏本身没有给出本项目−30℃/60 km/h整轮资格。</p></section>
<section><h2>配套轮辋能找到，但驱动接口要重做</h2>
<p><a href='{brief['sources'][1]['url']}'>WHL-220F供应商页面</a>列出适配200×50内外胎的两片式铝合金轮辋：10 mm轴、两6200Z轴承、24 mm中间隔套、轮毂宽47.625 mm。它是供10 mm轴使用的自由转轮辋，不能直接装到我们现有20 mm驱动转轴上。</p>
<p>优先比较“配套成品轮辋＋专用驱动转接件”，保留可更换轮组的思路；不按名义200×50自行推断胎圈直径或制造压力承载轮辋。另一种成品<a href='{brief['sources'][2]['url']}'>WHL-220R</a>带72齿HTD5M-15驱动轮、10 mm轴和76.2 mm轮毂宽，能作为固定轴带动轮辋的备选，但会改变当前转轴／轴承结构。本轮仅推荐轮胎规格，没有冻结这两条轮毂接口路线。</p>
<p>下一份安装图需要：充气外径/宽度、胎圈与轮辋匹配、轮毂总宽、轴孔与驱动连接、气门嘴扫掠空间，以及完整轮组质量。国内搜索找到200×50充气胎和铝合金整轮的商品标题；准确K909200×50现货与SKU价格尚未核实，其他品牌同尺寸不能直接混为同一试验轮。详见<a href='domestic_search_summary.json'>已筛选的国内搜索记录</a>。</p></section>
<section><h2>旧轮架的空间关系需要调整</h2><img src='wheel_envelope.svg' alt='原125轮与拟采用200轮的外包络及共同支承梁关系'>
<p>v0.32读取v0.31 STEP做的局部包络筛查中，Ø200×50圆柱外包络与两根共同支承梁重叠。该包络用于检查预留空间，不能替代真实轮胎CAD。应重排侧梁／电机／护罩位置；初步预留Ø220×70作为轮胎空间比较条件，轮毂、气门嘴和传动件另外计入，不先扩大整个机架。</p>
<p>在旧轴高162.5 mm不变的几何参照下，Ø200轮底点为62.5 mm；对应50/80 mm试样顶面，轮架位置分别约−12.5/+17.5 mm（未计轮胎压缩或雪下陷）。这为Z高度重定提供起点，旧界面的几何范围不能当作实际执行器可用行程。</p></section>
<section><h2>配重与卡固分别怎么用</h2>
<p>配重装在底座低处、对称分布，并固定防止移位；可拆小块便于搬运。它增加剩余脚底承重，提高依靠摩擦抵抗水平力的能力。卡固若接到独立承力固定点，则直接承受水平推力。把机器与一块同样会滑动的板夹在一起，整体仍然依靠脚底摩擦。</p>
<p>本表仅比较静态滑移：轮载100 N、Fx100 N、力余量1.5，所需总质量=(100+1.5×100/脚底μ)/9.80665。脚底μ是比较假设，实机需测；当前100.15 kg也是旧模型质量，新增配重列不构成实机配重数量。</p>
<div class='scroll'><table><tr><th>假设脚底μ</th><th>所需机器＋配重总质量</th><th>相对旧模型需补配重</th></tr>{rows}</table></div>
<p>保留“可拆配重＋底座双向止挡接口”。止挡连接在主测力链上游的机架底座上，不能接到被测轮架分走测量力；固定点应属于经确认的地面锚点或承力构件，箱内薄金属面板不直接当锚点。环境箱固定点仍未确认，因此当前计算不计入任何外部卡固反力。卡固位置、载荷和加速峰值明确后，再复算整体受力。</p></section>
<section><h2>200 mm轮的驱动核算</h2><p>60 km/h胎面线速度对应约<b>{rpm:.0f} rpm</b>；Fx=100 N时轮轴转矩<b>{torque:.1f} Nm</b>，理想摩擦功率约<b>{fx*60/3.6/1000:.2f} kW</b>。原1:1传动与9.8 Nm电机额定参考不够留出损失余量。</p>
<div class='scroll'><table><tr><th>电机转速÷轮速</th><th>电机转速，rpm</th><th>所需电机转矩，Nm（假设效率0.9）</th></tr>{drive_rows}</table></div>
<p>下一轮优先筛查1.5:1减速：轮速1592 rpm、电机约2387 rpm、电机转矩约7.41 Nm。实际冷态电机输出、皮带能力、启动惯量和整轮转速范围尚需代入；60 km/h继续作为逐级提升的目标。</p></section>
<section><h2>胎压需要和温度一起记录</h2><p>恒定容积的理想气体举例：20℃充到50 psi，到−20℃约{thermal[0]['ideal_fixed_volume_cold_gauge_MPa']:.3f} MPa、到−30℃约{thermal[1]['ideal_fixed_volume_cold_gauge_MPa']:.3f} MPa。实际轮胎容积与刚度会变，这些数值只用于说明测压温度的影响。0.345 MPa不直接作为冷态充气指令；工作压力需符合匹配整轮允许范围，冷态补气后的回温压力也需控制。</p>
<p>设备增加胎压测量／记录接口与回温释放压力安排。实验压力点由实验线结合轮印和低速预试验确定，不用胎压代替接触压强。</p></section>
<section><h2>下一步交付</h2><p>以K909200×50为名义基准，完成两条轮毂驱动接口比较和轮架局部布局；选定接口后再出新轴／轮架／护罩安装图。整机尺寸由这些实际接口反推，会议中的随口尺寸不沿用。</p><p><a href='wheel_brief.json'>参数、来源与计算</a> · <a href='build_review.py'>可复算脚本</a> · <a href='../common_support_v0_31/manufacturer_exchange_v0_31.zip'>已有图纸资料</a> · <a href='../pneumatic_rebaseline_v0_32/pressure_review.html'>上一轮核算</a></p></section></main></html>"""
(HERE / "wheel_review.html").write_text(doc, encoding="utf-8")

# Side view, exact reference coordinates. A projected 2-D envelope only.
def sx(x):
    return 265 + x * 1.6


def sy(z):
    return 475 - z * 1.6


rects = "".join(f"<rect x='{sx(x)}' y='{sy(225)}' width='{12*1.6}' height='{20*1.6}' fill='#b85946'/>" for x in [-72, 60])
svg = f"""<svg xmlns='http://www.w3.org/2000/svg' width='860' height='450' viewBox='0 0 860 450'>
<rect width='860' height='450' fill='#f3f7f9'/><g font-family='Microsoft YaHei,sans-serif' fill='#24313c'>
<text x='22' y='30' font-size='18'>轮胎侧视外包络：共同支承梁需要重新让位</text>
<circle cx='{sx(0)}' cy='{sy(162.5)}' r='{110*1.6}' fill='none' stroke='#c8932f' stroke-width='2' stroke-dasharray='7 5'/>
<circle cx='{sx(0)}' cy='{sy(162.5)}' r='{100*1.6}' fill='#d9ede8' fill-opacity='.65' stroke='#2d8e72' stroke-width='3'/>
<circle cx='{sx(0)}' cy='{sy(162.5)}' r='{62.5*1.6}' fill='none' stroke='#327eb0' stroke-width='2'/>
{rects}<circle cx='{sx(0)}' cy='{sy(162.5)}' r='4' fill='#24313c'/>
<text x='510' y='95' font-size='17' fill='#2d8e72'>拟采用轮胎：名义Ø200×50</text>
<text x='510' y='132' font-size='17' fill='#327eb0'>旧实心轮：Ø125×25</text>
<text x='510' y='169' font-size='17' fill='#b85946'>红色：旧共同支承梁投影</text>
<text x='510' y='206' font-size='17' fill='#9b701f'>虚线：Ø220预留空间比较案</text>
<text x='510' y='255' font-size='15'>同一轮轴位置，仅比较外包络。</text>
<text x='510' y='282' font-size='15'>胎宽、轮毂、气门嘴需另查轴向净空。</text>
<text x='22' y='435' font-size='14'>名义尺寸对照；下一轮重排支承梁并核对实际轮组CAD。</text></g></svg>"""
(HERE / "wheel_envelope.svg").write_text(svg, encoding="utf-8")
assert abs(listed_pressure - 0.3447378646584) < 1e-10
assert drive_cases[2]["within_9_8Nm_and_3000rpm_reference"]
print(json.dumps(dict(wheel=brief["wheel"], support=support_cases, rpm=rpm, torque_Nm=torque,
                     report=str(HERE / "wheel_review.html")), ensure_ascii=False, indent=2))
