#!/usr/bin/env python3
"""Q2 2026 定稿数据管线 — 生成与报告完全一致的冻结存档
输出: docs/quarterly-data-q2-2026.json + docs/signals-data-q2-2026.json
数据窗口: 2026-04-01 ~ 2026-06-30 (Jun close + 2025 同期基线 + JOLTS 四联)
"""
import json, time, urllib.request, urllib.parse
from datetime import date
from pathlib import Path

ROOT = Path(__file__).resolve().parent.parent
env = dict(l.split('=', 1) for l in open(Path.home() / '.nanobot' / 'workspace' / '.env').read().split('\n') if '=' in l and not l.startswith('#'))
FRED = env['FRED_API_KEY'].strip()
UA = 'RevolutioResearch/1.0 (https://revolutio.si; research@revolutio.si)'

def obs(sid, limit=26):
    url = f'https://api.stlouisfed.org/fred/series/observations?series_id={sid}&api_key={FRED}&file_type=json&sort_order=desc&limit={limit}'
    d = json.loads(urllib.request.urlopen(url, timeout=25).read())
    return [(o['date'], float(o['value'])) for o in d['observations'] if o['value'] != '.'][::-1]

def val_at(series, ym):
    return next((v for dt, v in series if dt.startswith(ym)), None)

def yoy_at(series, ym):
    y = int(ym[:4]) - 1
    cur = val_at(series, ym)
    prev = val_at(series, f'{y}-{ym[5:]}')
    if cur is None or prev in (None, 0): return None
    return round((cur - prev) / prev * 100, 2)

def sec_count(query, start, end):
    url = f'https://efts.sec.gov/LATEST/search-index?q={urllib.parse.quote(query)}&dateRange=custom&startdt={start}&enddt={end}&forms=10-Q'
    req = urllib.request.Request(url, headers={'User-Agent': UA})
    for attempt in range(4):
        try:
            d = json.loads(urllib.request.urlopen(req, timeout=25).read())
            return d.get('hits', {}).get('total', {}).get('value')
        except Exception as e:
            if attempt == 3: return f'ERROR: {str(e)[:80]}'
            time.sleep(2 + attempt * 2)

W = '2026-06'  # Q2 close month

# ── 就业层（含制造业独立序列）──
EMP_SERIES = {
    'USINFO': 'Information', 'USPBS': 'Professional and business services',
    'USFIRE': 'Financial activities', 'USEHS': 'Education and health services',
    'USTPU': 'Trade, transportation and utilities', 'MANEMP': 'Manufacturing',
    'PAYEMS': 'Total nonfarm',
}
employment = {}
for sid, label in EMP_SERIES.items():
    s = obs(sid)
    employment[sid] = {
        'label': label, 'series': sid,
        'jun26': val_at(s, W), 'jun25': val_at(s, '2025-06'),
        'yoy_pct': yoy_at(s, W), 'latest_date': s[-1][0],
        'history_2026': {dt[:7]: v for dt, v in s if dt.startswith('2026')},
    }
    time.sleep(0.4)

# ── JOLTS 四联（Q2 avg vs 2025Q2 avg）──
JOLTS_SERIES = {
    'JTU5100LDR': 'Information layoffs', 'JTU5100QUR': 'Information quits',
    'JTU5100HIR': 'Information hires', 'JTU5100JOR': 'Information openings',
    'JTU5200LDR': 'Prof sci tech layoffs', 'JTU6000LDR': 'Prof bus svcs layoffs',
    'JTU4000LDR': 'Trade transport layoffs', 'JTU1000LDR': 'Mining layoffs',
    'JTU6200LDR': 'Healthcare layoffs', 'JTU6100LDR': 'Education layoffs',
    'JTU3000LDR': 'Manufacturing layoffs',
    'JTU5100JOR': 'Information openings rate',
}
def q2_avg(series):
    vals = [v for dt, v in series if dt[:4] in ('2025', '2026') and dt[5:7] in ('04', '05', '06')]
    # 拆 25/26
    q225 = [v for dt, v in series if dt.startswith(('2025-04','2025-05','2025-06'))]
    q226 = [v for dt, v in series if dt.startswith(('2026-04','2026-05','2026-06'))]
    return (round(sum(q225)/len(q225), 2) if q225 else None, round(sum(q226)/len(q226), 2) if q226 else None)

jolts = {}
for sid, label in JOLTS_SERIES.items():
    s = obs(sid)
    a25, a26 = q2_avg(s)
    jolts[sid] = {'label': label, 'series': sid, 'q2_2025_avg': a25, 'q2_2026_avg': a26,
                  'jun_2026': val_at(s, W), 'history_2026': {dt[:7]: v for dt, v in s if dt.startswith('2026')}}
    time.sleep(0.4)

# ── 宏观 ──
macro = {}
for sid, label in [('UNRATE', 'Unemployment rate'), ('ICSA', 'Initial claims'), ('JTSJOL', 'Job openings K'), ('OPHNFB', 'Productivity idx')]:
    s = obs(sid, 40)
    macro[sid] = {'label': label, 'jun26': val_at(s, W), 'latest': s[-1], 'latest_date': s[-1][0]}
    time.sleep(0.4)

quarterly = {
    'report': 'Q2 2026', 'data_window': '2026-04-01..2026-06-30', 'frozen': date.today().isoformat(),
    'attribution': 'BLS CES/JOLTS via FRED (St. Louis Fed)',
    'employment': employment, 'jolts': jolts, 'macro': macro,
}
out1 = ROOT / 'docs' / 'quarterly-data-q2-2026.json'
out1.write_text(json.dumps(quarterly, indent=1))
print('✓', out1.name)

# ── SEC 层（Q2 对 Q2，含 agents 修复重试）──
Q2 = ('04-01', '06-30')
sec = {}
for key, q in [('ai_mentions', '"artificial intelligence"'), ('agent_mentions', '"AI agents"')]:
    sec[key] = {
        '2025Q2': sec_count(q, f'2025{Q2[0]}', f'2025{Q2[1]}'),
        '2026Q2': sec_count(q, f'2026{Q2[0]}', f'2026{Q2[1]}'),
    }
    time.sleep(1.2)
    # 校验，报错则重拉
    for qq in ('2025Q2', '2026Q2'):
        if isinstance(sec[key][qq], str) and sec[key][qq].startswith('ERROR'):
            print(f'  retry {key} {qq}...')
            time.sleep(4)
            y = '2025' if '2025' in qq else '2026'
            sec[key][qq] = sec_count(q, f'{y}{Q2[0]}', f'{y}{Q2[1]}')
    sec[key]['yoy_pct'] = (round((sec[key]['2026Q2'] - sec[key]['2025Q2']) / sec[key]['2025Q2'] * 100, 1)
                           if all(isinstance(v, (int, float)) for v in sec[key].values()) else None)
    print(f"  {key}: {sec[key]['2025Q2']} → {sec[key]['2026Q2']} ({sec[key]['yoy_pct']}%)")

# ── HN 层（90 天对齐窗口）──
def hn_count(query, days=90):
    from datetime import datetime, timedelta
    since = (datetime.utcnow() - timedelta(days=days)).strftime('%Y-%m-%d')
    url = (f'https://hn.algolia.com/api/v1/search?query={urllib.parse.quote(query)}'
           f'&tags=story&numericFilters=points>50,created_at_i>{int(datetime.utcnow().timestamp())-days*86400}&hitsPerPage=0')
    d = json.loads(urllib.request.urlopen(urllib.request.Request(url, headers={'User-Agent': UA}), timeout=20).read())
    return d.get('nbHits')

hn = {'window_days': 90, 'as_of': date.today().isoformat()}
for label, q in [('AGI', '"AGI"'), ('AI jobs', '"AI jobs"'), ('AI layoffs', '"AI layoffs"')]:
    hn[label] = hn_count(q)
    time.sleep(0.5)

# ── Wiki 层（Q2 窗口 4-6 月 + 1 月基线）──
def wiki(art):
    url = f'https://wikimedia.org/api/rest_v1/metrics/pageviews/per-article/en.wikipedia/all-access/user/{art}/monthly/20260101/20260831'
    d = json.loads(urllib.request.urlopen(urllib.request.Request(url, headers={'User-Agent': UA}), timeout=20).read())
    return {i['timestamp'][:6]: i['views'] for i in d.get('items', [])}

wiki_data = {}
for art in ['ChatGPT', 'Artificial_intelligence', 'Generative_artificial_intelligence', 'Technological_unemployment']:
    try:
        m = wiki(art)
        wiki_data[art] = {'jan': m.get('202601'), 'q2_avg': round((m.get('202604',0)+m.get('202605',0)+m.get('202606',0))/3), 'aug': m.get('202608'), 'monthly': m}
    except Exception as e:
        wiki_data[art] = {'error': str(e)[:80]}
    time.sleep(0.8)

signals = {
    'report': 'Q2 2026', 'frozen': date.today().isoformat(),
    'attribution': {'sec': 'SEC EDGAR FTS', 'hn': f'Algolia HN, 90-day aligned window as of {date.today()}', 'wiki': 'Wikimedia Pageviews'},
    'sec_disclosure': sec, 'hn_90d': hn, 'wiki': wiki_data,
}
out2 = ROOT / 'docs' / 'signals-data-q2-2026.json'
out2.write_text(json.dumps(signals, indent=1))
print('✓', out2.name)
print('\n=== 核对报告数字 ===')
print('SEC AI:', sec['ai_mentions']['2025Q2'], '→', sec['ai_mentions']['2026Q2'], f"({sec['ai_mentions']['yoy_pct']}%)")
print('SEC agents:', sec['agent_mentions']['2025Q2'], '→', sec['agent_mentions']['2026Q2'])
print('信息业 YoY:', employment['USINFO']['yoy_pct'], '| Jun26:', employment['USINFO']['jun26'])
print('制造 YoY (MANEMP):', employment['MANEMP']['yoy_pct'])
print('JOLTS 信息业 layoffs Q2avg:', jolts['JTU5100LDR']['q2_2025_avg'], '→', jolts['JTU5100LDR']['q2_2026_avg'])
print('HN 90d:', hn)
