SPB Git forge

spb/uqo-chat

Public
14commits 1branches 0releases
1.4 MBsize
maindefault branch
17 days agolast push
Python 64.6% TypeScript 33.7% CSS 0.8%
3.5 KB · 78 lines python
Raw Blame History
1"""unit_convert — area / length / price-per-unit conversions incl. Québec units (arpent)."""23from __future__ import annotations45from typing import Any67from pydantic import BaseModel, Field89from app.llm.schemas import ToolResult10from app.tools.coerce import num11from app.tools.registry import ToolContext, registry1213# base: m² for areas, m for lengths14AREA = {15    "m2": 1.0, "m²": 1.0, "metre carre": 1.0, "mètre carré": 1.0,16    "pi2": 0.09290304, "pi²": 0.09290304, "sqft": 0.09290304, "ft2": 0.09290304, "pied carré": 0.09290304,17    "ha": 10000.0, "hectare": 10000.0,18    "acre": 4046.8564224, "ac": 4046.8564224,19    "arpent2": 3418.894, "arpent carré": 3418.894, "arpent²": 3418.894, "arpent": 3418.894,20    "km2": 1e6, "km²": 1e6,21    "vg2": 0.83612736, "verge carrée": 0.83612736, "yd2": 0.83612736,22}23LENGTH = {24    "m": 1.0, "metre": 1.0, "mètre": 1.0, "cm": 0.01, "mm": 0.001, "km": 1000.0,25    "pi": 0.3048, "ft": 0.3048, "pied": 0.3048, "po": 0.0254, "in": 0.0254, "pouce": 0.0254,26    "vg": 0.9144, "yd": 0.9144, "verge": 0.9144, "mi": 1609.344, "mille": 1609.344,27    "arpent_lin": 58.47, "arpent linéaire": 58.47, "perche": 5.847,28}29ALIASES = {"pieds carrés": "pi2", "pi.ca.": "pi2", "pc": "pi2", "sq ft": "pi2", "metres carres": "m2",30           "mètres carrés": "m2", "hectares": "ha", "acres": "acre", "arpents": "arpent", "pieds": "pi",31           "pouces": "po", "metres": "m", "mètres": "m"}323334class Args(BaseModel):35    value: float | str36    from_unit: str = Field(..., description="ex. m2, pi2, acre, ha, arpent, m, pi, po, $/m2, $/pi2")37    to_unit: str383940def _norm(u: str) -> str:41    u = u.strip().lower().replace(" ", " ")42    return ALIASES.get(u, u)434445def convert(value: float, from_unit: str, to_unit: str) -> dict[str, Any]:46    f, t = _norm(from_unit), _norm(to_unit)47    per = f.startswith("$/") and t.startswith("$/")48    if per:49        f, t = f[2:], t[2:]50    for table, kind in ((AREA, "superficie"), (LENGTH, "longueur")):51        if f in table and t in table:52            factor = table[f] / table[t]53            if per:  # $/from → $/to : inverse relationship54                factor = table[t] / table[f]55                return {"result": value * factor, "factor": factor, "kind": f"prix par {kind}",56                        "formula": f"{value:g} $/{f} × ({table[t]:g} / {table[f]:g}) = {value * factor:,.4f} $/{t}"}57            return {"result": value * factor, "factor": factor, "kind": kind,58                    "formula": f"{value:g} {f} × {factor:,.6g} = {value * factor:,.4f} {t}"}59    raise ValueError(f"unités incompatibles ou inconnues : {from_unit} → {to_unit}")606162async def run(args: dict[str, Any], ctx: ToolContext) -> ToolResult:63    v = num(args["value"], None)64    if v is None:65        return ToolResult(content="Valeur numérique invalide.", error=True)66    try:67        out = convert(v, args["from_unit"], args["to_unit"])68    except ValueError as exc:69        return ToolResult(content=f"{exc}. Unités : {', '.join(sorted(set(AREA) | set(LENGTH)))} ; "70                          "préfixe $/ pour les prix unitaires.", error=True)71    return ToolResult(content=f"{out['formula']}  (facteur {out['factor']:.6g}, {out['kind']}). "72                      "Rappels : 1 m² = 10,7639 pi² ; 1 pi = 0,3048 m ; 1 arpent carré ≈ 3 418,9 m² ; 1 acre = 4 046,86 m².",73                      payload={"value": v, "from": args["from_unit"], "to": args["to_unit"], **out},74                      meta={"summary": out["formula"]})757677registry.register("unit_convert", run, Args)78