""" 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()