Refactor: Separate data sync from UI rendering, change colunm width

This commit is contained in:
2026-01-27 07:12:09 +08:00
parent 61b32bbdd7
commit 9e7f474d5e
5 changed files with 1691 additions and 144 deletions
+31 -51
View File
@@ -4,6 +4,7 @@ from engine import DataEngine
import concurrent.futures
from flask_caching import Cache
import csv, os, logging
from concurrent.futures import ThreadPoolExecutor
app = Flask(__name__)
cache = Cache(app, config={'CACHE_TYPE': 'SimpleCache'})
@@ -11,52 +12,6 @@ cache = Cache(app, config={'CACHE_TYPE': 'SimpleCache'})
import os
import csv
def load_instruments_from_csv(file_path):
instruments = []
# Standard static templates
# For AGI, we use the ISIN-based Tearsheet URL
TEMPLATES = {
'jpm': "https://am.jpmorgan.com/FundsMarketingHandler/historicalData?cusip={cusip}&country=hk&role=per",
'yahoo': "https://query1.finance.yahoo.com/v8/finance/chart/{cusip}?range=5y&interval=1d",
'agi': "https://markets.ft.com/data/funds/tearsheet/historical?s={cusip}"
}
try:
abs_path = os.path.join(os.path.dirname(__file__), file_path)
if not os.path.exists(abs_path):
print(f"Error: {file_path} not found.")
return []
with open(abs_path, mode='r', encoding='utf-8-sig') as csvfile:
reader = csv.DictReader(csvfile)
# Standardize header names to lowercase
reader.fieldnames = [name.strip().lower() for name in reader.fieldnames]
for row in reader:
symbol = row.get('symbol', '').strip()
cusip = row.get('cusip', '').strip()
provider = row.get('provider', 'jpm').strip().lower()
if symbol and cusip:
# Fetch correct template; default to JPM if provider is unknown
template = TEMPLATES.get(provider, TEMPLATES['jpm'])
url = template.format(cusip=cusip)
instruments.append({
"symbol": symbol,
"url": url,
"provider": provider
})
except Exception as e:
print(f"CSV Loading Error: {e}")
return instruments
# Usage
URL_CONFIG = load_instruments_from_csv('instruments.csv')
@cache.memoize(timeout=3600)
def fetch_and_calculate(config):
@@ -75,13 +30,38 @@ def index():
@app.route('/api/summary')
def get_summary():
engine_base = DataEngine()
instruments = engine_base.load_instruments_from_csv('instruments.csv')
results = []
with concurrent.futures.ThreadPoolExecutor(max_workers=5) as executor:
futures = [executor.submit(fetch_and_calculate, cfg) for cfg in URL_CONFIG]
for f in concurrent.futures.as_completed(futures):
if f.result(): results.append(f.result())
results.sort(key=lambda x: x['symbol'])
for item in instruments:
engine = DataEngine(
symbol=item['symbol'],
url=item['url'],
provider=item['provider']
)
metrics = engine.get_local_metrics()
if metrics and isinstance(metrics, dict):
metrics['symbol'] = item['symbol']
results.append(metrics)
else:
# 🔥 FIX: Include the 'error' key so JavaScript hits the Gatekeeper
results.append({
"symbol": item['symbol'],
"last_close": None, # Ensure this is null, not the string "No Data"
"error": True
})
return jsonify(results)
@app.route('/api/sync', methods=['POST'])
def run_sync():
# ✅ THIS RUNS THE FULL fetch_data() WITH NETWORK ACCESS
engine = DataEngine()
report = engine.global_sync()
return jsonify(report)
if __name__ == '__main__':
app.run(debug=True, host='0.0.0.0', port=5000)