File size: 5,035 Bytes
2fc729c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
"""The orchestrator that wires the whole loop together.

One cycle =
  1. RESOLVE  any predictions whose date has passed -> judge -> score (feedback)
  2. INGEST   fresh AI + economic signals
  3. PRIOR    job-evolution prior (PCA/GMM/OOD) from configured scenario
  4. FORECAST new falsifiable predictions, calibrated by track record + prior
  5. DEDUP    against the registry
  5b. CROWD   process contributions via CrowdGate (sparse aggregate)
  6. PUBLISH  (or queue for human review)
"""
from __future__ import annotations

import time

try:
    import evolution as ev
    import forecast as fc
    import ingest as ing
    import publish as pub
    from registry import Registry
except ImportError:
    from . import evolution as ev
    from . import forecast as fc
    from . import ingest as ing
    from . import publish as pub
    from .registry import Registry


def _build_evolution_prior(cfg: dict) -> str:
    """Build evolution prior prompt context. Pure aside from seeded GMM bootstrap."""
    ev_cfg = cfg.get("evolution", {})
    scenario = ev_cfg.get("scenario") or ev.CURRENT_AI_SCENARIO
    n_boot = int(ev_cfg.get("n_bootstrap", 50))
    prior = ev.build_prior(current_scenario=scenario, n_bootstrap=n_boot)
    return prior.to_prompt_context()


def run_cycle(cfg: dict) -> dict:
    if cfg.get("mock_llm"):
        fc.set_mock_mode(True)
    reg = Registry(cfg.get("database_path"))
    model = cfg["model"]
    sources = ing.default_sources()

    # 1. resolve due predictions -----------------------------------------
    due = reg.due()
    print(f"[resolve] {len(due)} prediction(s) due")
    if due:
        fresh_for_judging = ing.gather_signals(sources, max_total=30)
        for p in due:
            outcome, why = fc.judge_prediction(
                p, fresh_for_judging, model=model)
            p.resolve(outcome, why)
            reg.update(p)
            if p.outcome is not None:
                try:
                    from services.crowd_service import resolve_contributions_for_prediction
                    n = resolve_contributions_for_prediction(p.id, p.outcome)
                    if n:
                        print(f"  - [crowd] scored {n} contribution(s)")
                except Exception as e:
                    print(f"  - [crowd] resolve error: {e}")
            print(f"  - {p.status.value}: {p.statement[:80]}")

    # 2. ingest -----------------------------------------------------------
    signals = ing.gather_signals(sources, max_total=cfg.get("max_signals", 40))
    print(f"[ingest] {len(signals)} signal(s)")

    # 3. evolution prior --------------------------------------------------
    evolution_prior = _build_evolution_prior(cfg)
    ood_line = evolution_prior.splitlines()[2] if evolution_prior else ""
    print(f"[evolution] {ood_line}")

    # 4. forecast (calibrated by track record + evolution prior) ----------
    track = reg.track_record_summary()
    preds = fc.generate_predictions(
        signals, track, model=model,
        max_predictions=cfg.get("max_predictions", 6),
        evolution_prior=evolution_prior)
    print(f"[forecast] {len(preds)} candidate prediction(s)")

    # 5. dedup + persist --------------------------------------------------
    new_preds = reg.add_many(preds)
    print(f"[registry] {len(new_preds)} new after dedup")

    # 5b. crowd gate on open predictions with contributions -----------------
    if cfg.get("crowd", {}).get("enabled", True):
        try:
            from services.crowd_service import process_open_prediction_crowds
            n_crowd = process_open_prediction_crowds(cfg, reg)
            print(f"[crowd] {n_crowd} prediction(s) with contributions processed")
        except Exception as e:
            print(f"[crowd] processing error: {e}")

    # 6. publish ----------------------------------------------------------
    sb = reg.scoreboard()
    md = pub.render_markdown(new_preds, sb)
    html = pub.render_html(md)
    publishers = build_publishers(cfg)
    pub.publish_or_queue(publishers, md, html, new_preds,
                         require_review=cfg.get("require_review", True))

    return {"new": len(new_preds), "resolved": len(due), "scoreboard": sb}


def build_publishers(cfg: dict) -> list:
    out = []
    pc = cfg.get("publish", {})
    if pc.get("file", {}).get("enabled"):
        out.append(pub.FilePublisher(pc["file"].get("out_dir", "site")))
    if pc.get("webhook", {}).get("url"):
        out.append(pub.WebhookPublisher(pc["webhook"]["url"]))
    if pc.get("git", {}).get("repo_dir"):
        out.append(pub.GitPublisher(pc["git"]["repo_dir"],
                                    pc["git"].get("branch", "main")))
    return out or [pub.FilePublisher("site")]


def run_loop(cfg: dict):
    interval = cfg.get("interval_seconds", 86400)
    print(f"[loop] running every {interval}s. Ctrl-C to stop.")
    while True:
        try:
            run_cycle(cfg)
        except Exception as e:
            print(f"[loop] cycle error: {e}")
        time.sleep(interval)