"""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}"