Add pricing.py
This commit is contained in:
+109
@@ -0,0 +1,109 @@
|
||||
"""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}"
|
||||
Reference in New Issue
Block a user