Spaces:
Paused
Paused
Download monitor_methodologies.py from ruffy1601/carbon-ai-chatbot: direct link, hf CLI and curl.
- Browser
- Download file 15.8 kB
-
https://huggingface.co/spaces/ruffy1601/carbon-ai-chatbot/resolve/main/monitor_methodologies.py
- Command line
-
hf download hf://spaces/ruffy1601/carbon-ai-chatbot/monitor_methodologies.py
-
curl -L -o monitor_methodologies.py https://huggingface.co/spaces/ruffy1601/carbon-ai-chatbot/resolve/main/monitor_methodologies.py
15.8 kB
| """ | |
| λ°©λ²λ‘ μλ λͺ¨λν°λ§ νμ΄νλΌμΈ | |
| ================================ | |
| μ£ΌκΈ°μ μΌλ‘ μ€ννμ¬ μ κ·/κ°μ λ°©λ²λ‘ μ κ°μ§νκ³ | |
| knowledge_baseλ₯Ό μλμΌλ‘ κ°±μ ν©λλ€. | |
| μ€ν: | |
| python monitor_methodologies.py # λ³κ²½ κ°μ§ + μλ μ λ°μ΄νΈ | |
| python monitor_methodologies.py --check-only # λ³κ²½ κ°μ§λ§ (μ λ°μ΄νΈ μμ) | |
| python monitor_methodologies.py --scrape # μ격 λ μ§μ€νΈλ¦¬ μ€ν¬λν ν¬ν¨ | |
| python monitor_methodologies.py --dry-run # 미리보기 | |
| νλ¦: | |
| β λ‘컬 λ°©λ²λ‘ λλ ν 리 μ€μΊ (CDM/ORS/GS/Verra) | |
| β‘ methodology_state.json κ³Ό λΉκ΅ β μ κ·/κ°μ κ°μ§ | |
| β’ λ³κ²½λ νμΌ β knowledge_base/ λ³΅μ¬ | |
| β£ λ³κ²½ μμΌλ©΄ β update_all.py μ€ν (ChromaDB μ¬κ΅¬μΆ) | |
| β€ state νμΌ κ°±μ | |
| """ | |
| from __future__ import annotations | |
| import argparse | |
| import hashlib | |
| import json | |
| import shutil | |
| import subprocess | |
| import sys | |
| import time | |
| from dataclasses import asdict, dataclass | |
| from datetime import datetime | |
| from pathlib import Path | |
| from typing import Optional | |
| # ββ κ²½λ‘ μ€μ ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| ROOT = Path(__file__).parent | |
| KNOWLEDGE_BASE = ROOT / "react-agent" / "knowledge_base" | |
| STATE_FILE = ROOT / "methodology_state.json" | |
| # λ μ§μ€νΈλ¦¬λ³ λ‘컬 λλ ν 리 λ° prefix μ€μ | |
| REGISTRIES = { | |
| "CDM_PA": { | |
| "dir": ROOT / "cdm_methodologies" / "CDM_PA_Large_Scale_Methodologies", | |
| "prefix": "cdm_pa_", | |
| "label": "CDM λκ·λͺ¨", | |
| }, | |
| "CDM_SSC": { | |
| "dir": ROOT / "cdm_methodologies" / "CDM_SSC_Small_Scale_Methodologies", | |
| "prefix": "cdm_ssc_", | |
| "label": "CDM μκ·λͺ¨", | |
| }, | |
| "CDM_AR": { | |
| "dir": ROOT / "cdm_methodologies" / "CDM_AR_SSCAR_Afforestation_Reforestation_Methodologies", | |
| "prefix": "cdm_ar_", | |
| "label": "CDM μ‘°λ¦Ό/μ¬μ‘°λ¦Ό", | |
| }, | |
| "ORS": { | |
| "dir": ROOT / "ors_methodologies", | |
| "prefix": "ors_", | |
| "label": "ORS (κ΅λ΄ μμλ°°μΆμ)", | |
| }, | |
| "GS": { | |
| "dir": ROOT / "gs_methodologies", | |
| "prefix": "gs_", | |
| "label": "Gold Standard", | |
| }, | |
| "VERRA": { | |
| "dir": ROOT / "verra_methodologies", | |
| "prefix": "verra_", | |
| "label": "Verra VCS", | |
| }, | |
| } | |
| # knowledge_baseμ 볡μ¬ν νμΌ νμ₯μ | |
| ALLOWED_EXT = {".pdf", ".hwp", ".docx", ".txt"} | |
| # μ€ν¬λνΌ μ€ν¬λ¦½νΈ (--scrape μ΅μ μ¬μ© μ) | |
| SCRAPERS = { | |
| "ORS": ROOT / "scrape_ors.py", | |
| "GS": ROOT / "scrape_gs.py", | |
| "VERRA": ROOT / "scrape_verra.py", | |
| } | |
| # ββ λ°μ΄ν° ꡬ쑰 ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| class FileRecord: | |
| registry: str | |
| filename: str # μλ³Έ νμΌλͺ | |
| kb_filename: str # knowledge_base λ΄ νμΌλͺ | |
| file_hash: str # MD5 ν΄μ | |
| file_size: int | |
| last_seen: str # ISO λ μ§ | |
| in_kb: bool = False # knowledge_baseμ 볡μ¬λλμ§ | |
| def compute_hash(path: Path) -> str: | |
| """νμΌ MD5 ν΄μ κ³μ°""" | |
| h = hashlib.md5() | |
| with open(path, "rb") as f: | |
| for chunk in iter(lambda: f.read(65536), b""): | |
| h.update(chunk) | |
| return h.hexdigest() | |
| def load_state() -> dict[str, FileRecord]: | |
| """μν νμΌ λ‘λ""" | |
| if not STATE_FILE.exists(): | |
| return {} | |
| try: | |
| with open(STATE_FILE, "r", encoding="utf-8") as f: | |
| raw = json.load(f) | |
| return {k: FileRecord(**v) for k, v in raw.items()} | |
| except Exception: | |
| return {} | |
| def save_state(state: dict[str, FileRecord]) -> None: | |
| """μν νμΌ μ μ₯""" | |
| with open(STATE_FILE, "w", encoding="utf-8") as f: | |
| json.dump({k: asdict(v) for k, v in state.items()}, f, | |
| ensure_ascii=False, indent=2) | |
| def make_state_key(registry: str, filename: str) -> str: | |
| return f"{registry}:{filename}" | |
| # ββ μ€μΊ ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def scan_registry(registry_id: str, config: dict) -> dict[str, FileRecord]: | |
| """λ μ§μ€νΈλ¦¬ λλ ν 리 μ€μΊ β νμ¬ νμΌ λͺ©λ‘ λ°ν""" | |
| dir_path: Path = config["dir"] | |
| prefix: str = config["prefix"] | |
| records = {} | |
| if not dir_path.exists(): | |
| return records | |
| for file_path in sorted(dir_path.iterdir()): | |
| if file_path.suffix.lower() not in ALLOWED_EXT: | |
| continue | |
| if not file_path.is_file(): | |
| continue | |
| key = make_state_key(registry_id, file_path.name) | |
| file_hash = compute_hash(file_path) | |
| kb_filename = prefix + file_path.name | |
| records[key] = FileRecord( | |
| registry=registry_id, | |
| filename=file_path.name, | |
| kb_filename=kb_filename, | |
| file_hash=file_hash, | |
| file_size=file_path.stat().st_size, | |
| last_seen=datetime.now().isoformat(), | |
| in_kb=(KNOWLEDGE_BASE / kb_filename).exists(), | |
| ) | |
| return records | |
| def scan_all() -> dict[str, FileRecord]: | |
| """λͺ¨λ λ μ§μ€νΈλ¦¬ μ€μΊ""" | |
| all_records = {} | |
| for registry_id, config in REGISTRIES.items(): | |
| records = scan_registry(registry_id, config) | |
| all_records.update(records) | |
| return all_records | |
| # ββ λ³κ²½ κ°μ§ βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| class ChangeReport: | |
| new_files: list[FileRecord] # μ κ· λ°©λ²λ‘ | |
| updated_files: list[FileRecord] # κ°μ λ λ°©λ²λ‘ | |
| removed_keys: list[str] # μμ λ λ°©λ²λ‘ | |
| not_in_kb: list[FileRecord] # λ‘컬μ μμ§λ§ KBμ μλ νμΌ | |
| def detect_changes( | |
| current: dict[str, FileRecord], | |
| previous: dict[str, FileRecord], | |
| ) -> ChangeReport: | |
| """νμ¬ μν vs μ΄μ μν λΉκ΅""" | |
| new_files = [] | |
| updated_files = [] | |
| not_in_kb = [] | |
| for key, rec in current.items(): | |
| if key not in previous: | |
| new_files.append(rec) | |
| elif previous[key].file_hash != rec.file_hash: | |
| updated_files.append(rec) | |
| elif not rec.in_kb: | |
| not_in_kb.append(rec) | |
| removed_keys = [k for k in previous if k not in current] | |
| return ChangeReport( | |
| new_files=new_files, | |
| updated_files=updated_files, | |
| removed_keys=removed_keys, | |
| not_in_kb=not_in_kb, | |
| ) | |
| # ββ Knowledge Base λκΈ°ν βββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def sync_to_knowledge_base( | |
| records: list[FileRecord], | |
| dry_run: bool = False, | |
| ) -> list[FileRecord]: | |
| """νμΌλ€μ knowledge_base/μ 볡μ¬""" | |
| synced = [] | |
| KNOWLEDGE_BASE.mkdir(parents=True, exist_ok=True) | |
| for rec in records: | |
| # μλ³Έ νμΌ κ²½λ‘ λ³΅μ | |
| config = REGISTRIES[rec.registry] | |
| src = config["dir"] / rec.filename | |
| dst = KNOWLEDGE_BASE / rec.kb_filename | |
| if not src.exists(): | |
| print(f" [κ²½κ³ ] μλ³Έ νμΌ μμ: {src}") | |
| continue | |
| if dry_run: | |
| print(f" [DRY-RUN] λ³΅μ¬ μμ : {rec.kb_filename}") | |
| synced.append(rec) | |
| continue | |
| shutil.copy2(src, dst) | |
| rec.in_kb = True | |
| synced.append(rec) | |
| print(f" 볡μ¬: {rec.kb_filename} ({rec.file_size / 1024:.0f}KB)") | |
| return synced | |
| def remove_from_knowledge_base( | |
| keys: list[str], | |
| state: dict[str, FileRecord], | |
| dry_run: bool = False, | |
| ) -> None: | |
| """μμ λ λ°©λ²λ‘ μ knowledge_baseμμλ μ κ±°""" | |
| for key in keys: | |
| if key not in state: | |
| continue | |
| rec = state[key] | |
| dst = KNOWLEDGE_BASE / rec.kb_filename | |
| if dst.exists(): | |
| if dry_run: | |
| print(f" [DRY-RUN] μμ μμ : {rec.kb_filename}") | |
| else: | |
| dst.unlink() | |
| print(f" μμ : {rec.kb_filename}") | |
| # ββ μ€ν¬λνΌ μ€ν ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def run_scrapers(targets: Optional[list[str]] = None) -> None: | |
| """μ격 λ μ§μ€νΈλ¦¬ μ€ν¬λν (μ νμΌ λ€μ΄λ‘λ)""" | |
| targets = targets or list(SCRAPERS.keys()) | |
| for name in targets: | |
| scraper = SCRAPERS.get(name) | |
| if not scraper or not scraper.exists(): | |
| print(f" [κ²½κ³ ] μ€ν¬λνΌ μμ: {name}") | |
| continue | |
| print(f"\n μ€ν¬λν: {name}...") | |
| result = subprocess.run( | |
| [sys.executable, str(scraper)], | |
| cwd=str(ROOT), | |
| ) | |
| if result.returncode != 0: | |
| print(f" [κ²½κ³ ] {name} μ€ν¬λνΌ μ€ν¨ (μ’ λ£μ½λ {result.returncode})") | |
| # ββ update_all.py μ€ν ββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def run_update_pipeline(dry_run: bool = False) -> bool: | |
| """update_all.py μ€ν (ChromaDB + GraphDB + λ°°ν¬)""" | |
| update_script = ROOT / "update_all.py" | |
| if not update_script.exists(): | |
| print(" [μ€λ₯] update_all.py μμ") | |
| return False | |
| cmd = [sys.executable, str(update_script), "--yes", "--skip-graph", "--skip-community"] | |
| if dry_run: | |
| cmd.append("--dry-run") | |
| print(f"\n update_all.py μ€ν μ€...") | |
| result = subprocess.run(cmd, cwd=str(ROOT)) | |
| return result.returncode == 0 | |
| # ββ 리ν¬νΈ μΆλ ₯ ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def print_report(report: ChangeReport, current: dict[str, FileRecord]) -> None: | |
| """λ³κ²½ κ°μ§ κ²°κ³Ό μΆλ ₯""" | |
| total = len(report.new_files) + len(report.updated_files) + len(report.not_in_kb) | |
| print("\n" + "=" * 60) | |
| print(" λ°©λ²λ‘ λͺ¨λν°λ§ κ²°κ³Ό") | |
| print("=" * 60) | |
| # λ μ§μ€νΈλ¦¬λ³ ν΅κ³ | |
| registry_counts = {} | |
| for rec in current.values(): | |
| registry_counts[rec.registry] = registry_counts.get(rec.registry, 0) + 1 | |
| print("\n λ μ§μ€νΈλ¦¬λ³ 보μ λ°©λ²λ‘ :") | |
| for rid, config in REGISTRIES.items(): | |
| count = registry_counts.get(rid, 0) | |
| label = config["label"] | |
| print(f" {label:25s}: {count:4d}κ°") | |
| print(f"\n μ΄ λ³΄μ λ°©λ²λ‘ : {len(current)}κ°") | |
| # λ³κ²½μ¬ν | |
| if report.new_files: | |
| print(f"\n [μ κ·] {len(report.new_files)}κ° λ°κ²¬:") | |
| for rec in report.new_files[:10]: | |
| label = REGISTRIES[rec.registry]["label"] | |
| print(f" [{label}] {rec.filename[:60]}") | |
| if len(report.new_files) > 10: | |
| print(f" ... μΈ {len(report.new_files)-10}κ°") | |
| if report.updated_files: | |
| print(f"\n [κ°μ ] {len(report.updated_files)}κ° κ°μ§:") | |
| for rec in report.updated_files[:10]: | |
| label = REGISTRIES[rec.registry]["label"] | |
| print(f" [{label}] {rec.filename[:60]}") | |
| if report.not_in_kb: | |
| print(f"\n [λ―ΈλκΈ°] KBμ μλ νμΌ {len(report.not_in_kb)}κ°") | |
| if report.removed_keys: | |
| print(f"\n [μμ ] {len(report.removed_keys)}κ° λ°©λ²λ‘ μ κ±°λ¨") | |
| if total == 0: | |
| print("\n λ³κ²½μ¬ν μμ - Knowledge Base μ΅μ μν") | |
| else: | |
| print(f"\n β Knowledge Base κ°±μ νμ: {total}κ° νμΌ") | |
| print("=" * 60) | |
| # ββ λ©μΈ βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def main(): | |
| parser = argparse.ArgumentParser( | |
| description="λ°©λ²λ‘ μλ λͺ¨λν°λ§ νμ΄νλΌμΈ" | |
| ) | |
| parser.add_argument("--check-only", action="store_true", | |
| help="λ³κ²½ κ°μ§λ§ (KB κ°±μ μμ)") | |
| parser.add_argument("--scrape", action="store_true", | |
| help="μ격 λ μ§μ€νΈλ¦¬ μ€ν¬λν ν κ°μ§") | |
| parser.add_argument("--scrape-only", nargs="+", metavar="REGISTRY", | |
| help=f"νΉμ λ μ§μ€νΈλ¦¬λ§ μ€ν¬λν ({', '.join(SCRAPERS.keys())})") | |
| parser.add_argument("--dry-run", action="store_true", | |
| help="미리보기 (μ€μ νμΌ λ³κ²½ μμ)") | |
| parser.add_argument("--skip-update", action="store_true", | |
| help="update_all.py μ€ν 건λλ") | |
| args = parser.parse_args() | |
| print("=" * 60) | |
| print(" λ°©λ²λ‘ μλ λͺ¨λν°λ§ νμ΄νλΌμΈ") | |
| print(f" μ€ν μκ°: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}") | |
| print("=" * 60) | |
| # Step 0: μ€ν¬λν (μ΅μ ) | |
| if args.scrape or args.scrape_only: | |
| print("\n[Step 0] μ격 λ μ§μ€νΈλ¦¬ μ€ν¬λν") | |
| targets = args.scrape_only or None | |
| run_scrapers(targets) | |
| # Step 1: νμ¬ μν μ€μΊ | |
| print("\n[Step 1] λ‘컬 λ°©λ²λ‘ λλ ν 리 μ€μΊ μ€...") | |
| t0 = time.perf_counter() | |
| current_state = scan_all() | |
| print(f" μλ£: {len(current_state)}κ° νμΌ ({time.perf_counter()-t0:.1f}μ΄)") | |
| # Step 2: μ΄μ μν λ‘λ & λΉκ΅ | |
| print("\n[Step 2] λ³κ²½μ¬ν κ°μ§ μ€...") | |
| previous_state = load_state() | |
| report = detect_changes(current_state, previous_state) | |
| # 리ν¬νΈ μΆλ ₯ | |
| print_report(report, current_state) | |
| # --check-only μ΄λ©΄ μ¬κΈ°μ μ’ λ£ | |
| if args.check_only: | |
| save_state(current_state) | |
| return | |
| # Step 3: Knowledge Base λκΈ°ν | |
| files_to_sync = report.new_files + report.updated_files + report.not_in_kb | |
| if files_to_sync: | |
| print(f"\n[Step 3] Knowledge Base λκΈ°ν ({len(files_to_sync)}κ°)") | |
| synced = sync_to_knowledge_base(files_to_sync, dry_run=args.dry_run) | |
| # μμ λ λ°©λ²λ‘ KBμμλ μ κ±° | |
| if report.removed_keys: | |
| remove_from_knowledge_base( | |
| report.removed_keys, previous_state, dry_run=args.dry_run | |
| ) | |
| # μν νμΌ κ°±μ | |
| if not args.dry_run: | |
| # νμ¬ μνλ‘ μ λ°μ΄νΈ (removedλ μ μΈ) | |
| new_state = {k: v for k, v in current_state.items()} | |
| save_state(new_state) | |
| print(f"\n μν νμΌ μ μ₯: {STATE_FILE}") | |
| # Step 4: update_all.py μ€ν | |
| if not args.skip_update and synced: | |
| print(f"\n[Step 4] Knowledge Base μ¬κ΅¬μΆ (update_all.py)") | |
| if args.dry_run: | |
| print(" [DRY-RUN] update_all.py μ€ν 건λλ") | |
| else: | |
| success = run_update_pipeline(dry_run=False) | |
| if success: | |
| print(" Knowledge Base μ¬κ΅¬μΆ μλ£") | |
| print("\n λ€μ λ¨κ³:") | |
| print(" git push origin main # HuggingFace Spaces λ°°ν¬") | |
| else: | |
| print(" [κ²½κ³ ] update_all.py μ€ν¨ - μλ νμΈ νμ") | |
| else: | |
| # λ³κ²½ μμ΄λ μνλ μ μ₯ (last_seen κ°±μ ) | |
| if not args.dry_run: | |
| save_state(current_state) | |
| print("\n Knowledge Base κ°±μ λΆνμ") | |
| print(f"\nμλ£: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n") | |
| if __name__ == "__main__": | |
| main() | |