110 lines
3.5 KiB
Python
110 lines
3.5 KiB
Python
"""ConnectVM rate card (INR) and pricing engine.
|
|
|
|
Model: flavor-based monthly pricing, prorated by billable hours in the
|
|
billing month. A VM present for the whole month pays its full flavor price;
|
|
created mid-month it prorates. Custom flavors (not on the card) are priced
|
|
from add-on unit rates. Volumes and floating IPs use add-on rates.
|
|
"""
|
|
|
|
# ---- Rate card: flavor monthly prices (INR) -------------------------------
|
|
# key = normalized flavor name ("B1 - Lite" / "B1 · Lite" -> "b1 lite")
|
|
FLAVOR_MONTHLY = {
|
|
"m2 tiny": 1200.0,
|
|
"m1 standard": 2303.0,
|
|
"a1 nano": 2303.0,
|
|
"b1 lite": 4607.0,
|
|
"c1 core": 9215.0,
|
|
"r1 medium": 27647.0,
|
|
"s1 large": 34559.0,
|
|
"t3 max": 43199.0,
|
|
"d2 edge": 46079.0,
|
|
"t2 xlarge": 62207.0,
|
|
"w2 prime": 73727.0,
|
|
"x1 elite": 92159.0,
|
|
}
|
|
|
|
# Card specs (vcpus, ram_mb, disk_gb) to detect spec drift on named flavors
|
|
FLAVOR_SPECS = {
|
|
"m2 tiny": (1, 2048, 10),
|
|
"m1 standard": (1, 4096, 20),
|
|
"a1 nano": (2, 4096, 20),
|
|
"b1 lite": (4, 8192, 40),
|
|
"c1 core": (8, 16384, 80),
|
|
"r1 medium": (16, 65536, 200),
|
|
"s1 large": (24, 65536, 300),
|
|
"t3 max": (32, 131072, 480),
|
|
"d2 edge": (12, 32768, 100),
|
|
"t2 xlarge": (32, 98304, 480),
|
|
"w2 prime": (42, 131072, 500),
|
|
"x1 elite": (64, 131072, 800),
|
|
}
|
|
|
|
# ---- Add-on rates (INR/month) ---------------------------------------------
|
|
ADDON_VCPU = 480.0 # per vCPU
|
|
ADDON_RAM_PER_GB = 192.0 # per GB RAM
|
|
ADDON_DISK_PER_GB = 9.6 # ₹96 per 10 GB SSD
|
|
FIP_MONTHLY = 300.0 # additional IPv4
|
|
SNAPSHOT_PER_10GB = 48.0 # not metered yet
|
|
LB_MONTHLY = 1440.0 # not metered yet (Octavia)
|
|
BACKUP_PER_10GB = 72.0 # not metered yet
|
|
BANDWIDTH_PER_GB = 1.80 # not metered yet
|
|
|
|
RATE_CARD_INFO = {
|
|
"currency": "INR",
|
|
"model": "flavor monthly price prorated by billable hours; "
|
|
"custom flavors computed from add-on rates",
|
|
"addon_rates_per_month": {
|
|
"vcpu": ADDON_VCPU,
|
|
"ram_gb": ADDON_RAM_PER_GB,
|
|
"ssd_gb": ADDON_DISK_PER_GB,
|
|
"floating_ip": FIP_MONTHLY,
|
|
},
|
|
"not_yet_metered": {
|
|
"snapshot_per_10gb": SNAPSHOT_PER_10GB,
|
|
"load_balancer": LB_MONTHLY,
|
|
"backup_per_10gb": BACKUP_PER_10GB,
|
|
"bandwidth_per_gb": BANDWIDTH_PER_GB,
|
|
},
|
|
}
|
|
|
|
|
|
def normalize_flavor(name: str) -> str:
|
|
for ch in ("·", "-", "_", "."):
|
|
name = name.replace(ch, " ")
|
|
return " ".join(name.lower().split())
|
|
|
|
|
|
def flavor_monthly(name: str, vcpus: int, ram_mb: int, disk_gb: int):
|
|
"""Return (monthly_price_inr, source, spec_mismatch)."""
|
|
key = normalize_flavor(name or "")
|
|
if key in FLAVOR_MONTHLY:
|
|
spec = FLAVOR_SPECS[key]
|
|
mismatch = spec != (vcpus, ram_mb, disk_gb)
|
|
return FLAVOR_MONTHLY[key], "rate-card", mismatch
|
|
price = (
|
|
vcpus * ADDON_VCPU
|
|
+ (ram_mb / 1024) * ADDON_RAM_PER_GB
|
|
+ disk_gb * ADDON_DISK_PER_GB
|
|
)
|
|
return price, "computed", False
|
|
|
|
|
|
def period_hours(begin, end) -> float:
|
|
return (end - begin).total_seconds() / 3600
|
|
|
|
|
|
def inr(amount: float) -> str:
|
|
"""Format with Indian digit grouping: 92,159.00 / 1,23,456.00"""
|
|
neg = amount < 0
|
|
whole, _, frac = f"{abs(amount):.2f}".partition(".")
|
|
if len(whole) > 3:
|
|
head, tail = whole[:-3], whole[-3:]
|
|
groups = []
|
|
while len(head) > 2:
|
|
groups.insert(0, head[-2:])
|
|
head = head[:-2]
|
|
if head:
|
|
groups.insert(0, head)
|
|
whole = ",".join(groups + [tail])
|
|
return f"₹{'-' if neg else ''}{whole}.{frac}"
|