#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ 快取單一來源模組。 ADR-017 Phase 3f-2: 將 sales/import/export/daily 會共同碰到的 module-level cache 收斂到這裡,避免各 route 各自持有一份 dict。 """ import time class FingerprintCache: """TTL + fingerprint 的小型 in-memory cache。""" def __init__(self, name, fingerprint_fn): self.name = name self._store = {} self._fp_fn = fingerprint_fn def get(self, key, ttl=300): entry = self._store.get(key) if not entry: return None if time.time() - entry['ts'] > ttl: return None try: if self._fp_fn() != entry['fp']: return None except Exception: pass return entry['data'] def set(self, key, data): try: fp = self._fp_fn() except Exception: fp = None self._store[key] = {'data': data, 'ts': time.time(), 'fp': fp} def clear(self): self._store.clear() _SALES_DF_CACHE = {} _SALES_PROCESSED_CACHE = {} _SALES_OPTIONS_CACHE = {} _SALES_ANALYSIS_RESULT_CACHE = {} _SALES_CACHE_MAX_ENTRIES = 10 _SALES_CACHE_TTL = 600 _DAILY_SALES_PROCESSED_CACHE = {} _DASHBOARD_DATA_CACHE = { 'consolidated_data': None, 'consolidated_timestamp': None, 'today_start': None, 'full_data': None, 'full_timestamp': None, } _DASHBOARD_CACHE_TTL = 1800 def cleanup_sales_cache(): """清理 sales 處理後快取的過期與超額條目。""" current_time = time.time() expired_keys = [ key for key, value in _SALES_PROCESSED_CACHE.items() if value.get('time') and current_time - value['time'] > _SALES_CACHE_TTL ] for key in expired_keys: _SALES_PROCESSED_CACHE.pop(key, None) if len(_SALES_PROCESSED_CACHE) > _SALES_CACHE_MAX_ENTRIES: sorted_items = sorted( [(key, value.get('time', 0)) for key, value in _SALES_PROCESSED_CACHE.items()], key=lambda item: item[1], ) for key, _ in sorted_items[:-_SALES_CACHE_MAX_ENTRIES]: _SALES_PROCESSED_CACHE.pop(key, None) def set_sales_processed_cache(key, entry, aliases=()): """寫入 sales cache,並可同步寫入別名 key。""" entry.setdefault('time', time.time()) _SALES_PROCESSED_CACHE[key] = entry for alias in aliases: _SALES_PROCESSED_CACHE[alias] = entry cleanup_sales_cache() def clear_sales_cache_for_table(table_name): """匯入資料後清除指定表對應的所有 sales cache key。""" _SALES_DF_CACHE.pop(table_name, None) keys_to_delete = [ key for key in list(_SALES_PROCESSED_CACHE.keys()) if key == table_name or key.startswith(f"{table_name}_") ] for key in keys_to_delete: _SALES_PROCESSED_CACHE.pop(key, None) def clear_sales_cache(): """清除所有 sales cache。""" _SALES_DF_CACHE.clear() _SALES_PROCESSED_CACHE.clear() _SALES_OPTIONS_CACHE.clear() _SALES_ANALYSIS_RESULT_CACHE.clear() def clear_daily_sales_cache(): """清除當日業績 cache。""" _DAILY_SALES_PROCESSED_CACHE.clear() def clear_dashboard_cache(): """清除商品看板 cache。""" _DASHBOARD_DATA_CACHE.clear() _DASHBOARD_DATA_CACHE.update({ 'consolidated_data': None, 'consolidated_timestamp': None, 'today_start': None, 'full_data': None, 'full_timestamp': None, })