File size: 2,339 Bytes
a02845e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
"""Deterministic commodity unit and INR conversion helpers.

External COMEX quotes are USD per troy ounce.  These helpers convert that
explicit unit into an indicative Indian landed value; they do not claim to be
an MCX settlement or a jeweller's retail quote.
"""

from dataclasses import dataclass
from decimal import Decimal, ROUND_HALF_UP
from typing import Union

Number = Union[Decimal, int, float, str]

TROY_OUNCE_GRAMS = Decimal("31.1034768")
GOLD_IMPORT_DUTY_RATE = Decimal("0.06")
GST_RATE = Decimal("0.03")
GRAMS_PER_10_GRAMS = Decimal("10")
GRAMS_PER_KILOGRAM = Decimal("1000")


@dataclass(frozen=True)
class CommodityConversion:
    """Auditable values for a USD/troy-ounce to INR conversion."""

    raw_usd_per_troy_ounce: Decimal
    usd_to_inr: Decimal
    inr_per_gram_before_taxes: Decimal
    inr_per_display_unit: Decimal


def usd_per_troy_ounce_to_indian_landed_price(
    raw_usd_per_troy_ounce: Number,
    usd_to_inr: Number,
    grams_per_display_unit: Number,
    import_duty_rate: Decimal = GOLD_IMPORT_DUTY_RATE,
    gst_rate: Decimal = GST_RATE,
) -> CommodityConversion:
    """Convert USD/troy oz to INR for a declared gram-based display unit.

    Tax is applied once, sequentially: import duty first, then GST.  Decimal
    inputs prevent binary floating-point rounding from leaking into prices.
    """

    raw_price = _decimal(raw_usd_per_troy_ounce)
    exchange_rate = _decimal(usd_to_inr)
    display_grams = _decimal(grams_per_display_unit)

    if raw_price < 0 or exchange_rate <= 0 or display_grams <= 0:
        raise ValueError("Commodity price, FX rate, and display grams must be positive.")

    inr_per_gram = raw_price * exchange_rate / TROY_OUNCE_GRAMS
    tax_multiplier = (Decimal("1") + import_duty_rate) * (Decimal("1") + gst_rate)
    inr_per_display_unit = inr_per_gram * display_grams * tax_multiplier

    return CommodityConversion(
        raw_usd_per_troy_ounce=raw_price,
        usd_to_inr=exchange_rate,
        inr_per_gram_before_taxes=inr_per_gram,
        inr_per_display_unit=inr_per_display_unit,
    )


def rounded_inr(value: Decimal) -> float:
    """Round a Decimal currency value to paise for JSON responses."""

    return float(value.quantize(Decimal("0.01"), rounding=ROUND_HALF_UP))


def _decimal(value: Number) -> Decimal:
    return Decimal(str(value))