Spaces:
Runtime error
Runtime error
| """ | |
| Cebuano Fake News Data Entry Tool | |
| =================================== | |
| Manually input Cebuano fake news articles into | |
| data/raw/augmented_ceb_fakes.csv for model training. | |
| Usage: | |
| python add_cebuano_data.py | |
| Features: | |
| - Interactive CLI with menu options | |
| - Paste single-line or multi-line articles | |
| - Preview entries before saving | |
| - View existing dataset stats | |
| - Duplicate detection (warns if near-identical text exists) | |
| """ | |
| import csv | |
| import os | |
| import sys | |
| import textwrap | |
| from datetime import datetime | |
| # ββ Paths ββ | |
| SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__)) | |
| CSV_PATH = os.path.join(SCRIPT_DIR, "data", "raw", "augmented_ceb_fakes.csv") | |
| BACKUP_DIR = os.path.join(SCRIPT_DIR, "data", "raw", ".backups") | |
| def _count_existing_rows() -> int: | |
| """Count how many articles are already in the CSV.""" | |
| if not os.path.exists(CSV_PATH): | |
| return 0 | |
| with open(CSV_PATH, "r", encoding="utf-8") as f: | |
| reader = csv.reader(f) | |
| header = next(reader, None) | |
| return sum(1 for _ in reader) | |
| def _load_existing_articles() -> list[str]: | |
| """Load all existing article texts (for duplicate checking).""" | |
| articles = [] | |
| if not os.path.exists(CSV_PATH): | |
| return articles | |
| with open(CSV_PATH, "r", encoding="utf-8") as f: | |
| reader = csv.reader(f) | |
| next(reader, None) # skip header | |
| for row in reader: | |
| if row: | |
| articles.append(row[0].strip().lower()) | |
| return articles | |
| def _is_duplicate(new_text: str, existing: list[str], threshold: int = 50) -> bool: | |
| """Check if the first `threshold` characters match any existing article.""" | |
| snippet = new_text.strip().lower()[:threshold] | |
| if len(snippet) < 20: | |
| return False | |
| for art in existing: | |
| if art[:threshold] == snippet: | |
| return True | |
| return False | |
| def _backup_csv(): | |
| """Create a timestamped backup of the current CSV.""" | |
| if not os.path.exists(CSV_PATH): | |
| return | |
| os.makedirs(BACKUP_DIR, exist_ok=True) | |
| ts = datetime.now().strftime("%Y%m%d_%H%M%S") | |
| backup_path = os.path.join(BACKUP_DIR, f"augmented_ceb_fakes_{ts}.csv") | |
| with open(CSV_PATH, "r", encoding="utf-8") as src: | |
| with open(backup_path, "w", encoding="utf-8", newline="") as dst: | |
| dst.write(src.read()) | |
| print(f" Backup saved: {os.path.relpath(backup_path, SCRIPT_DIR)}") | |
| def _ensure_csv_exists(): | |
| """Create the CSV with header if it doesn't exist yet.""" | |
| if not os.path.exists(CSV_PATH): | |
| os.makedirs(os.path.dirname(CSV_PATH), exist_ok=True) | |
| with open(CSV_PATH, "w", encoding="utf-8", newline="") as f: | |
| writer = csv.writer(f) | |
| writer.writerow(["article"]) | |
| print(f" Created new CSV: {CSV_PATH}") | |
| def _append_articles(articles: list[str]): | |
| """Append a list of article texts to the CSV.""" | |
| with open(CSV_PATH, "a", encoding="utf-8", newline="") as f: | |
| writer = csv.writer(f) | |
| for text in articles: | |
| writer.writerow([text]) | |
| def _preview_text(text: str, width: int = 80) -> str: | |
| """Return a truncated preview of article text.""" | |
| preview = text.replace("\n", " ").strip() | |
| if len(preview) > width: | |
| return preview[:width] + "..." | |
| return preview | |
| # ββ CLI Display ββ | |
| def print_header(): | |
| print() | |
| print("=" * 60) | |
| print(" CEBUANO FAKE NEWS β DATA ENTRY TOOL") | |
| print("=" * 60) | |
| print(f" CSV: {os.path.relpath(CSV_PATH, SCRIPT_DIR)}") | |
| print(f" Existing articles: {_count_existing_rows():,}") | |
| print("=" * 60) | |
| def print_menu(): | |
| print() | |
| print(" [1] Add a single article") | |
| print(" [2] Add multiple articles (batch mode)") | |
| print(" [3] View dataset stats") | |
| print(" [4] View last 5 entries") | |
| print(" [5] Exit") | |
| print() | |
| def input_single_article() -> str | None: | |
| """Prompt the user to type/paste a single article. | |
| Supports multi-line input: type on multiple lines, then enter | |
| a blank line to finish. | |
| """ | |
| print() | |
| print(" Paste or type the Cebuano fake news article below.") | |
| print(" (Enter a blank line when done, or type 'cancel' to abort)") | |
| print(" " + "-" * 50) | |
| lines = [] | |
| while True: | |
| try: | |
| line = input(" > ") | |
| except EOFError: | |
| break | |
| if line.strip().lower() == "cancel": | |
| return None | |
| if line.strip() == "" and lines: | |
| break | |
| lines.append(line) | |
| text = " ".join(lines).strip() | |
| if not text: | |
| print(" (empty input β skipped)") | |
| return None | |
| return text | |
| def add_single_article(): | |
| """Menu option 1: add one article.""" | |
| _ensure_csv_exists() | |
| existing = _load_existing_articles() | |
| text = input_single_article() | |
| if text is None: | |
| print(" Cancelled.") | |
| return | |
| # Duplicate check | |
| if _is_duplicate(text, existing): | |
| print() | |
| print(" β WARNING: This article appears to be a duplicate!") | |
| confirm = input(" Add anyway? (y/n): ").strip().lower() | |
| if confirm != "y": | |
| print(" Skipped.") | |
| return | |
| # Preview and confirm | |
| print() | |
| print(" Preview:") | |
| print(f" {_preview_text(text, 100)}") | |
| print(f" Length: {len(text):,} chars, {len(text.split()):,} words") | |
| print() | |
| confirm = input(" Save this article? (y/n): ").strip().lower() | |
| if confirm != "y": | |
| print(" Discarded.") | |
| return | |
| _append_articles([text]) | |
| print(f" β Article saved! Total articles now: {_count_existing_rows():,}") | |
| def add_batch_articles(): | |
| """Menu option 2: add multiple articles in batch.""" | |
| _ensure_csv_exists() | |
| existing = _load_existing_articles() | |
| print() | |
| print(" BATCH MODE β Enter one article per prompt.") | |
| print(" Type 'done' when finished, 'cancel' to discard all.") | |
| print() | |
| pending: list[str] = [] | |
| article_num = 1 | |
| while True: | |
| print(f" --- Article #{article_num} ---") | |
| text = input_single_article() | |
| if text is None: | |
| # Check if user typed cancel | |
| break | |
| if _is_duplicate(text, existing): | |
| print(" β Possible duplicate β skipping this one.") | |
| continue | |
| pending.append(text) | |
| print(f" β Queued ({len(pending)} pending)") | |
| article_num += 1 | |
| cont = input(" Add another? (y/n/done): ").strip().lower() | |
| if cont in ("n", "done"): | |
| break | |
| if not pending: | |
| print(" No articles to save.") | |
| return | |
| # Preview all | |
| print() | |
| print(f" === {len(pending)} articles ready to save ===") | |
| for i, art in enumerate(pending, 1): | |
| print(f" {i}. {_preview_text(art, 70)}") | |
| print() | |
| confirm = input(f" Save all {len(pending)} articles? (y/n): ").strip().lower() | |
| if confirm != "y": | |
| print(" Discarded all.") | |
| return | |
| _backup_csv() | |
| _append_articles(pending) | |
| print(f" β {len(pending)} articles saved! Total now: {_count_existing_rows():,}") | |
| def show_stats(): | |
| """Menu option 3: show dataset statistics.""" | |
| if not os.path.exists(CSV_PATH): | |
| print(" CSV not found β no data yet.") | |
| return | |
| articles = _load_existing_articles() | |
| total = len(articles) | |
| if total == 0: | |
| print(" Dataset is empty.") | |
| return | |
| word_counts = [len(a.split()) for a in articles] | |
| char_counts = [len(a) for a in articles] | |
| print() | |
| print(" ββββββββββββββββββββββββββββββββββββββββ") | |
| print(" β DATASET STATISTICS β") | |
| print(" β βββββββββββββββββββββββββββββββββββββββ£") | |
| print(f" β Total articles: {total:>10,} β") | |
| print(f" β Avg words/article: {sum(word_counts)//total:>10,} β") | |
| print(f" β Min words: {min(word_counts):>10,} β") | |
| print(f" β Max words: {max(word_counts):>10,} β") | |
| print(f" β Avg chars/article: {sum(char_counts)//total:>10,} β") | |
| print(f" β CSV size: {os.path.getsize(CSV_PATH):>10,} B β") | |
| print(" ββββββββββββββββββββββββββββββββββββββββ") | |
| print() | |
| def show_last_entries(): | |
| """Menu option 4: show last 5 entries.""" | |
| if not os.path.exists(CSV_PATH): | |
| print(" CSV not found β no data yet.") | |
| return | |
| articles = [] | |
| with open(CSV_PATH, "r", encoding="utf-8") as f: | |
| reader = csv.reader(f) | |
| next(reader, None) | |
| for row in reader: | |
| if row: | |
| articles.append(row[0]) | |
| if not articles: | |
| print(" No articles in dataset.") | |
| return | |
| last5 = articles[-5:] | |
| print() | |
| print(f" Last {len(last5)} entries (of {len(articles)} total):") | |
| print(" " + "-" * 50) | |
| for i, art in enumerate(last5, len(articles) - len(last5) + 1): | |
| print(f" #{i}: {_preview_text(art, 70)}") | |
| print() | |
| # ββ Main Loop ββ | |
| def main(): | |
| print_header() | |
| while True: | |
| print_menu() | |
| try: | |
| choice = input(" Choose an option [1-5]: ").strip() | |
| except (EOFError, KeyboardInterrupt): | |
| print("\n Goodbye!") | |
| break | |
| if choice == "1": | |
| add_single_article() | |
| elif choice == "2": | |
| add_batch_articles() | |
| elif choice == "3": | |
| show_stats() | |
| elif choice == "4": | |
| show_last_entries() | |
| elif choice == "5": | |
| print(" Goodbye!") | |
| break | |
| else: | |
| print(" Invalid choice, try again.") | |
| if __name__ == "__main__": | |
| main() | |