((set, get) => ({
setShowCitationModal: (show) => set({ showCitationModal: show }),
// Document selector
->>>>>>> dev
showDocumentSelector: false,
setShowDocumentSelector: (show) => set({ showDocumentSelector: show }),
}));
\ No newline at end of file
diff --git a/requirements.txt b/requirements.txt
index 27e92906..29e739c6 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,10 +1,3 @@
-<<<<<<< HEAD
-flask==3.1.1
-flask-cors==6.0.1
-python-dotenv==1.0.1
-openai==1.12.0
-faiss-cpu==1.11.0
-=======
# Core Flask and Web Framework
flask==3.1.1
flask-cors==6.0.1
@@ -16,20 +9,11 @@ PyYAML==6.0.2
# AI and Language Models
openai==1.95.1
->>>>>>> dev
langchain==0.1.12
langchain-community==0.0.38
langchain-core==0.1.53
langchain-openai==0.0.8
langchain-text-splitters==0.0.2
-<<<<<<< HEAD
-pydantic==2.6.1
-PyYAML==6.0.2
-requests==2.31.0
-tiktoken==0.6.0
-numpy==1.26.4
-pandas==2.2.3
-=======
langgraph==0.0.27
langsmith==0.1.22
@@ -102,4 +86,3 @@ typing_extensions==4.13.2
typing-inspect==0.9.0
typing-inspection==0.4.0
annotated-types==0.7.0
->>>>>>> dev
diff --git a/scripts/README_xml_analysis.md b/scripts/README_xml_analysis.md
deleted file mode 100644
index 0e743152..00000000
--- a/scripts/README_xml_analysis.md
+++ /dev/null
@@ -1,127 +0,0 @@
-# XML Label Analysis Scripts
-
-This directory contains scripts for analyzing XML files in the data directory and comparing XML label types across different folders (HOSPICE, MPFS, SNF).
-
-## Scripts Overview
-
-### 1. `analyze_xml_labels.py` - Comprehensive Analysis Script
-
-**Purpose**: Performs a detailed analysis of XML labels across all folders in the data directory.
-
-**Features**:
-- Analyzes all XML files in HOSPICE, MPFS, and SNF folders
-- Extracts all unique XML labels (tag names) from each file
-- Compares labels across folders to check for consistency
-- Generates detailed reports and saves results to JSON
-- Provides comprehensive statistics and analysis
-
-**Usage**:
-```bash
-python scripts/analyze_xml_labels.py
-```
-
-**Output**:
-- Console report with detailed analysis
-- JSON file: `xml_label_analysis_results.json`
-- Summary indicating whether folders use consistent XML labels
-
-### 2. `quick_xml_label_check.py` - Quick Check Script
-
-**Purpose**: Provides a concise summary of XML label consistency across folders.
-
-**Features**:
-- Fast analysis of XML label consistency
-- Simple console output with key findings
-- No file output, just immediate results
-
-**Usage**:
-```bash
-python scripts/quick_xml_label_check.py
-```
-
-**Output**:
-- Quick summary of label counts per folder
-- Whether all folders use the same label set
-- List of unique labels per folder (if any differences found)
-- List of common labels across all folders
-
-### 3. `test_xml_label_analyzer.py` - Unit Tests
-
-**Purpose**: Unit tests to verify the functionality of the XML label analyzer.
-
-**Features**:
-- Tests all major functions of the analyzer
-- Uses temporary test files
-- Verifies edge cases (empty folders, nonexistent folders)
-- Ensures correct label extraction and comparison
-
-**Usage**:
-```bash
-python scripts/test_xml_label_analyzer.py
-```
-
-## Analysis Results Summary
-
-Based on the analysis of the current data directory:
-
-### Label Counts by Folder Type:
-- **HOSPICE**: 52 unique XML labels
-- **MPFS**: 56 unique XML labels
-- **SNF**: 51 unique XML labels
-
-### Consistency Check:
-❌ **INCONSISTENT** - Folders have different XML label sets
-
-### Differences Found:
-- **MPFS** has 4 unique labels not found in other folders:
- - `APPENDIX`
- - `CONTENTS`
- - `NOTE`
- - `SECHD`
-
-### Common Labels:
-All three folder types share 49 common XML labels, including:
-- `RULE`, `PREAMB`, `AGENCY`, `SUBJECT`
-- `P`, `HD`, `SECTION`, `SECTNO`
-- `FTNT`, `FTREF`, `SU`, `E`
-- And 39 other common labels
-
-## Key Findings
-
-1. **High Consistency**: 49 out of 52-56 labels are shared across all folder types
-2. **Minor Differences**: Only MPFS has additional labels, likely for specific document features
-3. **Core Structure**: All folders use the same core XML structure for regulatory documents
-4. **Processing Compatibility**: The differences are minor enough that the same XML processing logic can handle all folder types
-
-## Technical Details
-
-### XML Label Categories:
-- **Document Structure**: `RULE`, `PREAMB`, `SUPLINF`
-- **Metadata**: `AGENCY`, `SUBJECT`, `CFR`, `DEPDOC`, `RIN`
-- **Content**: `P`, `HD`, `SECTION`, `SECTNO`
-- **Formatting**: `E`, `SU`, `FTNT`, `FTREF`
-- **Tables**: `GPOTABLE`, `ROW`, `CHED`
-- **MPFS-specific**: `APPENDIX`, `CONTENTS`, `NOTE`, `SECHD`
-
-### Error Handling:
-- Scripts handle malformed XML files gracefully
-- Empty or nonexistent folders are handled appropriately
-- Detailed logging for debugging
-
-## Usage Recommendations
-
-1. **For Quick Checks**: Use `quick_xml_label_check.py`
-2. **For Detailed Analysis**: Use `analyze_xml_labels.py`
-3. **For Development**: Run `test_xml_label_analyzer.py` to verify functionality
-4. **For Integration**: Import `XMLLabelAnalyzer` class from `analyze_xml_labels.py`
-
-## Dependencies
-
-- Python 3.6+
-- Standard library modules: `xml.etree.ElementTree`, `pathlib`, `json`, `logging`
-- No external dependencies required
-
-## Author
-
-Fanxing Bu
-Date: 2024-12-19
\ No newline at end of file
diff --git a/scripts/XML_LABEL_ANALYSIS_SUMMARY.md b/scripts/XML_LABEL_ANALYSIS_SUMMARY.md
deleted file mode 100644
index c9012817..00000000
--- a/scripts/XML_LABEL_ANALYSIS_SUMMARY.md
+++ /dev/null
@@ -1,251 +0,0 @@
-# XML标签分析总结与优化方案
-
-## 📊 分析结果总览
-
-### 标签统计
-- **HOSPICE**: 52个唯一XML标签
-- **MPFS**: 56个唯一XML标签
-- **SNF**: 51个唯一XML标签
-- **共有标签**: 49个
-- **MPFS独有标签**: 4个 (APPENDIX, CONTENTS, NOTE, SECHD)
-
-### 一致性评估
-❌ **INCONSISTENT** - 文件夹间存在标签差异,但核心结构高度一致
-
----
-
-## 🏷️ 完整XML标签列表
-
-### 🔗 所有文件夹共有的标签 (49个)
-```
-ACT, ADD, AGENCY, AGY, AMDPAR, AUTH, BILCOD, BOXHD, CFR, CHED, DATED, DATES, DEPDOC, E, EFFDATE, ENT, FP, FRDOC, FTNT, FTREF, FURINF, GID, GPH, GPOTABLE, HD, LI, LSTSUB, NAME, P, PART, PREAMB, PRORULE, PRTPAGE, REGTEXT, RIN, ROW, RULE, SECTION, SECTNO, SIG, STARS, SU, SUBAGY, SUBJECT, SUM, SUPLINF, TITLE, TNOTE, TTITLE
-```
-
-### 📁 各文件夹独有标签
-
-#### HOSPICE 文件夹 (52个标签)
-**独有标签**: 无 (0个)
-**额外标签**: `FR`, `SUBPART`, `TDESC`
-
-#### MPFS 文件夹 (56个标签)
-**独有标签**: 4个
-- `APPENDIX` - 附录
-- `CONTENTS` - 目录
-- `NOTE` - 注释
-- `SECHD` - 章节标题
-
-**额外标签**: `EXTRACT`, `FR`, `SUBPART`
-
-#### SNF 文件夹 (51个标签)
-**独有标签**: 无 (0个)
-**额外标签**: `EXTRACT`, `TDESC`
-
----
-
-## 🎯 基于语义分析的标签分类
-
-### ✅ 建议纳入Chunk的标签
-
-#### 主要内容标签
-- **`P`** - 正文段落 (主要内容来源)
-- **`E`** - 强调字段/子标题 (保留在段落中)
-- **`SU`** - 上标引用 (脚注引用)
-- **`FTNT`** - 脚注内容 (重要补充信息)
-- **`SUMMARY`** - 摘要内容 (重要概述)
-- **`SUBJECT`** - 主题标题 (可作为section_header)
-- **`DATES`** - 日期信息 (重要时间信息)
-- **`HD`** - 标题 (结构化的标题信息)
-- **`SECTION`** - 章节 (重要结构信息)
-- **`SECTNO`** - 章节编号 (重要标识)
-
-#### 格式化标签
-- **`LI`** - 列表项
-- **`LSTSUB`** - 列表子项
-- **`ROW`** - 表格行
-- **`GPOTABLE`** - 政府印刷办公室表格
-- **`TTITLE`** - 表格标题
-- **`CHED`** - 表格列标题
-- **`BOXHD`** - 表格框标题
-- **`TDESC`** - 表格描述
-- **`TNOTE`** - 表格注释
-
-### 📋 建议用于Metadata的标签
-
-#### 文档信息
-- **`AGENCY`** - 机构信息
-- **`SUBAGY`** - 子机构
-- **`CFR`** - 联邦法规代码
-- **`DEPDOC`** - 部门文档编号
-- **`RIN`** - 法规信息编号
-- **`ACT`** - 法案信息
-- **`AUTH`** - 授权依据
-
-#### 签署信息
-- **`SIG`** - 签署信息容器
-- **`NAME`** - 签署人姓名
-- **`TITLE`** - 签署人职称
-
-#### 文档标识
-- **`FRDOC`** - 联邦注册文档编号
-- **`BILCOD`** - 账单代码
-- **`GID`** - 图形ID
-- **`PRTPAGE`** - 页码信息
-
-#### 结构容器
-- **`AMDPAR`** - 修订段落引导语
-- **`PRORULE`** - 拟议规则
-- **`REGTEXT`** - 法规文本容器
-- **`PREAMB`** - 前言容器
-- **`SUPLINF`** - 补充信息容器
-- **`RULE`** - 规则容器
-
-### ❌ 建议忽略的标签
-
-#### 纯格式化标签
-- **`PRTPAGE`** - 页码标记 (仅表示分页,不具语义)
-- **`GPH`** - 图形页容器 (页脚图页索引,与chunk内容无关)
-- **`STARS`** - 分隔符 (格式标记,不纳入chunk内容)
-- **`FTREF`** - 脚注引用标记 (仅格式化)
-
----
-
-## 🧠 特殊处理标签说明
-
-### 重要语义标签
-| 标签 | 用途 | 处理建议 |
-|------|------|----------|
-| `...` | 标注小节内的小标题或关键术语 | 保留在chunk中,提取T属性 |
-| ` + ` 组合 | 法规正文中小节内的结构性语言 | 保留完整结构 |
-| `` | 授权依据 | 放入metadata,不入chunk |
-| `` | 补充材料 | 通常不纳入chunk |
-
-### 层级构建标签
-| 标签 | 层级 | 用途 |
-|------|------|------|
-| `` | 1 | 主标题 |
-| `` | 2 | 一级标题 |
-| `` | 3 | 二级标题 |
-| `` | 4 | 三级标题 |
-
----
-
-## 🏗️ 优化Chunk生成策略
-
-### 1. 摘要Chunk (chunk_index=0)
-**内容来源**:
-- `PREAMB > SUMMARY` 段落
-- `PREAMB > SUBJECT` 主题
-- `PREAMB > DATES` 日期信息
-
-**格式**:
-```
-**主题**: [SUBJECT内容]
-**摘要**: [SUMMARY内容]
-**日期**: [DATES内容]
-```
-
-### 2. 结构化Chunk生成
-**基于**: `REGTEXT > SECTION > P`
-
-**Section Header构建**:
-```
-42 CFR Part 600 > § 600.125 > Submission of revisions
-```
-
-**Chunk内容优先级**:
-1. `` 段落 (主要内容)
-2. `` 强调字段 (子标题)
-3. `` 标题 (结构信息)
-4. `` 章节编号 (标识信息)
-5. `` 脚注 (补充信息)
-
-### 3. 属性提取策略
-| 标签 | 提取属性 | 用途 |
-|------|----------|------|
-| `HD` | `SOURCE` | 层级判断 |
-| `E` | `T` | 格式化类型 |
-| `SECTION` | `TITLE`, `PART` | 章节信息 |
-| `P` | `ID` | 段落标识 |
-| `FTNT` | `ID` | 脚注标识 |
-
----
-
-## 📈 实施建议
-
-### 1. 配置驱动
-- 使用 `xml_labels_config.py` 管理标签分类
-- 支持自定义标签处理策略
-- 便于维护和扩展
-
-### 2. 模块化设计
-- 分离标签分类、文本提取、chunk生成逻辑
-- 支持不同文档类型的定制化处理
-- 便于单元测试和调试
-
-### 3. 质量保证
-- 保留重要格式化信息 (`**强调**`, `[引用]`)
-- 构建清晰的section_header层级
-- 生成有意义的metadata
-
-### 4. 性能优化
-- 忽略无语义价值的标签
-- 优化文本提取算法
-- 支持增量处理
-
----
-
-## 🔧 技术实现
-
-### 文件结构
-```
-app/
-├── config/
-│ └── xml_labels_config.py # 标签配置
-├── core/
-│ ├── xml_chunker.py # 原始chunker
-│ └── optimized_xml_chunker.py # 优化chunker
-└── scripts/
- ├── analyze_xml_labels.py # 标签分析
- ├── quick_xml_label_check.py # 快速检查
- └── simple_chunker_test.py # 功能测试
-```
-
-### 核心功能
-1. **标签分类**: 自动识别标签类型和处理策略
-2. **文本提取**: 保留语义价值的格式化信息
-3. **结构构建**: 生成清晰的文档层级结构
-4. **Chunk生成**: 基于语义的智能分块
-5. **Metadata提取**: 自动提取文档元信息
-
----
-
-## 📊 预期效果
-
-### 质量提升
-- **语义完整性**: 保留重要内容和结构
-- **可读性**: 清晰的格式化输出
-- **一致性**: 统一的处理标准
-
-### 效率提升
-- **处理速度**: 忽略无价值标签
-- **存储优化**: 减少冗余信息
-- **维护性**: 配置驱动的灵活管理
-
-### 用户体验
-- **搜索质量**: 更好的语义匹配
-- **导航体验**: 清晰的文档结构
-- **内容理解**: 保留关键上下文
-
----
-
-## 🎯 下一步计划
-
-1. **实施优化chunker**: 基于配置的智能处理
-2. **性能测试**: 验证处理效率和输出质量
-3. **用户反馈**: 收集使用体验和改进建议
-4. **持续优化**: 根据实际使用情况调整策略
-
----
-
-*Author: Fanxing Bu*
-*Date: 2024-12-19*
\ No newline at end of file
diff --git a/scripts/fix_filenames_manually.py b/scripts/fix_filenames_manually.py
deleted file mode 100644
index 2d53a6b6..00000000
--- a/scripts/fix_filenames_manually.py
+++ /dev/null
@@ -1,56 +0,0 @@
-#!/usr/bin/env python3
-"""
-Manually fix filenames to keep doc ID unchanged but correct the year.
-"""
-
-import os
-import shutil
-from pathlib import Path
-
-# Mapping of current wrong names to correct names (keeping doc ID)
-CORRECT_NAMES = {
- # MPFS - extracted year: 2025, 2024
- "2025_MPFS_final_2025-2025.xml": "2025_MPFS_final_2025-08676.xml", # was 2026_MPFS_final_2026-08676.xml
- "2024_MPFS_final_2024-2024.xml": "2024_MPFS_final_2024-02705.xml", # was 2025_MPFS_final_2025-02705.xml
- "2025_MPFS_proposed_2025-2025.xml": "2025_MPFS_proposed_2025-14828.xml", # was 2025_MPFS_proposed_2025-14828.xml
- "2024_MPFS_final_2024-2024.xml": "2024_MPFS_final_2024-24184.xml", # was 2024_MPFS_final_2024-24184.xml
- "2025_MPFS_final_2025-2025.xml": "2025_MPFS_final_2025-25382.xml", # was 2025_MPFS_final_2025-25382.xml
- "2024_MPFS_proposed_2024-2024.xml": "2024_MPFS_proposed_2024-14624.xml", # was 2024_MPFS_proposed_2024-14624.xml
-
- # HOSPICE - extracted year: 2024, 2025, 2026
- "2024_HOSPICE_final_2024-2024.xml": "2024_HOSPICE_final_2024-16116.xml", # was 2024_HOSPICE_final_2024-16116.xml
- "2025_HOSPICE_final_2025-2025.xml": "2025_HOSPICE_final_2025-16910.xml", # was 2025_HOSPICE_final_2025-16910.xml
- "2025_HOSPICE_final_2025-2025.xml": "2025_HOSPICE_final_2025-22495.xml", # was 2025_HOSPICE_final_2025-22495.xml
- "2025_HOSPICE_proposed_2025-2025.xml": "2025_HOSPICE_proposed_2025-06921.xml", # was 2025_HOSPICE_proposed_2025-06921.xml
- "2026_HOSPICE_proposed_2026-2026.xml": "2026_HOSPICE_proposed_2026-06317.xml", # was 2026_HOSPICE_proposed_2026-06317.xml
-
- # SNF - extracted year: 2024, 2025, 2026
- "2024_SNF_final_2024-2024.xml": "2024_SNF_final_2024-07522.xml", # was 2025_SNF_final_2025-07522.xml
- "2025_SNF_proposed_2025-2025.xml": "2025_SNF_proposed_2025-06812.xml", # was 2025_SNF_proposed_2025-06812.xml
- "2025_SNF_final_2025-2025.xml": "2025_SNF_final_2025-22504.xml", # was 2025_SNF_final_2025-22504.xml
- "2024_SNF_final_2024-2024.xml": "2024_SNF_final_2024-16249.xml", # was 2024_SNF_final_2024-16249.xml
- "2026_SNF_proposed_2026-2026.xml": "2026_SNF_proposed_2026-06348.xml", # was 2026_SNF_proposed_2026-06348.xml
- "2025_SNF_final_2025-2025.xml": "2025_SNF_final_2025-16907.xml", # was 2025_SNF_final_2025-16907.xml
- "2024_SNF_final_2024-2024.xml": "2024_SNF_final_2024-22050.xml", # was 2024_SNF_final_2024-22050.xml
-}
-
-def fix_filenames():
- """Fix filenames to keep doc ID unchanged but correct the year."""
- project_root = Path(__file__).parent.parent
- data_dir = project_root / "data"
-
- print("Fixing filenames to keep doc ID unchanged...")
-
- for wrong_name, correct_name in CORRECT_NAMES.items():
- category = correct_name.split('_')[1]
- wrong_path = data_dir / category / wrong_name
- correct_path = data_dir / category / correct_name
-
- if wrong_path.exists():
- print(f" Fixing: {wrong_name} -> {correct_name}")
- shutil.move(str(wrong_path), str(correct_path))
- else:
- print(f" Warning: {wrong_name} not found")
-
-if __name__ == "__main__":
- fix_filenames()
\ No newline at end of file
diff --git a/scripts/fix_processed_files.py b/scripts/fix_processed_files.py
deleted file mode 100644
index e6e4d0ca..00000000
--- a/scripts/fix_processed_files.py
+++ /dev/null
@@ -1,122 +0,0 @@
-#!/usr/bin/env python3
-"""
-Fix processed_files.json to match the renamed XML files.
-
-This script updates the file paths in processed_files.json to match
-the corrected year naming convention.
-"""
-
-import os
-import json
-import shutil
-from pathlib import Path
-import re
-
-# Year mapping: old_year -> new_year
-YEAR_MAPPING = {
- "2023": "2024",
- "2024": "2025",
- "2025": "2026"
-}
-
-def get_new_file_path(old_file_path: str) -> str:
- """
- Convert old file path to new file path with corrected year.
-
- Args:
- old_file_path: Original file path (e.g., "MPFS/2023_MPFS_final_2023-24184.xml")
-
- Returns:
- New file path with corrected year (e.g., "MPFS/2024_MPFS_final_2024-24184.xml")
- """
- # Extract components from path: CATEGORY/YYYY_TYPE_TYPE_YYYY-XXXXX.xml
- pattern = r'^([A-Z]+)/(\d{4})_([A-Z]+)_([a-z]+)_\d{4}-\d+\.xml$'
- match = re.match(pattern, old_file_path)
-
- if not match:
- print(f"Warning: Could not parse file path pattern: {old_file_path}")
- return old_file_path
-
- category = match.group(1)
- old_year = match.group(2)
- file_type = match.group(3)
- rule_type = match.group(4)
-
- # Get new year from mapping
- if old_year in YEAR_MAPPING:
- new_year = YEAR_MAPPING[old_year]
- # Create new file path with corrected year
- new_file_path = f"{category}/{new_year}_{file_type}_{rule_type}_{new_year}-{old_file_path.split('-')[1]}"
- return new_file_path
- else:
- print(f"Warning: No mapping found for year {old_year} in {old_file_path}")
- return old_file_path
-
-def main():
- """Main function to fix processed_files.json."""
- print("Fixing processed_files.json...")
- print("Year mapping:")
- for old_year, new_year in YEAR_MAPPING.items():
- print(f" {old_year} -> {new_year}")
-
- # Get project root directory
- project_root = Path(__file__).parent.parent
- processed_files_path = project_root / "rag_data" / "processed_files.json"
-
- if not os.path.exists(processed_files_path):
- print(f"Error: {processed_files_path} not found")
- return
-
- print(f"\nProcessing: {processed_files_path}")
-
- # Load current processed files
- with open(processed_files_path, 'r') as f:
- processed_files = json.load(f)
-
- print(f"Found {len(processed_files)} processed files")
-
- # Create backup
- backup_path = str(processed_files_path) + '.backup2'
- shutil.copy2(processed_files_path, backup_path)
- print(f"Backup created: {backup_path}")
-
- # Update file paths
- updated_files = {}
- changes_made = 0
-
- for old_path, file_info in processed_files.items():
- new_path = get_new_file_path(old_path)
-
- if old_path != new_path:
- print(f" Updating: {old_path} -> {new_path}")
- updated_files[new_path] = file_info
- changes_made += 1
- else:
- updated_files[old_path] = file_info
-
- if changes_made > 0:
- # Write updated file
- with open(processed_files_path, 'w') as f:
- json.dump(updated_files, f, indent=2)
- print(f"\nUpdated {changes_made} file paths")
- else:
- print("\nNo changes needed")
-
- print("\nVerification:")
- # Check if all files exist
- missing_files = []
- for file_path in updated_files.keys():
- full_path = project_root / "data" / file_path
- if not os.path.exists(full_path):
- missing_files.append(file_path)
- print(f" ✗ {file_path} (file not found)")
- else:
- print(f" ✓ {file_path}")
-
- if missing_files:
- print(f"\nWarning: {len(missing_files)} files are missing")
- else:
- print("\n✅ All files found!")
-
-if __name__ == "__main__":
- main()
\ No newline at end of file
diff --git a/scripts/fix_year_from_content.py b/scripts/fix_year_from_content.py
deleted file mode 100644
index 6505ec80..00000000
--- a/scripts/fix_year_from_content.py
+++ /dev/null
@@ -1,323 +0,0 @@
-#!/usr/bin/env python3
-"""
-Fix year naming by extracting the correct year from XML content.
-
-This script:
-1. Extracts the target year from XML content (SUBJECT field) using regex (CY XXXX)
-2. Renames files by replacing the year in filename but keeping doc ID unchanged
-3. Updates processed_files.json, chunks.json, and faiss_metadata.json accordingly
-"""
-
-import os
-import json
-import shutil
-import re
-import xml.etree.ElementTree as ET
-from pathlib import Path
-from typing import Dict, Optional
-
-def extract_year_from_xml(xml_path: str, category: str) -> Optional[str]:
- """
- Extract target year from XML content using category-specific patterns.
-
- Args:
- xml_path: Path to XML file
- category: One of 'MPFS', 'HOSPICE', 'SNF'
- Returns:
- Extracted year (e.g., "2024") or None if not found
- """
- try:
- with open(xml_path, 'r', encoding='utf-8') as f:
- content = f.read()
-
- if category == 'SNF':
- # Federal Fiscal Year XXXX
- match = re.search(r'Federal Fiscal Year\s*(\d{4})', content, re.IGNORECASE)
- if match:
- return match.group(1)
- elif category == 'HOSPICE':
- # FY XXXX
- match = re.search(r'FY\s*(\d{4})', content, re.IGNORECASE)
- if match:
- return match.group(1)
- elif category == 'MPFS':
- # CY XXXX
- match = re.search(r'CY\s*(\d{4})', content, re.IGNORECASE)
- if match:
- return match.group(1)
- return None
- except Exception as e:
- print(f"Error reading {xml_path}: {e}")
- return None
-
-def get_new_filename(old_filename: str, target_year: str) -> str:
- """
- Generate new filename with corrected year but keeping doc ID unchanged.
-
- Args:
- old_filename: Original filename (e.g., "2023_MPFS_final_2023-24184.xml")
- target_year: Target year from XML content (e.g., "2024")
-
- Returns:
- New filename (e.g., "2024_MPFS_final_2024-24184.xml")
- """
- # Parse filename pattern: YYYY_TYPE_TYPE_YYYY-XXXXX.xml
- pattern = r'^(\d{4})_([A-Z]+)_([a-z]+)_(\d{4})-(\d+)\.xml$'
- match = re.match(pattern, old_filename)
-
- if not match:
- print(f"Warning: Could not parse filename pattern: {old_filename}")
- return old_filename
-
- old_year1 = match.group(1) # First year in filename
- file_type = match.group(2)
- rule_type = match.group(3)
- old_year2 = match.group(4) # Second year in filename
- doc_id = match.group(5) # Document ID
-
- # Create new filename with target year but keeping doc ID unchanged
- new_filename = f"{target_year}_{file_type}_{rule_type}_{target_year}-{doc_id}.xml"
- return new_filename
-
-def rename_xml_files_with_content_analysis(data_dir: str) -> Dict[str, str]:
- """
- Rename XML files based on year extracted from content.
-
- Args:
- data_dir: Path to data directory
-
- Returns:
- Dictionary mapping old file paths to new file paths
- """
- file_mapping = {}
-
- for category_dir in ['MPFS', 'HOSPICE', 'SNF']:
- category_path = os.path.join(data_dir, category_dir)
- if not os.path.exists(category_path):
- continue
-
- print(f"\nProcessing {category_dir} directory...")
-
- for filename in os.listdir(category_path):
- if filename.endswith('.xml'):
- old_path = os.path.join(category_path, filename)
-
- # Extract target year from XML content
- target_year = extract_year_from_xml(old_path, category_dir)
-
- if target_year is None:
- print(f" Warning: Could not extract year from {filename} for {category_dir}, skipping")
- continue
-
- # Generate new filename
- new_filename = get_new_filename(filename, target_year)
- new_path = os.path.join(category_path, new_filename)
-
- if old_path != new_path:
- print(f" Renaming: {filename} -> {new_filename} (target year: {target_year})")
- try:
- shutil.move(old_path, new_path)
- file_mapping[old_path] = new_path
- except Exception as e:
- print(f" Error renaming {filename}: {e}")
- else:
- print(f" No change needed: {filename} (already correct year: {target_year})")
-
- return file_mapping
-
-def update_processed_files(processed_files_path: str, file_mapping: Dict[str, str]):
- """
- Update processed_files.json with new file paths.
-
- Args:
- processed_files_path: Path to processed_files.json
- file_mapping: Mapping of old to new file paths
- """
- if not os.path.exists(processed_files_path):
- print(f"Warning: {processed_files_path} not found")
- return
-
- print(f"\nUpdating {processed_files_path}...")
-
- with open(processed_files_path, 'r') as f:
- processed_files = json.load(f)
-
- updated_files = {}
- changes_made = 0
-
- for old_path, file_info in processed_files.items():
- # Convert relative path to absolute for comparison
- old_abs_path = os.path.abspath(old_path)
-
- # Find corresponding new path
- new_path = None
- for old_file_path, new_file_path in file_mapping.items():
- if old_file_path == old_abs_path:
- # Convert back to relative path
- new_path = os.path.relpath(new_file_path, os.path.dirname(processed_files_path))
- break
-
- if new_path:
- print(f" Updating: {old_path} -> {new_path}")
- updated_files[new_path] = file_info
- changes_made += 1
- else:
- updated_files[old_path] = file_info
-
- if changes_made > 0:
- # Backup original file
- backup_path = processed_files_path + '.backup3'
- shutil.copy2(processed_files_path, backup_path)
- print(f" Backup created: {backup_path}")
-
- # Write updated file
- with open(processed_files_path, 'w') as f:
- json.dump(updated_files, f, indent=2)
- print(f" Updated {changes_made} entries")
- else:
- print(" No changes needed")
-
-def update_chunks_file(chunks_path: str, file_mapping: Dict[str, str]):
- """
- Update chunks.json with new file paths.
-
- Args:
- chunks_path: Path to chunks.json
- file_mapping: Mapping of old to new file paths
- """
- if not os.path.exists(chunks_path):
- print(f"Warning: {chunks_path} not found")
- return
-
- print(f"\nUpdating {chunks_path}...")
-
- # This file might be large, so we'll process it line by line
- backup_path = chunks_path + '.backup3'
- shutil.copy2(chunks_path, backup_path)
- print(f" Backup created: {backup_path}")
-
- changes_made = 0
- temp_path = chunks_path + '.tmp'
-
- with open(chunks_path, 'r') as infile, open(temp_path, 'w') as outfile:
- for line in infile:
- # Look for file paths in the JSON line
- for old_file_path, new_file_path in file_mapping.items():
- old_relative_path = os.path.relpath(old_file_path, os.path.dirname(chunks_path))
- new_relative_path = os.path.relpath(new_file_path, os.path.dirname(chunks_path))
-
- if old_relative_path in line:
- line = line.replace(old_relative_path, new_relative_path)
- changes_made += 1
- print(f" Updated chunk reference: {old_relative_path} -> {new_relative_path}")
-
- outfile.write(line)
-
- # Replace original with updated file
- shutil.move(temp_path, chunks_path)
- print(f" Updated {changes_made} chunk references")
-
-def update_faiss_metadata(faiss_metadata_path: str, file_mapping: Dict[str, str]):
- """
- Update faiss_metadata.json with new file paths.
-
- Args:
- faiss_metadata_path: Path to faiss_metadata.json
- file_mapping: Mapping of old to new file paths
- """
- if not os.path.exists(faiss_metadata_path):
- print(f"Warning: {faiss_metadata_path} not found")
- return
-
- print(f"\nUpdating {faiss_metadata_path}...")
-
- # This file might be large, so we'll process it line by line
- backup_path = faiss_metadata_path + '.backup3'
- shutil.copy2(faiss_metadata_path, backup_path)
- print(f" Backup created: {backup_path}")
-
- changes_made = 0
- temp_path = faiss_metadata_path + '.tmp'
-
- with open(faiss_metadata_path, 'r') as infile, open(temp_path, 'w') as outfile:
- for line in infile:
- # Look for file paths in the JSON line
- for old_file_path, new_file_path in file_mapping.items():
- old_relative_path = os.path.relpath(old_file_path, os.path.dirname(faiss_metadata_path))
- new_relative_path = os.path.relpath(new_file_path, os.path.dirname(faiss_metadata_path))
-
- if old_relative_path in line:
- line = line.replace(old_relative_path, new_relative_path)
- changes_made += 1
- print(f" Updated metadata reference: {old_relative_path} -> {new_relative_path}")
-
- outfile.write(line)
-
- # Replace original with updated file
- shutil.move(temp_path, faiss_metadata_path)
- print(f" Updated {changes_made} metadata references")
-
-def main():
- """Main function to execute the year naming fix based on content analysis."""
- print("Starting year naming fix based on XML content analysis...")
-
- # Get project root directory
- project_root = Path(__file__).parent.parent
- data_dir = project_root / "data"
- rag_data_dir = project_root / "rag_data"
-
- print(f"\nProject root: {project_root}")
- print(f"Data directory: {data_dir}")
- print(f"RAG data directory: {rag_data_dir}")
-
- # Step 1: Rename XML files based on content analysis
- print("\n" + "="*60)
- print("STEP 1: Analyzing XML content and renaming files")
- print("="*60)
- file_mapping = rename_xml_files_with_content_analysis(str(data_dir))
-
- if not file_mapping:
- print("No files were renamed. Exiting.")
- return
-
- print(f"\nRenamed {len(file_mapping)} files:")
- for old_path, new_path in file_mapping.items():
- print(f" {os.path.basename(old_path)} -> {os.path.basename(new_path)}")
-
- # Step 2: Update processed files
- print("\n" + "="*60)
- print("STEP 2: Updating processed files")
- print("="*60)
- processed_files_path = rag_data_dir / "processed_files.json"
- update_processed_files(str(processed_files_path), file_mapping)
-
- # Step 3: Update chunks file
- print("\n" + "="*60)
- print("STEP 3: Updating chunks file")
- print("="*60)
- chunks_path = rag_data_dir / "chunks.json"
- update_chunks_file(str(chunks_path), file_mapping)
-
- # Step 4: Update FAISS metadata
- print("\n" + "="*60)
- print("STEP 4: Updating FAISS metadata")
- print("="*60)
- faiss_metadata_path = rag_data_dir / "faiss_metadata.json"
- update_faiss_metadata(str(faiss_metadata_path), file_mapping)
-
- print("\n" + "="*60)
- print("Year naming fix completed!")
- print("="*60)
- print("\nSummary:")
- print(f"- Renamed {len(file_mapping)} XML files based on content analysis")
- print("- Updated processed_files.json")
- print("- Updated chunks.json")
- print("- Updated faiss_metadata.json")
- print("\nBackup files created with .backup3 extension")
- print("\nNext steps:")
- print("1. Verify the changes look correct")
- print("2. Test the search functionality")
- print("3. If everything works, you can remove the .backup files")
-
-if __name__ == "__main__":
- main()
\ No newline at end of file
diff --git a/scripts/fix_year_naming.py b/scripts/fix_year_naming.py
deleted file mode 100644
index 3092fa1d..00000000
--- a/scripts/fix_year_naming.py
+++ /dev/null
@@ -1,298 +0,0 @@
-#!/usr/bin/env python3
-"""
-Fix year naming in XML files and corresponding processed results.
-
-This script corrects the year naming convention where:
-- 2023 files should be 2024 (actual year 2024)
-- 2024 files should be 2025 (actual year 2025)
-- 2025 files should be 2026 (actual year 2026)
-
-The script will:
-1. Rename XML files in data/ directories
-2. Update processed_files.json with new file paths
-3. Update chunks.json with new file paths
-4. Update faiss_metadata.json with new file paths
-"""
-
-import os
-import json
-import shutil
-from pathlib import Path
-from typing import Dict, List, Tuple
-import re
-
-# Year mapping: old_year -> new_year
-YEAR_MAPPING = {
- "2023": "2024",
- "2024": "2025",
- "2025": "2026"
-}
-
-def get_new_filename(old_filename: str) -> str:
- """
- Convert old filename to new filename with corrected year.
-
- Args:
- old_filename: Original filename (e.g., "2023_MPFS_final_2023-14624.xml")
-
- Returns:
- New filename with corrected year (e.g., "2024_MPFS_final_2024-14624.xml")
- """
- # Extract year from filename pattern: YYYY_TYPE_TYPE_YYYY-XXXXX.xml
- pattern = r'^(\d{4})_([A-Z]+)_([a-z]+)_\d{4}-\d+\.xml$'
- match = re.match(pattern, old_filename)
-
- if not match:
- print(f"Warning: Could not parse filename pattern: {old_filename}")
- return old_filename
-
- old_year = match.group(1)
- file_type = match.group(2)
- rule_type = match.group(3)
-
- # Get new year from mapping
- if old_year in YEAR_MAPPING:
- new_year = YEAR_MAPPING[old_year]
- # Create new filename with corrected year
- new_filename = f"{new_year}_{file_type}_{rule_type}_{new_year}-{old_filename.split('-')[1]}"
- return new_filename
- else:
- print(f"Warning: No mapping found for year {old_year} in {old_filename}")
- return old_filename
-
-def rename_xml_files(data_dir: str) -> Dict[str, str]:
- """
- Rename XML files in data directory and return mapping of old to new paths.
-
- Args:
- data_dir: Path to data directory
-
- Returns:
- Dictionary mapping old file paths to new file paths
- """
- file_mapping = {}
-
- for category_dir in ['MPFS', 'HOSPICE', 'SNF']:
- category_path = os.path.join(data_dir, category_dir)
- if not os.path.exists(category_path):
- continue
-
- print(f"\nProcessing {category_dir} directory...")
-
- for filename in os.listdir(category_path):
- if filename.endswith('.xml'):
- old_path = os.path.join(category_path, filename)
- new_filename = get_new_filename(filename)
- new_path = os.path.join(category_path, new_filename)
-
- if old_path != new_path:
- print(f" Renaming: {filename} -> {new_filename}")
- try:
- shutil.move(old_path, new_path)
- file_mapping[old_path] = new_path
- except Exception as e:
- print(f" Error renaming {filename}: {e}")
- else:
- print(f" No change needed: {filename}")
-
- return file_mapping
-
-def update_processed_files(processed_files_path: str, file_mapping: Dict[str, str]):
- """
- Update processed_files.json with new file paths.
-
- Args:
- processed_files_path: Path to processed_files.json
- file_mapping: Mapping of old to new file paths
- """
- if not os.path.exists(processed_files_path):
- print(f"Warning: {processed_files_path} not found")
- return
-
- print(f"\nUpdating {processed_files_path}...")
-
- with open(processed_files_path, 'r') as f:
- processed_files = json.load(f)
-
- updated_files = {}
- changes_made = 0
-
- for old_path, file_info in processed_files.items():
- # Convert relative path to absolute for comparison
- old_abs_path = os.path.abspath(old_path)
-
- # Find corresponding new path
- new_path = None
- for old_file_path, new_file_path in file_mapping.items():
- if old_file_path == old_abs_path:
- # Convert back to relative path
- new_path = os.path.relpath(new_file_path, os.path.dirname(processed_files_path))
- break
-
- if new_path:
- print(f" Updating: {old_path} -> {new_path}")
- updated_files[new_path] = file_info
- changes_made += 1
- else:
- updated_files[old_path] = file_info
-
- if changes_made > 0:
- # Backup original file
- backup_path = processed_files_path + '.backup'
- shutil.copy2(processed_files_path, backup_path)
- print(f" Backup created: {backup_path}")
-
- # Write updated file
- with open(processed_files_path, 'w') as f:
- json.dump(updated_files, f, indent=2)
- print(f" Updated {changes_made} entries")
- else:
- print(" No changes needed")
-
-def update_chunks_file(chunks_path: str, file_mapping: Dict[str, str]):
- """
- Update chunks.json with new file paths.
-
- Args:
- chunks_path: Path to chunks.json
- file_mapping: Mapping of old to new file paths
- """
- if not os.path.exists(chunks_path):
- print(f"Warning: {chunks_path} not found")
- return
-
- print(f"\nUpdating {chunks_path}...")
-
- # This file might be large, so we'll process it line by line
- backup_path = chunks_path + '.backup'
- shutil.copy2(chunks_path, backup_path)
- print(f" Backup created: {backup_path}")
-
- changes_made = 0
- temp_path = chunks_path + '.tmp'
-
- with open(chunks_path, 'r') as infile, open(temp_path, 'w') as outfile:
- for line in infile:
- # Look for file paths in the JSON line
- for old_file_path, new_file_path in file_mapping.items():
- old_relative_path = os.path.relpath(old_file_path, os.path.dirname(chunks_path))
- new_relative_path = os.path.relpath(new_file_path, os.path.dirname(chunks_path))
-
- if old_relative_path in line:
- line = line.replace(old_relative_path, new_relative_path)
- changes_made += 1
- print(f" Updated chunk reference: {old_relative_path} -> {new_relative_path}")
-
- outfile.write(line)
-
- # Replace original with updated file
- shutil.move(temp_path, chunks_path)
- print(f" Updated {changes_made} chunk references")
-
-def update_faiss_metadata(faiss_metadata_path: str, file_mapping: Dict[str, str]):
- """
- Update faiss_metadata.json with new file paths.
-
- Args:
- faiss_metadata_path: Path to faiss_metadata.json
- file_mapping: Mapping of old to new file paths
- """
- if not os.path.exists(faiss_metadata_path):
- print(f"Warning: {faiss_metadata_path} not found")
- return
-
- print(f"\nUpdating {faiss_metadata_path}...")
-
- # This file might be large, so we'll process it line by line
- backup_path = faiss_metadata_path + '.backup'
- shutil.copy2(faiss_metadata_path, backup_path)
- print(f" Backup created: {backup_path}")
-
- changes_made = 0
- temp_path = faiss_metadata_path + '.tmp'
-
- with open(faiss_metadata_path, 'r') as infile, open(temp_path, 'w') as outfile:
- for line in infile:
- # Look for file paths in the JSON line
- for old_file_path, new_file_path in file_mapping.items():
- old_relative_path = os.path.relpath(old_file_path, os.path.dirname(faiss_metadata_path))
- new_relative_path = os.path.relpath(new_file_path, os.path.dirname(faiss_metadata_path))
-
- if old_relative_path in line:
- line = line.replace(old_relative_path, new_relative_path)
- changes_made += 1
- print(f" Updated metadata reference: {old_relative_path} -> {new_relative_path}")
-
- outfile.write(line)
-
- # Replace original with updated file
- shutil.move(temp_path, faiss_metadata_path)
- print(f" Updated {changes_made} metadata references")
-
-def main():
- """Main function to execute the year naming fix."""
- print("Starting year naming fix...")
- print("Year mapping:")
- for old_year, new_year in YEAR_MAPPING.items():
- print(f" {old_year} -> {new_year}")
-
- # Get project root directory
- project_root = Path(__file__).parent.parent
- data_dir = project_root / "data"
- rag_data_dir = project_root / "rag_data"
-
- print(f"\nProject root: {project_root}")
- print(f"Data directory: {data_dir}")
- print(f"RAG data directory: {rag_data_dir}")
-
- # Step 1: Rename XML files
- print("\n" + "="*50)
- print("STEP 1: Renaming XML files")
- print("="*50)
- file_mapping = rename_xml_files(str(data_dir))
-
- if not file_mapping:
- print("No files were renamed. Exiting.")
- return
-
- print(f"\nRenamed {len(file_mapping)} files:")
- for old_path, new_path in file_mapping.items():
- print(f" {os.path.basename(old_path)} -> {os.path.basename(new_path)}")
-
- # Step 2: Update processed files
- print("\n" + "="*50)
- print("STEP 2: Updating processed files")
- print("="*50)
- processed_files_path = rag_data_dir / "processed_files.json"
- update_processed_files(str(processed_files_path), file_mapping)
-
- # Step 3: Update chunks file
- print("\n" + "="*50)
- print("STEP 3: Updating chunks file")
- print("="*50)
- chunks_path = rag_data_dir / "chunks.json"
- update_chunks_file(str(chunks_path), file_mapping)
-
- # Step 4: Update FAISS metadata
- print("\n" + "="*50)
- print("STEP 4: Updating FAISS metadata")
- print("="*50)
- faiss_metadata_path = rag_data_dir / "faiss_metadata.json"
- update_faiss_metadata(str(faiss_metadata_path), file_mapping)
-
- print("\n" + "="*50)
- print("Year naming fix completed!")
- print("="*50)
- print("\nSummary:")
- print(f"- Renamed {len(file_mapping)} XML files")
- print("- Updated processed_files.json")
- print("- Updated chunks.json")
- print("- Updated faiss_metadata.json")
- print("\nBackup files created with .backup extension")
- print("\nNext steps:")
- print("1. Verify the changes look correct")
- print("2. Test the search functionality")
- print("3. If everything works, you can remove the .backup files")
-
-if __name__ == "__main__":
- main()
\ No newline at end of file
diff --git a/scripts/quick_xml_label_check.py b/scripts/quick_xml_label_check.py
deleted file mode 100644
index 1c6649c5..00000000
--- a/scripts/quick_xml_label_check.py
+++ /dev/null
@@ -1,103 +0,0 @@
-#!/usr/bin/env python3
-"""
-quick_xml_label_check.py
-
-Quick script to check XML label consistency across data folders.
-Provides a concise summary of whether different types use the same XML labels.
-
-Author: Fanxing Bu
-Date: 2024-12-19
-"""
-
-import xml.etree.ElementTree as ET
-from pathlib import Path
-from typing import Dict, Set
-import sys
-
-def extract_labels_from_file(xml_path: Path) -> Set[str]:
- """Extract all XML labels from a single file."""
- try:
- tree = ET.parse(xml_path)
- root = tree.getroot()
- return {elem.tag for elem in root.iter()}
- except Exception as e:
- print(f"Error processing {xml_path}: {e}")
- return set()
-
-def analyze_folder_labels(folder_path: Path) -> Set[str]:
- """Analyze all XML files in a folder and return all unique labels."""
- if not folder_path.exists():
- print(f"Folder {folder_path} does not exist")
- return set()
-
- xml_files = list(folder_path.glob("*.xml"))
- if not xml_files:
- print(f"No XML files found in {folder_path}")
- return set()
-
- all_labels = set()
- for xml_file in xml_files:
- labels = extract_labels_from_file(xml_file)
- all_labels.update(labels)
-
- return all_labels
-
-def main():
- """Main function for quick XML label check."""
- data_dir = Path("data")
- folder_types = ["HOSPICE", "MPFS", "SNF"]
-
- print("=" * 60)
- print("QUICK XML LABEL CONSISTENCY CHECK")
- print("=" * 60)
-
- # Analyze each folder
- folder_labels = {}
- for folder_type in folder_types:
- folder_path = data_dir / folder_type
- print(f"\nAnalyzing {folder_type}...")
-
- labels = analyze_folder_labels(folder_path)
- folder_labels[folder_type] = labels
- print(f" Found {len(labels)} unique XML labels")
-
- # Compare labels
- print("\n" + "=" * 60)
- print("COMPARISON RESULTS:")
- print("=" * 60)
-
- if not folder_labels:
- print("No data to compare")
- return
-
- # Find common labels across all folders
- common_labels = set.intersection(*folder_labels.values())
- print(f"\nCommon labels across all folders: {len(common_labels)}")
-
- # Check if all folders have the same label set
- all_labels_union = set.union(*folder_labels.values())
- is_consistent = len(common_labels) == len(all_labels_union)
-
- print(f"All folders use same label set: {'✅ YES' if is_consistent else '❌ NO'}")
-
- if not is_consistent:
- print("\nDifferences found:")
- for folder_type, labels in folder_labels.items():
- other_labels = set.union(*[l for f, l in folder_labels.items() if f != folder_type])
- unique_labels = labels - other_labels
- if unique_labels:
- print(f" {folder_type} unique labels: {sorted(unique_labels)}")
-
- # Show label counts
- print(f"\nLabel counts per folder:")
- for folder_type, labels in folder_labels.items():
- print(f" {folder_type}: {len(labels)} labels")
-
- # Show common labels
- if common_labels:
- print(f"\nCommon labels ({len(common_labels)}):")
- for label in sorted(common_labels):
- print(f" - {label}")
-
-if __name__ == "__main__":
- main()
\ No newline at end of file
diff --git a/scripts/restore_and_fix.py b/scripts/restore_and_fix.py
deleted file mode 100644
index eb563798..00000000
--- a/scripts/restore_and_fix.py
+++ /dev/null
@@ -1,56 +0,0 @@
-#!/usr/bin/env python3
-"""
-Restore original filenames and then rename with correct logic.
-"""
-
-import os
-import shutil
-from pathlib import Path
-
-# Original filename mapping (before the wrong rename)
-ORIGINAL_NAMES = {
- # MPFS
- "2025_MPFS_final_2025-2025.xml": "2026_MPFS_final_2026-08676.xml",
- "2024_MPFS_final_2024-2024.xml": "2025_MPFS_final_2025-02705.xml",
- "2025_MPFS_proposed_2025-2025.xml": "2025_MPFS_proposed_2025-14828.xml",
- "2024_MPFS_final_2024-2024.xml": "2024_MPFS_final_2024-24184.xml",
- "2025_MPFS_final_2025-2025.xml": "2025_MPFS_final_2025-25382.xml",
- "2024_MPFS_proposed_2024-2024.xml": "2024_MPFS_proposed_2024-14624.xml",
-
- # HOSPICE
- "2024_HOSPICE_final_2024-2024.xml": "2024_HOSPICE_final_2024-16116.xml",
- "2025_HOSPICE_final_2025-2025.xml": "2025_HOSPICE_final_2025-16910.xml",
- "2025_HOSPICE_final_2025-2025.xml": "2025_HOSPICE_final_2025-22495.xml",
- "2025_HOSPICE_proposed_2025-2025.xml": "2025_HOSPICE_proposed_2025-06921.xml",
- "2026_HOSPICE_proposed_2026-2026.xml": "2026_HOSPICE_proposed_2026-06317.xml",
-
- # SNF
- "2024_SNF_final_2024-2024.xml": "2025_SNF_final_2025-07522.xml",
- "2025_SNF_proposed_2025-2025.xml": "2025_SNF_proposed_2025-06812.xml",
- "2025_SNF_final_2025-2025.xml": "2025_SNF_final_2025-22504.xml",
- "2024_SNF_final_2024-2024.xml": "2024_SNF_final_2024-16249.xml",
- "2026_SNF_proposed_2026-2026.xml": "2026_SNF_proposed_2026-06348.xml",
- "2025_SNF_final_2025-2025.xml": "2025_SNF_final_2025-16907.xml",
- "2024_SNF_final_2024-2024.xml": "2024_SNF_final_2024-22050.xml",
-}
-
-def restore_original_names():
- """Restore original filenames."""
- project_root = Path(__file__).parent.parent
- data_dir = project_root / "data"
-
- print("Restoring original filenames...")
-
- for wrong_name, original_name in ORIGINAL_NAMES.items():
- category = original_name.split('_')[1]
- wrong_path = data_dir / category / wrong_name
- original_path = data_dir / category / original_name
-
- if wrong_path.exists():
- print(f" Restoring: {wrong_name} -> {original_name}")
- shutil.move(str(wrong_path), str(original_path))
- else:
- print(f" Warning: {wrong_name} not found")
-
-if __name__ == "__main__":
- restore_original_names()
\ No newline at end of file
diff --git a/scripts/test_xml_auto_headings_analysis.py b/scripts/test_xml_auto_headings_analysis.py
deleted file mode 100644
index 65635c45..00000000
--- a/scripts/test_xml_auto_headings_analysis.py
+++ /dev/null
@@ -1,38 +0,0 @@
-import pytest
-from lxml import etree
-from xml_auto_headings_analysis import get_text, is_heading, count_tokens, build_section_tree
-
-def test_get_text():
- elem = etree.Element('p')
- elem.text = 'This is a test of the get_text function.'
- assert get_text(elem, 3) == 'This is a ...'
- assert get_text(elem, 10) == 'This is a test of the get_text function.'
- assert get_text(None) == ''
- elem2 = etree.Element('p')
- assert get_text(elem2) == ''
-
-def test_is_heading():
- assert is_heading('CHAPTER I')
- assert is_heading('1. Introduction')
- assert is_heading('Section 2.1')
- assert not is_heading('This is a normal sentence.')
- assert is_heading('Short Title')
- assert not is_heading('')
-
-def test_count_tokens():
- text = 'This is a test.'
- n = count_tokens(text)
- assert n > 0
- assert isinstance(n, int)
- assert count_tokens('') == 0
-
-def test_build_section_tree():
- xml = '''TitlePrefaceAmendmentAnother'''
- root = etree.fromstring(xml)
- tree = build_section_tree(root)
- assert isinstance(tree, list)
- assert len(tree) == 4 # 2 FP, 1 HD, 1 AMDPAR
- tags = [n['tag'] for n in tree]
- assert set(tags) == {'HD', 'FP', 'AMDPAR', 'FP'}
- for node in tree:
- assert 'tag' in node and 'text' in node and 'children' in node
\ No newline at end of file
diff --git a/scripts/test_xml_label_analyzer.py b/scripts/test_xml_label_analyzer.py
deleted file mode 100644
index e116c6f1..00000000
--- a/scripts/test_xml_label_analyzer.py
+++ /dev/null
@@ -1,194 +0,0 @@
-#!/usr/bin/env python3
-"""
-test_xml_label_analyzer.py
-
-Unit tests for the XML label analyzer functionality.
-
-Author: Fanxing Bu
-Date: 2024-12-19
-"""
-
-import unittest
-import tempfile
-import os
-from pathlib import Path
-import xml.etree.ElementTree as ET
-from analyze_xml_labels import XMLLabelAnalyzer
-
-class TestXMLLabelAnalyzer(unittest.TestCase):
- """Test cases for XMLLabelAnalyzer class."""
-
- def setUp(self):
- """Set up test fixtures."""
- self.temp_dir = tempfile.mkdtemp()
- self.test_data_dir = Path(self.temp_dir) / "test_data"
- self.test_data_dir.mkdir()
-
- # Create test XML files
- self.create_test_xml_files()
-
- def tearDown(self):
- """Clean up test fixtures."""
- import shutil
- shutil.rmtree(self.temp_dir)
-
- def create_test_xml_files(self):
- """Create test XML files with known structures."""
- # Create HOSPICE folder
- hospice_dir = self.test_data_dir / "HOSPICE"
- hospice_dir.mkdir()
-
- # Test file 1 - basic structure
- hospice_xml1 = """
-
-
- Test Agency
- Test Subject
- Test paragraph
-
-
- Test Header
- More content
-
-"""
-
- with open(hospice_dir / "test_hospice1.xml", "w") as f:
- f.write(hospice_xml1)
-
- # Test file 2 - different structure
- hospice_xml2 = """
-
-
- Test Agency
- Test Subject
- Footnote content
-
-
- Test Header
-
-
-"""
-
- with open(hospice_dir / "test_hospice2.xml", "w") as f:
- f.write(hospice_xml2)
-
- # Create MPFS folder with different labels
- mpfs_dir = self.test_data_dir / "MPFS"
- mpfs_dir.mkdir()
-
- mpfs_xml = """
-
-
- Test Agency
- Test Subject
- Test paragraph
- Test Appendix
-
-
- Test Header
- Table of Contents
-
-"""
-
- with open(mpfs_dir / "test_mpfs.xml", "w") as f:
- f.write(mpfs_xml)
-
- # Create SNF folder
- snf_dir = self.test_data_dir / "SNF"
- snf_dir.mkdir()
-
- snf_xml = """
-
-
- Test Agency
- Test Subject
- Test paragraph
-
-
- Test Header
- More content
-
-"""
-
- with open(snf_dir / "test_snf.xml", "w") as f:
- f.write(snf_xml)
-
- def test_extract_xml_labels(self):
- """Test extracting XML labels from a single file."""
- analyzer = XMLLabelAnalyzer(str(self.test_data_dir))
- test_file = self.test_data_dir / "HOSPICE" / "test_hospice1.xml"
-
- labels = analyzer.extract_xml_labels(test_file)
- expected_labels = {"RULE", "PREAMB", "AGENCY", "SUBJECT", "P", "SUPLINF", "HD"}
-
- self.assertEqual(labels, expected_labels)
-
- def test_analyze_folder(self):
- """Test analyzing a folder of XML files."""
- analyzer = XMLLabelAnalyzer(str(self.test_data_dir))
- result = analyzer.analyze_folder("HOSPICE")
-
- self.assertEqual(result["folder_name"], "HOSPICE")
- self.assertEqual(result["file_count"], 2)
- self.assertIn("test_hospice1.xml", result["file_labels"])
- self.assertIn("test_hospice2.xml", result["file_labels"])
-
- # Check that all expected labels are found
- all_labels = result["all_labels"]
- expected_labels = {"RULE", "PREAMB", "AGENCY", "SUBJECT", "P", "SUPLINF", "HD", "FTNT", "SECTION"}
- self.assertEqual(all_labels, expected_labels)
-
- def test_compare_folders(self):
- """Test comparing labels across folders."""
- analyzer = XMLLabelAnalyzer(str(self.test_data_dir))
- results = analyzer.compare_folders()
-
- # Check that all folders were analyzed
- self.assertIn("HOSPICE", results["folder_results"])
- self.assertIn("MPFS", results["folder_results"])
- self.assertIn("SNF", results["folder_results"])
-
- # Check that MPFS has unique labels
- unique_per_folder = results["unique_per_folder"]
- self.assertIn("MPFS", unique_per_folder)
- mpfs_unique = unique_per_folder["MPFS"]
- self.assertIn("APPENDIX", mpfs_unique)
- self.assertIn("CONTENTS", mpfs_unique)
-
- # Check consistency
- consistency = results["consistency_check"]
- self.assertFalse(consistency["label_sets_are_equal"]) # Should be different due to MPFS unique labels
-
- def test_common_labels(self):
- """Test finding common labels across folders."""
- analyzer = XMLLabelAnalyzer(str(self.test_data_dir))
- results = analyzer.compare_folders()
-
- common_labels = results["common_across_folders"]
- expected_common = {"RULE", "PREAMB", "AGENCY", "SUBJECT", "P", "SUPLINF", "HD"}
-
- self.assertEqual(common_labels, expected_common)
-
- def test_empty_folder(self):
- """Test handling of empty folder."""
- analyzer = XMLLabelAnalyzer(str(self.test_data_dir))
-
- # Create empty folder
- empty_dir = self.test_data_dir / "EMPTY"
- empty_dir.mkdir()
-
- result = analyzer.analyze_folder("EMPTY")
- self.assertEqual(result, {}) # Should return empty dict for empty folder
-
- def test_nonexistent_folder(self):
- """Test handling of nonexistent folder."""
- analyzer = XMLLabelAnalyzer(str(self.test_data_dir))
- result = analyzer.analyze_folder("NONEXISTENT")
- self.assertEqual(result, {}) # Should return empty dict for nonexistent folder
-
-def run_tests():
- """Run all tests."""
- unittest.main(verbosity=2)
-
-if __name__ == "__main__":
- run_tests()
\ No newline at end of file
diff --git a/scripts/test_year_extraction.py b/scripts/test_year_extraction.py
deleted file mode 100644
index 3d615f3d..00000000
--- a/scripts/test_year_extraction.py
+++ /dev/null
@@ -1,60 +0,0 @@
-#!/usr/bin/env python3
-"""
-Test script to verify year extraction from XML content.
-"""
-
-import os
-import re
-from pathlib import Path
-
-def extract_year_from_xml(xml_path: str, category: str):
- """Extract target year from XML content using category-specific patterns."""
- try:
- with open(xml_path, 'r', encoding='utf-8') as f:
- content = f.read()
- if category == 'SNF':
- match = re.search(r'Federal Fiscal Year\s*(\d{4})', content, re.IGNORECASE)
- if match:
- return match.group(1), 'Federal Fiscal Year'
- elif category == 'HOSPICE':
- match = re.search(r'FY\s*(\d{4})', content, re.IGNORECASE)
- if match:
- return match.group(1), 'FY'
- elif category == 'MPFS':
- match = re.search(r'CY\s*(\d{4})', content, re.IGNORECASE)
- if match:
- return match.group(1), 'CY'
- return None, None
- except Exception as e:
- print(f"Error reading {xml_path}: {e}")
- return None, None
-
-def main():
- """Test year extraction on all XML files by category."""
- print("Testing year extraction from XML content by category...")
-
- # Get project root directory
- project_root = Path(__file__).parent.parent
- data_dir = project_root / "data"
-
- print(f"Data directory: {data_dir}")
-
- for category_dir in ['MPFS', 'HOSPICE', 'SNF']:
- category_path = data_dir / category_dir
- if not category_path.exists():
- continue
-
- print(f"\n{category_dir}:")
-
- for filename in os.listdir(category_path):
- if filename.endswith('.xml'):
- xml_path = category_path / filename
- extracted_year, source = extract_year_from_xml(str(xml_path), category_dir)
-
- if extracted_year:
- print(f" ✓ {filename} -> {source} {extracted_year}")
- else:
- print(f" ✗ {filename} -> No year found")
-
-if __name__ == "__main__":
- main()
\ No newline at end of file
diff --git a/scripts/test_year_fix.py b/scripts/test_year_fix.py
deleted file mode 100644
index 6ee3ee9b..00000000
--- a/scripts/test_year_fix.py
+++ /dev/null
@@ -1,208 +0,0 @@
-#!/usr/bin/env python3
-"""
-Test script to verify the year naming fix.
-
-This script checks:
-1. XML file naming consistency
-2. Processed files mapping
-3. Chunks file references
-4. FAISS metadata references
-"""
-
-import os
-import json
-from pathlib import Path
-import re
-
-def check_xml_files(data_dir: str):
- """Check XML file naming consistency."""
- print("Checking XML file naming...")
-
- issues = []
- for category_dir in ['MPFS', 'HOSPICE', 'SNF']:
- category_path = os.path.join(data_dir, category_dir)
- if not os.path.exists(category_path):
- continue
-
- print(f"\n {category_dir}:")
- for filename in os.listdir(category_path):
- if filename.endswith('.xml'):
- # Check if filename follows expected pattern
- pattern = r'^(\d{4})_([A-Z]+)_([a-z]+)_\d{4}-\d+\.xml$'
- match = re.match(pattern, filename)
-
- if match:
- year = match.group(1)
- file_type = match.group(2)
- rule_type = match.group(3)
-
- # Check if year is in expected range (2024-2026)
- if year in ['2024', '2025', '2026']:
- print(f" ✓ {filename}")
- else:
- print(f" ✗ {filename} (unexpected year: {year})")
- issues.append(f"Unexpected year {year} in {filename}")
- else:
- print(f" ✗ {filename} (doesn't match pattern)")
- issues.append(f"Pattern mismatch in {filename}")
-
- return issues
-
-def check_processed_files(processed_files_path: str):
- """Check processed_files.json consistency."""
- print("\nChecking processed_files.json...")
-
- if not os.path.exists(processed_files_path):
- print(" ✗ File not found")
- return ["processed_files.json not found"]
-
- issues = []
- with open(processed_files_path, 'r') as f:
- processed_files = json.load(f)
-
- print(f" Found {len(processed_files)} processed files")
-
- # Get project root directory for file existence check
- project_root = Path(processed_files_path).parent.parent
-
- for file_path, file_info in processed_files.items():
- # Check if file exists in data directory
- full_path = project_root / "data" / file_path
- if not os.path.exists(full_path):
- print(f" ✗ {file_path} (file not found)")
- issues.append(f"File not found: {file_path}")
- else:
- print(f" ✓ {file_path}")
-
- # Check if path follows expected pattern
- if not file_path.endswith('.xml'):
- print(f" ✗ {file_path} (not an XML file)")
- issues.append(f"Not an XML file: {file_path}")
-
- return issues
-
-def check_chunks_file(chunks_path: str):
- """Check chunks.json file references."""
- print("\nChecking chunks.json...")
-
- if not os.path.exists(chunks_path):
- print(" ✗ File not found")
- return ["chunks.json not found"]
-
- issues = []
- chunk_count = 0
- file_references = set()
-
- with open(chunks_path, 'r') as f:
- for line in f:
- chunk_count += 1
- # Look for file paths in the line
- if '"file_path"' in line:
- # Extract file path from JSON
- match = re.search(r'"file_path":\s*"([^"]+)"', line)
- if match:
- file_path = match.group(1)
- file_references.add(file_path)
-
- print(f" Found {chunk_count} chunks")
- print(f" Found {len(file_references)} unique file references")
-
- for file_path in sorted(file_references):
- if not os.path.exists(file_path):
- print(f" ✗ {file_path} (file not found)")
- issues.append(f"File not found in chunks: {file_path}")
- else:
- print(f" ✓ {file_path}")
-
- return issues
-
-def check_faiss_metadata(faiss_metadata_path: str):
- """Check faiss_metadata.json file references."""
- print("\nChecking faiss_metadata.json...")
-
- if not os.path.exists(faiss_metadata_path):
- print(" ✗ File not found")
- return ["faiss_metadata.json not found"]
-
- issues = []
- metadata_count = 0
- file_references = set()
-
- with open(faiss_metadata_path, 'r') as f:
- for line in f:
- metadata_count += 1
- # Look for file paths in the line
- if '"file_path"' in line:
- # Extract file path from JSON
- match = re.search(r'"file_path":\s*"([^"]+)"', line)
- if match:
- file_path = match.group(1)
- file_references.add(file_path)
-
- print(f" Found {metadata_count} metadata entries")
- print(f" Found {len(file_references)} unique file references")
-
- for file_path in sorted(file_references):
- if not os.path.exists(file_path):
- print(f" ✗ {file_path} (file not found)")
- issues.append(f"File not found in metadata: {file_path}")
- else:
- print(f" ✓ {file_path}")
-
- return issues
-
-def main():
- """Main test function."""
- print("="*60)
- print("YEAR NAMING FIX VERIFICATION")
- print("="*60)
-
- # Get project root directory
- project_root = Path(__file__).parent.parent
- data_dir = project_root / "data"
- rag_data_dir = project_root / "rag_data"
-
- print(f"Project root: {project_root}")
- print(f"Data directory: {data_dir}")
- print(f"RAG data directory: {rag_data_dir}")
-
- all_issues = []
-
- # Check XML files
- xml_issues = check_xml_files(str(data_dir))
- all_issues.extend(xml_issues)
-
- # Check processed files
- processed_files_path = rag_data_dir / "processed_files.json"
- processed_issues = check_processed_files(str(processed_files_path))
- all_issues.extend(processed_issues)
-
- # Check chunks file
- chunks_path = rag_data_dir / "chunks.json"
- chunks_issues = check_chunks_file(str(chunks_path))
- all_issues.extend(chunks_issues)
-
- # Check FAISS metadata
- faiss_metadata_path = rag_data_dir / "faiss_metadata.json"
- metadata_issues = check_faiss_metadata(str(faiss_metadata_path))
- all_issues.extend(metadata_issues)
-
- # Summary
- print("\n" + "="*60)
- print("VERIFICATION SUMMARY")
- print("="*60)
-
- if all_issues:
- print(f"❌ Found {len(all_issues)} issues:")
- for issue in all_issues:
- print(f" - {issue}")
- print("\nPlease fix these issues before proceeding.")
- else:
- print("✅ All checks passed! The year naming fix appears to be successful.")
- print("\nNext steps:")
- print("1. Test the search functionality")
- print("2. Verify search results are correct")
- print("3. If everything works, you can remove the .backup files")
-
-if __name__ == "__main__":
- main()
\ No newline at end of file
diff --git a/scripts/xml_auto_headings_analysis.py b/scripts/xml_auto_headings_analysis.py
deleted file mode 100644
index 533e0884..00000000
--- a/scripts/xml_auto_headings_analysis.py
+++ /dev/null
@@ -1,234 +0,0 @@
-"""
-xml_auto_headings_analysis.py
-
-Script to analyze XML tags, print tag frequencies, sample contents, auto-infer likely title, heading, and paragraph tags, count total words/tokens, and build a section tree for structural tags (e.g., HD, FP, AMDPAR).
-
-Usage:
- python scripts/xml_auto_headings_analysis.py data/2025-06008.xml
-
-Data format for section tree:
- {
- 'tag': str, # e.g., 'HD'
- 'text': str, # text content
- 'children': [ ... ] # list of child nodes (same structure)
- }
-"""
-import sys
-from lxml import etree
-from collections import Counter, defaultdict
-import re
-
-try:
- import tiktoken
-except ImportError:
- tiktoken = None
- print("Warning: tiktoken not installed, token count will be word count.")
-
-SECTION_TAGS = {'HD', 'FP', 'AMDPAR'} # 可根据实际需要扩展
-
-# Tag priority levels for section tree construction
-TAG_LEVELS = {
- 'RULE': 0,
- 'PREAMB': 1, 'SUPLINF': 1, 'REGTEXT': 1,
- 'HD': 2,
- 'FP': 3, 'AMDPAR': 3, 'SECTION': 3, 'SUBPART': 3,
- 'P': 4, 'E': 4, 'FTNT': 4, 'NOTE': 4,
- 'PRTPAGE': 5, 'GID': 5, 'BILCOD': 5, 'AUTH': 5
-}
-
-# Helper to get tag level, default to 99 for unknown tags
-get_tag_level = lambda tag: TAG_LEVELS.get(tag, 99)
-
-def get_text(elem, max_words=8):
- """
- Get a short preview of element text.
- """
- if elem is None or elem.text is None:
- return ''
- words = elem.text.strip().split()
- if len(words) > max_words:
- return ' '.join(words[:max_words]) + ' ...'
- return ' '.join(words)
-
-def is_heading(text):
- """
- Heuristic to determine if text looks like a heading.
- """
- if not text:
- return False
- if text.isupper() and len(text.split()) <= 10:
- return True
- if re.match(r'^[A-Z]?[0-9]+[\.-]', text):
- return True
- if len(text.split()) <= 8 and text.istitle():
- return True
- return False
-
-def count_tokens(text, encoding_name='cl100k_base'):
- """
- Count tokens using tiktoken if available, else fallback to word count.
- >>> count_tokens('This is a test.') > 0
- True
- """
- if not text:
- return 0
- if tiktoken:
- enc = tiktoken.get_encoding(encoding_name)
- return len(enc.encode(text))
- else:
- return len(text.strip().split())
-
-def build_section_tree(elem, parent_level=-1):
- """
- Build a nested section tree according to tag priority levels.
- Returns a list of section nodes.
- Each node: {'tag': str, 'text': str, 'children': list}
- """
- nodes = []
- for child in elem:
- tag = child.tag
- level = get_tag_level(tag)
- node = {
- 'tag': tag,
- 'text': get_text(child, 20),
- 'children': build_section_tree(child, level)
- }
- nodes.append(node)
- return nodes
-
-def print_section_tree(nodes, level=0, max_words=5, max_per_tag=3):
- """
- Pretty print the section tree, showing structure and a short content preview.
- At each level, group by tag, show up to max_per_tag examples per tag, recursively.
- Each node shows only the first max_words words of its text, with ellipsis if longer.
- """
- tag_groups = defaultdict(list)
- for node in nodes:
- tag_groups[node['tag']].append(node)
- for tag, group in tag_groups.items():
- for i, node in enumerate(group):
- if i >= max_per_tag:
- print(' ' * level + f"... ({len(group) - max_per_tag} more {tag})")
- break
- words = node['text'].split()
- text = ' '.join(words[:max_words])
- if len(words) > max_words:
- text += ' ...'
- print(' ' * level + f"- {node['tag']}: {text}")
- print_section_tree(node['children'], level+1, max_words, max_per_tag)
-
-def main(xml_path):
- """
- Main analysis function.
- Args:
- xml_path (str): Path to XML file.
- """
- try:
- tree = etree.parse(xml_path)
- except Exception as e:
- print(f"Error parsing XML: {e}")
- sys.exit(1)
- root = tree.getroot()
-
- tag_counter = Counter()
- tag_samples = defaultdict(list)
- tag_lengths = defaultdict(list)
- total_words = 0
- total_tokens = 0
-
- # Traverse all elements, collect stats and count words/tokens
- for elem in root.iter():
- tag_counter[elem.tag] += 1
- if elem.text and elem.text.strip():
- sample = get_text(elem, 12)
- if len(tag_samples[elem.tag]) < 3:
- tag_samples[elem.tag].append(sample)
- word_count = len(elem.text.strip().split())
- tag_lengths[elem.tag].append(word_count)
- total_words += word_count
- total_tokens += count_tokens(elem.text)
-
- print('=== Tag Frequency & Sample Content ===')
- for tag, count in tag_counter.most_common():
- print(f'- {tag}: {count}')
- for i, sample in enumerate(tag_samples[tag]):
- print(f' Sample {i+1}: {sample}')
- print()
-
- # Print likely heuristics explanation
- print('=== Heuristics for "Likely" Tags ===')
- print('- Likely title: tag that appears only once and has no more than 15 words.')
- print('- Likely heading: tag that appears 5-200 times, with short text that looks like a heading (all caps, numbered, or title case).')
- print('- Likely paragraph: tag that appears more than 100 times and has at least one sample with more than 10 words.')
- print()
-
- # Heuristic: likely title tag is the first tag with only one occurrence and short text
- likely_title = None
- for tag, count in tag_counter.items():
- if count == 1 and tag_samples[tag]:
- text = tag_samples[tag][0]
- if len(text.split()) <= 15:
- likely_title = (tag, text)
- break
- print('=== Likely Article Title ===')
- if likely_title:
- print(f'Tag: {likely_title[0]} | Content: {likely_title[1]}')
- else:
- print('Not found')
- print()
-
- # Heuristic: likely heading tags are those with moderate frequency, short text, and heading-like features
- heading_candidates = []
- for tag, samples in tag_samples.items():
- if 5 <= tag_counter[tag] <= 200:
- for s in samples:
- if is_heading(s):
- heading_candidates.append((tag, s))
- break
- heading_tags = sorted(set([t for t, _ in heading_candidates]))
- print('=== Likely Heading Tags ===')
- for tag in heading_tags:
- print(f'- {tag} (count: {tag_counter[tag]})')
- for s in tag_samples[tag]:
- if is_heading(s):
- print(f' Example: {s}')
- print()
-
- # Heuristic: likely paragraph tags are those with high frequency and long text
- para_candidates = []
- for tag, lens in tag_lengths.items():
- if tag_counter[tag] > 100 and sum(l > 10 for l in lens) > 0:
- para_candidates.append(tag)
- print('=== Likely Paragraph Tags ===')
- for tag in para_candidates:
- print(f'- {tag} (count: {tag_counter[tag]})')
- for s in tag_samples[tag]:
- print(f' Example: {s}')
- print()
-
- # Print summary
- print('=== Summary ===')
- print(f'Article title tag: {likely_title[0] if likely_title else "Not found"}')
- print(f'Heading tags: {", ".join(heading_tags) if heading_tags else "Not found"}')
- print(f'Paragraph tags: {", ".join(para_candidates) if para_candidates else "Not found"}')
- print()
-
- # Print total word/token count
- print('=== Word/Token Count ===')
- print(f'Total words: {total_words}')
- print(f'Total tokens: {total_tokens}')
- print()
-
- # Build and print section tree (all tags)
- print('=== Section Tree (all tags, grouped, up to 3 per tag per level) ===')
- section_tree = build_section_tree(root)
- if section_tree:
- print_section_tree(section_tree)
- else:
- print('No section structure found.')
-
-if __name__ == '__main__':
- if len(sys.argv) != 2:
- print('Usage: python scripts/xml_auto_headings_analysis.py ')
- sys.exit(1)
- main(sys.argv[1])
\ No newline at end of file
diff --git a/test_cases_data_fetcher_auto_update.md b/test_cases_data_fetcher_auto_update.md
deleted file mode 100644
index 10ed7c98..00000000
--- a/test_cases_data_fetcher_auto_update.md
+++ /dev/null
@@ -1,1100 +0,0 @@
-# Test Cases for Data Fetcher and Auto Update Pipeline
-
-**Author:** Fanxing Bu
-**Module:** `app/core/data_fetcher/fetch_regulations.py` and `app/core/auto_update_pipeline.py`
-
-## Overview
-
-This document provides comprehensive test cases for all functions in the data fetcher and auto update pipeline modules. Each test case includes:
-- Function description
-- Input/output specifications
-- Test scenarios (normal, edge cases, error conditions)
-- Sample data structures
-- Expected behaviors
-
----
-
-## Module: `app/core/data_fetcher/fetch_regulations.py`
-
-### 1. `setup_logging(verbose: bool = False) -> logging.Logger`
-
-**Function Description:** Sets up logging configuration with specified verbosity level.
-
-**Test Cases:**
-
-#### TC-001: Default Logging Setup
-- **Description:** Test logging setup with default parameters (verbose=False)
-- **Input:** `verbose=False`
-- **Expected Output:** Logger instance with INFO level
-- **Test Steps:**
- 1. Call `setup_logging(False)`
- 2. Verify logger level is INFO
- 3. Verify logger format includes timestamp, level, and message
-
-#### TC-002: Verbose Logging Setup
-- **Description:** Test logging setup with verbose mode enabled
-- **Input:** `verbose=True`
-- **Expected Output:** Logger instance with DEBUG level
-- **Test Steps:**
- 1. Call `setup_logging(True)`
- 2. Verify logger level is DEBUG
- 3. Verify logger format is correct
-
-#### TC-003: Logger Functionality Test
-- **Description:** Test that created logger can log messages
-- **Input:** `verbose=True`
-- **Expected Output:** Logger that can log debug, info, warning, error messages
-- **Test Steps:**
- 1. Create logger with `setup_logging(True)`
- 2. Log messages at different levels
- 3. Verify messages are properly formatted
-
----
-
-### 2. `get_single_document(doc_number: str) -> Optional[Dict]`
-
-**Function Description:** Fetches a single document by its document number from Federal Register API.
-
-**Test Cases:**
-
-#### TC-004: Valid Document Fetch
-- **Description:** Test fetching a valid document number
-- **Input:** `doc_number="2024-06431"`
-- **Expected Output:** Dictionary with document data
-- **Sample Response:**
-```json
-{
- "document_number": "2024-06431",
- "title": "Medicare Program; Calendar Year (CY) 2025 Home Health Prospective Payment System Rate Update",
- "type": "Rule",
- "publication_date": "2024-03-28",
- "html_url": "https://www.federalregister.gov/documents/2024/03/28/2024-06431/...",
- "pdf_url": "https://www.govinfo.gov/content/pkg/FR-2024-03-28/pdf/2024-06431.pdf",
- "full_text_xml_url": "https://www.federalregister.gov/documents/full_text/xml/2024/2024-06431.xml"
-}
-```
-
-#### TC-005: Invalid Document Number
-- **Description:** Test fetching with invalid document number
-- **Input:** `doc_number="invalid-doc-123"`
-- **Expected Output:** `None`
-- **Test Steps:**
- 1. Call function with invalid document number
- 2. Verify function returns None
- 3. Verify error is logged
-
-#### TC-006: Empty Document Number
-- **Description:** Test with empty document number
-- **Input:** `doc_number=""`
-- **Expected Output:** `None`
-- **Test Steps:**
- 1. Call function with empty string
- 2. Verify function returns None
- 3. Verify error is logged
-
-#### TC-007: Network Error Handling
-- **Description:** Test behavior when network request fails
-- **Input:** `doc_number="2024-06431"` (with mocked network failure)
-- **Expected Output:** `None`
-- **Test Steps:**
- 1. Mock requests.get to raise RequestException
- 2. Call function
- 3. Verify function returns None
- 4. Verify error is logged
-
----
-
-### 3. `get_latest_documents(days: int = 365) -> List[Dict]`
-
-**Function Description:** Fetches latest documents from Federal Register API with pagination support.
-
-**Test Cases:**
-
-#### TC-008: Default Days Parameter
-- **Description:** Test with default days parameter (365)
-- **Input:** `days=365`
-- **Expected Output:** List of document dictionaries
-- **Test Steps:**
- 1. Call function with default parameter
- 2. Verify returned list contains document dictionaries
- 3. Verify documents have required fields (document_number, title, type, publication_date)
-
-#### TC-009: Custom Days Parameter
-- **Description:** Test with custom days parameter
-- **Input:** `days=30`
-- **Expected Output:** List of documents from last 30 days
-- **Test Steps:**
- 1. Call function with days=30
- 2. Verify API call uses correct date range
- 3. Verify returned documents are within date range
-
-#### TC-010: Pagination Handling
-- **Description:** Test handling of multiple pages of results
-- **Input:** `days=365` (with mocked multi-page response)
-- **Expected Output:** Combined list from all pages
-- **Test Steps:**
- 1. Mock API to return multiple pages
- 2. Call function
- 3. Verify all pages are processed
- 4. Verify delay between requests
-
-#### TC-011: Empty Results
-- **Description:** Test when no documents are found
-- **Input:** `days=1` (very recent)
-- **Expected Output:** Empty list `[]`
-- **Test Steps:**
- 1. Call function with very recent date range
- 2. Verify empty list is returned
- 3. Verify no errors are logged
-
-#### TC-012: API Error Handling
-- **Description:** Test behavior when API request fails
-- **Input:** `days=365` (with mocked API failure)
-- **Expected Output:** Empty list `[]`
-- **Test Steps:**
- 1. Mock requests.get to raise RequestException
- 2. Call function
- 3. Verify empty list is returned
- 4. Verify error is logged
-
----
-
-### 4. `is_valid_xml(filepath: Path) -> bool`
-
-**Function Description:** Validates if a file is a valid XML file using lxml parser.
-
-**Test Cases:**
-
-#### TC-013: Valid XML File
-- **Description:** Test with valid XML file
-- **Input:** Path to valid XML file
-- **Expected Output:** `True`
-- **Sample XML:**
-```xml
-
-
-
- 2024-06431
- 2024-03-28
- Rule
- Medicare Program; Calendar Year (CY) 2025 Home Health Prospective Payment System Rate Update
-
-
-```
-
-#### TC-014: Invalid XML File
-- **Description:** Test with malformed XML file
-- **Input:** Path to invalid XML file
-- **Expected Output:** `False`
-- **Sample Invalid XML:**
-```xml
-
-
-
- 2024-06431
- 2024-03-28
- Rule
- Medicare Program; Calendar Year (CY) 2025 Home Health Prospective Payment System Rate Update
-
-
-```
-
-#### TC-015: Non-XML File
-- **Description:** Test with non-XML file (e.g., text file)
-- **Input:** Path to text file
-- **Expected Output:** `False`
-- **Sample Content:**
-```
-This is a plain text file, not XML
-```
-
-#### TC-016: Non-existent File
-- **Description:** Test with non-existent file path
-- **Input:** Path to non-existent file
-- **Expected Output:** `False`
-- **Test Steps:**
- 1. Call function with non-existent path
- 2. Verify function returns False
- 3. Verify no exception is raised
-
-#### TC-017: Empty XML File
-- **Description:** Test with empty XML file
-- **Input:** Path to empty file
-- **Expected Output:** `False`
-- **Test Steps:**
- 1. Create empty file
- 2. Call function
- 3. Verify function returns False
-
----
-
-### 5. `extract_year_from_title(doc: Dict, program_type: str) -> Optional[str]`
-
-**Function Description:** Extracts year from document title using regex patterns for different program types.
-
-**Test Cases:**
-
-#### TC-018: MPFS Calendar Year Extraction
-- **Description:** Test year extraction for MPFS documents
-- **Input:**
- - `doc={"title": "Medicare Program; Calendar Year (CY) 2025 Home Health Prospective Payment System Rate Update"}`
- - `program_type="MPFS"`
-- **Expected Output:** `"2025"`
-- **Test Patterns:**
- - "CY 2025" → "2025"
- - "Calendar Year (CY) 2025" → "2025"
- - "Calendar Year 2025" → "2025"
-
-#### TC-019: HOSPICE Fiscal Year Extraction
-- **Description:** Test year extraction for HOSPICE documents
-- **Input:**
- - `doc={"title": "Medicare Program; Fiscal Year (FY) 2025 Hospice Wage Index Update"}`
- - `program_type="HOSPICE"`
-- **Expected Output:** `"2025"`
-- **Test Patterns:**
- - "FY 2025" → "2025"
- - "Fiscal Year (FY) 2025" → "2025"
- - "Fiscal Year 2025" → "2025"
-
-#### TC-020: SNF Federal Fiscal Year Extraction
-- **Description:** Test year extraction for SNF documents
-- **Input:**
- - `doc={"title": "Medicare Program; Federal Fiscal Year 2025 Prospective Payment System and Consolidated Billing for Skilled Nursing Facilities"}`
- - `program_type="SNF"`
-- **Expected Output:** `"2025"`
-- **Test Pattern:**
- - "Federal Fiscal Year 2025" → "2025"
-
-#### TC-021: No Year Found
-- **Description:** Test when no year pattern is found in title
-- **Input:**
- - `doc={"title": "Medicare Program; General Update"}`
- - `program_type="MPFS"`
-- **Expected Output:** `None`
-
-#### TC-022: Case Insensitive Matching
-- **Description:** Test case insensitive regex matching
-- **Input:**
- - `doc={"title": "Medicare Program; calendar year (cy) 2025 update"}`
- - `program_type="MPFS"`
-- **Expected Output:** `"2025"`
-
-#### TC-023: Multiple Year Patterns
-- **Description:** Test when multiple year patterns exist (should return first match)
-- **Input:**
- - `doc={"title": "Medicare Program; Calendar Year (CY) 2025 and Fiscal Year 2026 Update"}`
- - `program_type="MPFS"`
-- **Expected Output:** `"2025"`
-
-#### TC-024: Invalid Program Type
-- **Description:** Test with unsupported program type
-- **Input:**
- - `doc={"title": "Medicare Program; Calendar Year (CY) 2025 Update"}`
- - `program_type="INVALID"`
-- **Expected Output:** `None`
-
----
-
-### 6. `detect_program_type(doc: Dict) -> Tuple[bool, str]`
-
-**Function Description:** Detects program type from document title using keyword matching.
-
-**Test Cases:**
-
-#### TC-025: MPFS Detection
-- **Description:** Test detection of MPFS program type
-- **Input:** `doc={"title": "Medicare Physician Fee Schedule Update"}`
-- **Expected Output:** `(True, "MPFS")`
-- **Test Keywords:**
- - "medicare physician fee schedule"
- - "physician fee schedule"
- - "mpfs"
-
-#### TC-026: HOSPICE Detection
-- **Description:** Test detection of HOSPICE program type
-- **Input:** `doc={"title": "Hospice Wage Index Update"}`
-- **Expected Output:** `(True, "HOSPICE")`
-- **Test Keywords:**
- - "hospice wage"
- - "hospice payment"
- - "hospice quality"
-
-#### TC-027: SNF Detection
-- **Description:** Test detection of SNF program type
-- **Input:** `doc={"title": "Prospective Payment System and Consolidated Billing for Skilled Nursing Facilities"}`
-- **Expected Output:** `(True, "SNF")`
-- **Test Keywords:**
- - "skilled nursing facility"
- - "snf"
- - "nursing facility"
- - "consolidated billing"
-
-#### TC-028: Correction Document Detection
-- **Description:** Test detection of correction documents (should be skipped)
-- **Input:** `doc={"title": "Medicare Program; Correction to Physician Fee Schedule"}`
-- **Expected Output:** `(False, "")`
-
-#### TC-029: Unrecognized Program Type
-- **Description:** Test with unrecognized program type
-- **Input:** `doc={"title": "Medicare Program; General Update"}`
-- **Expected Output:** `(False, "")`
-
-#### TC-030: Case Insensitive Detection
-- **Description:** Test case insensitive keyword matching
-- **Input:** `doc={"title": "MEDICARE PHYSICIAN FEE SCHEDULE UPDATE"}`
-- **Expected Output:** `(True, "MPFS")`
-
-#### TC-031: Empty Title
-- **Description:** Test with empty title
-- **Input:** `doc={"title": ""}`
-- **Expected Output:** `(False, "")`
-
----
-
-### 7. `download_xml(doc: Dict, save_dir: Path, logger: Optional[logging.Logger] = None) -> bool`
-
-**Function Description:** Downloads XML file for a document and saves it with standardized naming.
-
-**Test Cases:**
-
-#### TC-032: Successful Download
-- **Description:** Test successful XML download
-- **Input:**
- - `doc={"document_number": "2024-06431", "title": "Medicare Program; Calendar Year (CY) 2025 Home Health Prospective Payment System Rate Update", "type": "Rule", "publication_date": "2024-03-28"}`
- - `save_dir=Path("data")`
-- **Expected Output:** `True`
-- **Expected File:** `data/MPFS/2025_MPFS_final_2024-06431.xml`
-
-#### TC-033: File Already Exists
-- **Description:** Test when file already exists and is valid
-- **Input:** Existing valid XML file
-- **Expected Output:** `True`
-- **Test Steps:**
- 1. Create valid XML file at expected location
- 2. Call download function
- 3. Verify function returns True without downloading
-
-#### TC-034: Invalid XML Download
-- **Description:** Test when downloaded file is not valid XML
-- **Input:** Document that returns invalid XML
-- **Expected Output:** `False`
-- **Test Steps:**
- 1. Mock response to return invalid XML
- 2. Call download function
- 3. Verify function returns False
- 4. Verify downloaded file is deleted
-
-#### TC-035: Missing Document Information
-- **Description:** Test with missing required document fields
-- **Input:** `doc={"title": "Test Document"}` (missing document_number, publication_date, type)
-- **Expected Output:** `False`
-
-#### TC-036: Unrecognized Program Type
-- **Description:** Test with document that has unrecognized program type
-- **Input:** `doc={"document_number": "2024-06431", "title": "General Update", "type": "Rule", "publication_date": "2024-03-28"}`
-- **Expected Output:** `False`
-
-#### TC-037: Year Extraction Failure
-- **Description:** Test when year cannot be extracted from title
-- **Input:** `doc={"document_number": "2024-06431", "title": "Medicare Program Update", "type": "Rule", "publication_date": "2024-03-28"}`
-- **Expected Output:** `False`
-
-#### TC-038: Network Error During Download
-- **Description:** Test behavior when network request fails
-- **Input:** Valid document (with mocked network failure)
-- **Expected Output:** `False`
-- **Test Steps:**
- 1. Mock requests.get to raise RequestException
- 2. Call download function
- 3. Verify function returns False
- 4. Verify error is logged
-
-#### TC-039: Directory Creation
-- **Description:** Test automatic directory creation
-- **Input:** Valid document with non-existent program directory
-- **Expected Output:** `True`
-- **Test Steps:**
- 1. Ensure program directory doesn't exist
- 2. Call download function
- 3. Verify directory is created
- 4. Verify file is saved
-
----
-
-### 8. `generate_filename(doc: Dict, program_type: str) -> Optional[str]`
-
-**Function Description:** Generates standardized filename for a document.
-
-**Test Cases:**
-
-#### TC-040: MPFS Final Rule Filename
-- **Description:** Test filename generation for MPFS final rule
-- **Input:**
- - `doc={"document_number": "2024-06431", "title": "Medicare Program; Calendar Year (CY) 2025 Home Health Prospective Payment System Rate Update", "type": "Rule"}`
- - `program_type="MPFS"`
-- **Expected Output:** `"2025_MPFS_final_2024-06431.xml"`
-
-#### TC-041: HOSPICE Proposed Rule Filename
-- **Description:** Test filename generation for HOSPICE proposed rule
-- **Input:**
- - `doc={"document_number": "2024-06432", "title": "Medicare Program; Fiscal Year (FY) 2025 Hospice Wage Index Update", "type": "Proposed Rule"}`
- - `program_type="HOSPICE"`
-- **Expected Output:** `"2025_HOSPICE_proposed_2024-06432.xml"`
-
-#### TC-042: SNF Final Rule Filename
-- **Description:** Test filename generation for SNF final rule
-- **Input:**
- - `doc={"document_number": "2024-06433", "title": "Medicare Program; Federal Fiscal Year 2025 Prospective Payment System and Consolidated Billing for Skilled Nursing Facilities", "type": "Rule"}`
- - `program_type="SNF"`
-- **Expected Output:** `"2025_SNF_final_2024-06433.xml"`
-
-#### TC-043: Missing Document Number
-- **Description:** Test with missing document number
-- **Input:**
- - `doc={"title": "Test Document", "type": "Rule"}`
- - `program_type="MPFS"`
-- **Expected Output:** `None`
-
-#### TC-044: Missing Document Type
-- **Description:** Test with missing document type
-- **Input:**
- - `doc={"document_number": "2024-06431", "title": "Test Document"}`
- - `program_type="MPFS"`
-- **Expected Output:** `None`
-
-#### TC-045: Year Extraction Failure
-- **Description:** Test when year cannot be extracted from title
-- **Input:**
- - `doc={"document_number": "2024-06431", "title": "Medicare Program Update", "type": "Rule"}`
- - `program_type="MPFS"`
-- **Expected Output:** `None`
-
----
-
-### 9. `parse_args() -> argparse.Namespace`
-
-**Function Description:** Parses command line arguments for the script.
-
-**Test Cases:**
-
-#### TC-046: Default Arguments
-- **Description:** Test with no command line arguments
-- **Input:** `[]` (empty argument list)
-- **Expected Output:** Namespace with default values
-- **Expected Values:**
- - mode='latest'
- - doc_number=None
- - date=None
- - days=365
- - output_dir='data'
- - verbose=False
-
-#### TC-047: Single Mode with Document Number
-- **Description:** Test single mode with document number
-- **Input:** `["--mode", "single", "--doc-number", "2024-06431"]`
-- **Expected Output:** Namespace with single mode and document number
-- **Expected Values:**
- - mode='single'
- - doc_number='2024-06431'
-
-#### TC-048: Latest Mode with Custom Days
-- **Description:** Test latest mode with custom days
-- **Input:** `["--mode", "latest", "--days", "30"]`
-- **Expected Output:** Namespace with latest mode and 30 days
-- **Expected Values:**
- - mode='latest'
- - days=30
-
-#### TC-049: Verbose Mode
-- **Description:** Test verbose flag
-- **Input:** `["--verbose"]`
-- **Expected Output:** Namespace with verbose=True
-
-#### TC-050: Custom Output Directory
-- **Description:** Test custom output directory
-- **Input:** `["--output-dir", "/custom/path"]`
-- **Expected Output:** Namespace with custom output directory
-- **Expected Values:**
- - output_dir='/custom/path'
-
-#### TC-051: Invalid Mode
-- **Description:** Test with invalid mode argument
-- **Input:** `["--mode", "invalid"]`
-- **Expected Output:** SystemExit exception
-- **Test Steps:**
- 1. Call parse_args with invalid mode
- 2. Verify SystemExit is raised
-
----
-
-### 10. `main()`
-
-**Function Description:** Main function that orchestrates the complete document fetching process.
-
-**Test Cases:**
-
-#### TC-052: Single Document Mode
-- **Description:** Test main function in single document mode
-- **Input:** Command line arguments for single document
-- **Expected Output:** Downloaded XML file
-- **Test Steps:**
- 1. Mock command line arguments for single mode
- 2. Call main function
- 3. Verify document is fetched and downloaded
- 4. Verify summary is printed
-
-#### TC-053: Latest Documents Mode
-- **Description:** Test main function in latest documents mode
-- **Input:** Command line arguments for latest documents
-- **Expected Output:** Multiple downloaded XML files
-- **Test Steps:**
- 1. Mock command line arguments for latest mode
- 2. Call main function
- 3. Verify documents are fetched and downloaded
- 4. Verify summary statistics are printed
-
-#### TC-054: Error Handling
-- **Description:** Test main function error handling
-- **Input:** Invalid command line arguments
-- **Expected Output:** Error message and graceful exit
-- **Test Steps:**
- 1. Mock invalid arguments
- 2. Call main function
- 3. Verify error is handled gracefully
-
-#### TC-055: Summary Statistics
-- **Description:** Test summary statistics generation
-- **Input:** Various document processing scenarios
-- **Expected Output:** Accurate summary statistics
-- **Test Steps:**
- 1. Process documents with various outcomes
- 2. Verify summary shows correct counts
- 3. Verify all categories are accounted for
-
----
-
-## Module: `app/core/auto_update_pipeline.py`
-
-### 11. `AutoUpdatePipeline.__init__(days_back: int = 365, model: str = None)`
-
-**Function Description:** Initializes the AutoUpdatePipeline with configuration parameters.
-
-**Test Cases:**
-
-#### TC-056: Default Initialization
-- **Description:** Test initialization with default parameters
-- **Input:** No parameters
-- **Expected Output:** AutoUpdatePipeline instance with default values
-- **Expected Values:**
- - days_back=365
- - model=config.default_embedding_model
- - data_dir=Path(config.docs_data_path)
-
-#### TC-057: Custom Days Back
-- **Description:** Test initialization with custom days_back
-- **Input:** `days_back=30`
-- **Expected Output:** AutoUpdatePipeline instance with custom days_back
-- **Expected Values:**
- - days_back=30
-
-#### TC-058: Custom Model
-- **Description:** Test initialization with custom model
-- **Input:** `model="text-embedding-ada-002"`
-- **Expected Output:** AutoUpdatePipeline instance with custom model
-- **Expected Values:**
- - model="text-embedding-ada-002"
-
-#### TC-059: Both Custom Parameters
-- **Description:** Test initialization with both custom parameters
-- **Input:** `days_back=60, model="text-embedding-ada-002"`
-- **Expected Output:** AutoUpdatePipeline instance with both custom values
-- **Expected Values:**
- - days_back=60
- - model="text-embedding-ada-002"
-
----
-
-### 12. `fetch_new_regulations() -> List[Dict]`
-
-**Function Description:** Fetches new regulations from Federal Register and filters for relevant documents.
-
-**Test Cases:**
-
-#### TC-060: Successful Fetch with Relevant Documents
-- **Description:** Test fetching regulations with relevant documents found
-- **Input:** None (uses instance days_back)
-- **Expected Output:** List of relevant regulation documents
-- **Test Steps:**
- 1. Mock get_latest_documents to return relevant documents
- 2. Call fetch_new_regulations
- 3. Verify relevant documents are returned
- 4. Verify filtering logic works correctly
-
-#### TC-061: No Relevant Documents
-- **Description:** Test when no relevant documents are found
-- **Input:** None
-- **Expected Output:** Empty list `[]`
-- **Test Steps:**
- 1. Mock get_latest_documents to return irrelevant documents
- 2. Call fetch_new_regulations
- 3. Verify empty list is returned
-
-#### TC-062: Correction Document Filtering
-- **Description:** Test filtering of correction documents
-- **Input:** Documents including correction documents
-- **Expected Output:** List excluding correction documents
-- **Test Steps:**
- 1. Mock documents including correction documents (starting with "C")
- 2. Call fetch_new_regulations
- 3. Verify correction documents are filtered out
-
-#### TC-063: Future-Dated Document Filtering
-- **Description:** Test filtering of future-dated documents
-- **Input:** Documents including future-dated documents
-- **Expected Output:** List excluding future-dated documents
-- **Test Steps:**
- 1. Mock documents including future dates
- 2. Call fetch_new_regulations
- 3. Verify future-dated documents are filtered out
-
-#### TC-064: Non-Rule Document Filtering
-- **Description:** Test filtering of non-rule documents
-- **Input:** Documents including non-rule types
-- **Expected Output:** List containing only Rule and Proposed Rule documents
-- **Test Steps:**
- 1. Mock documents including non-rule types
- 2. Call fetch_new_regulations
- 3. Verify only rule documents are included
-
-#### TC-065: Unrecognized Program Type Filtering
-- **Description:** Test filtering of documents with unrecognized program types
-- **Input:** Documents including unrecognized program types
-- **Expected Output:** List containing only recognized program types
-- **Test Steps:**
- 1. Mock documents including unrecognized program types
- 2. Call fetch_new_regulations
- 3. Verify only recognized program types are included
-
-#### TC-066: Error Handling
-- **Description:** Test error handling during fetch process
-- **Input:** None (with mocked error)
-- **Expected Output:** Empty list `[]`
-- **Test Steps:**
- 1. Mock get_latest_documents to raise exception
- 2. Call fetch_new_regulations
- 3. Verify empty list is returned
- 4. Verify error is logged
-
----
-
-### 13. `download_new_files(regulations: List[Dict]) -> List[Path]`
-
-**Function Description:** Downloads XML files for new regulations.
-
-**Test Cases:**
-
-#### TC-067: Successful Downloads
-- **Description:** Test successful download of multiple files
-- **Input:** List of valid regulation documents
-- **Expected Output:** List of downloaded file paths
-- **Test Steps:**
- 1. Mock valid regulation documents
- 2. Call download_new_files
- 3. Verify files are downloaded
- 4. Verify correct file paths are returned
-
-#### TC-068: Empty Regulations List
-- **Description:** Test with empty regulations list
-- **Input:** `[]`
-- **Expected Output:** Empty list `[]`
-- **Test Steps:**
- 1. Call download_new_files with empty list
- 2. Verify empty list is returned
- 3. Verify appropriate log message
-
-#### TC-069: File Already Exists
-- **Description:** Test when files already exist and are valid
-- **Input:** Regulations for files that already exist
-- **Expected Output:** List of existing file paths
-- **Test Steps:**
- 1. Create valid XML files at expected locations
- 2. Call download_new_files
- 3. Verify existing files are not re-downloaded
- 4. Verify file paths are returned
-
-#### TC-070: Download Failure Handling
-- **Description:** Test handling of download failures
-- **Input:** Regulations including some that will fail to download
-- **Expected Output:** List of successfully downloaded file paths
-- **Test Steps:**
- 1. Mock some downloads to fail
- 2. Call download_new_files
- 3. Verify successful downloads are returned
- 4. Verify failed downloads are logged
-
-#### TC-071: Filename Generation Failure
-- **Description:** Test when filename generation fails
-- **Input:** Regulations that cannot generate valid filenames
-- **Expected Output:** List excluding failed filename generations
-- **Test Steps:**
- 1. Mock regulations that cause filename generation to fail
- 2. Call download_new_files
- 3. Verify failed cases are skipped
- 4. Verify errors are logged
-
-#### TC-072: Directory Creation
-- **Description:** Test automatic directory creation for new program types
-- **Input:** Regulations for new program types
-- **Expected Output:** List of downloaded files in new directories
-- **Test Steps:**
- 1. Mock regulations for new program types
- 2. Ensure directories don't exist
- 3. Call download_new_files
- 4. Verify directories are created
- 5. Verify files are saved in correct locations
-
----
-
-### 14. `process_new_files(downloaded_files: List[Path]) -> List[Dict]`
-
-**Function Description:** Processes newly downloaded files through incremental pipeline.
-
-**Test Cases:**
-
-#### TC-073: Successful Processing
-- **Description:** Test successful processing of multiple files
-- **Input:** List of valid file paths
-- **Expected Output:** List of processing results
-- **Test Steps:**
- 1. Mock valid file paths
- 2. Mock incremental pipeline to return success results
- 3. Call process_new_files
- 4. Verify processing results are returned
-
-#### TC-074: Empty Files List
-- **Description:** Test with empty files list
-- **Input:** `[]`
-- **Expected Output:** Empty list `[]`
-- **Test Steps:**
- 1. Call process_new_files with empty list
- 2. Verify empty list is returned
- 3. Verify appropriate log message
-
-#### TC-075: Processing Failure Handling
-- **Description:** Test handling of processing failures
-- **Input:** Files including some that will fail processing
-- **Expected Output:** List of processing results (successful and failed)
-- **Test Steps:**
- 1. Mock some files to fail processing
- 2. Call process_new_files
- 3. Verify successful processing results are returned
- 4. Verify failed processing is logged
-
-#### TC-076: Path Conversion
-- **Description:** Test conversion of absolute paths to relative paths
-- **Input:** List of absolute file paths
-- **Expected Output:** Processing results for relative paths
-- **Test Steps:**
- 1. Provide absolute file paths
- 2. Call process_new_files
- 3. Verify paths are converted to relative paths
- 4. Verify incremental pipeline receives relative paths
-
-#### TC-077: Cost Tracking
-- **Description:** Test cost tracking in processing results
-- **Input:** Files with various processing costs
-- **Expected Output:** Processing results with cost information
-- **Test Steps:**
- 1. Mock processing results with different costs
- 2. Call process_new_files
- 3. Verify cost information is preserved
- 4. Verify cost display logic works correctly
-
----
-
-### 15. `run_full_update() -> Dict`
-
-**Function Description:** Runs the complete automated update process.
-
-**Test Cases:**
-
-#### TC-078: Complete Successful Update
-- **Description:** Test complete successful update process
-- **Input:** None
-- **Expected Output:** Dictionary with complete update statistics
-- **Test Steps:**
- 1. Mock all sub-functions to return success
- 2. Call run_full_update
- 3. Verify all steps are executed
- 4. Verify comprehensive statistics are returned
- 5. Verify timing information is included
-
-#### TC-079: No New Regulations
-- **Description:** Test when no new regulations are found
-- **Input:** None
-- **Expected Output:** Dictionary with zero statistics
-- **Test Steps:**
- 1. Mock fetch_new_regulations to return empty list
- 2. Call run_full_update
- 3. Verify statistics show zero values
- 4. Verify process completes gracefully
-
-#### TC-080: Partial Success
-- **Description:** Test when some steps succeed and others fail
-- **Input:** None
-- **Expected Output:** Dictionary with mixed success/failure statistics
-- **Test Steps:**
- 1. Mock mixed success/failure scenarios
- 2. Call run_full_update
- 3. Verify statistics reflect partial success
- 4. Verify all steps are attempted
-
-#### TC-081: Error Handling
-- **Description:** Test error handling during update process
-- **Input:** None (with mocked errors)
-- **Expected Output:** Dictionary with error information
-- **Test Steps:**
- 1. Mock errors in various steps
- 2. Call run_full_update
- 3. Verify errors are handled gracefully
- 4. Verify partial results are returned
-
-#### TC-082: Statistics Calculation
-- **Description:** Test accurate statistics calculation
-- **Input:** Various processing scenarios
-- **Expected Output:** Accurate statistics dictionary
-- **Expected Statistics:**
- - regulations_found
- - files_downloaded
- - files_processed
- - files_successful
- - total_chunks_created
- - total_embeddings_added
- - total_cost
- - duration_seconds
-
-#### TC-083: Timing Information
-- **Description:** Test timing information in results
-- **Input:** None
-- **Expected Output:** Dictionary with accurate timing
-- **Test Steps:**
- 1. Call run_full_update
- 2. Verify duration_seconds is included
- 3. Verify timing is reasonable
- 4. Verify timing is rounded to 2 decimal places
-
----
-
-### 16. `check_for_updates() -> bool`
-
-**Function Description:** Checks if there are any new regulations available.
-
-**Test Cases:**
-
-#### TC-084: Updates Available
-- **Description:** Test when new regulations are available
-- **Input:** None
-- **Expected Output:** `True`
-- **Test Steps:**
- 1. Mock fetch_new_regulations to return new regulations
- 2. Mock some files to not exist
- 3. Call check_for_updates
- 4. Verify True is returned
-
-#### TC-085: No Updates Available
-- **Description:** Test when no new regulations are available
-- **Input:** None
-- **Expected Output:** `False`
-- **Test Steps:**
- 1. Mock fetch_new_regulations to return empty list
- 2. Call check_for_updates
- 3. Verify False is returned
-
-#### TC-086: All Files Already Exist
-- **Description:** Test when regulations exist but files are already downloaded
-- **Input:** None
-- **Expected Output:** `False`
-- **Test Steps:**
- 1. Mock fetch_new_regulations to return regulations
- 2. Mock all files to exist
- 3. Call check_for_updates
- 4. Verify False is returned
-
-#### TC-087: Mixed Scenario
-- **Description:** Test when some files exist and others don't
-- **Input:** None
-- **Expected Output:** `True`
-- **Test Steps:**
- 1. Mock fetch_new_regulations to return regulations
- 2. Mock some files to exist and others to not exist
- 3. Call check_for_updates
- 4. Verify True is returned (if any files missing)
-
-#### TC-088: Error Handling
-- **Description:** Test error handling during update check
-- **Input:** None (with mocked errors)
-- **Expected Output:** `False`
-- **Test Steps:**
- 1. Mock fetch_new_regulations to raise exception
- 2. Call check_for_updates
- 3. Verify False is returned
- 4. Verify error is logged
-
----
-
-### 17. `get_system_status() -> Dict`
-
-**Function Description:** Gets comprehensive system status information.
-
-**Test Cases:**
-
-#### TC-089: Complete Status Information
-- **Description:** Test complete system status retrieval
-- **Input:** None
-- **Expected Output:** Dictionary with comprehensive status information
-- **Expected Keys:**
- - updates_available
- - days_back
- - last_check
- - processed_files_count
- - total_chunks
- - faiss_index_size
- - new_files
- - deleted_files
-
-#### TC-090: Status with Updates Available
-- **Description:** Test status when updates are available
-- **Input:** None
-- **Expected Output:** Dictionary with updates_available=True
-- **Test Steps:**
- 1. Mock check_for_updates to return True
- 2. Call get_system_status
- 3. Verify updates_available=True
-
-#### TC-091: Status with No Updates
-- **Description:** Test status when no updates are available
-- **Input:** None
-- **Expected Output:** Dictionary with updates_available=False
-- **Test Steps:**
- 1. Mock check_for_updates to return False
- 2. Call get_system_status
- 3. Verify updates_available=False
-
-#### TC-092: Incremental Pipeline Status Integration
-- **Description:** Test integration with incremental pipeline status
-- **Input:** None
-- **Expected Output:** Dictionary including incremental pipeline status
-- **Test Steps:**
- 1. Mock incremental pipeline status
- 2. Call get_system_status
- 3. Verify incremental pipeline status is included
- 4. Verify all status information is merged correctly
-
-#### TC-093: Timestamp Information
-- **Description:** Test timestamp information in status
-- **Input:** None
-- **Expected Output:** Dictionary with current timestamp
-- **Test Steps:**
- 1. Call get_system_status
- 2. Verify last_check contains current timestamp
- 3. Verify timestamp is in ISO format
-
----
-
-## Integration Test Cases
-
-### TC-094: End-to-End Pipeline Test
-**Description:** Test complete end-to-end pipeline execution
-**Test Steps:**
-1. Initialize AutoUpdatePipeline
-2. Run complete update process
-3. Verify all components work together
-4. Verify final results are consistent
-
-### TC-095: Error Recovery Test
-**Description:** Test system recovery after errors
-**Test Steps:**
-1. Introduce errors at various points
-2. Verify system continues processing
-3. Verify partial results are preserved
-4. Verify error reporting is accurate
-
-### TC-096: Performance Test
-**Description:** Test performance with large datasets
-**Test Steps:**
-1. Use large number of regulations
-2. Measure processing time
-3. Verify memory usage is reasonable
-4. Verify system remains responsive
-
-### TC-097: Configuration Test
-**Description:** Test different configuration scenarios
-**Test Steps:**
-1. Test with different models
-2. Test with different days_back values
-3. Test with different data directories
-4. Verify configuration is properly applied
-
----
-
-## Test Data Requirements
-
-### Sample Document Data
-```json
-{
- "document_number": "2024-06431",
- "title": "Medicare Program; Calendar Year (CY) 2025 Home Health Prospective Payment System Rate Update",
- "type": "Rule",
- "publication_date": "2024-03-28",
- "html_url": "https://www.federalregister.gov/documents/2024/03/28/2024-06431/...",
- "pdf_url": "https://www.govinfo.gov/content/pkg/FR-2024-03-28/pdf/2024-06431.pdf",
- "full_text_xml_url": "https://www.federalregister.gov/documents/full_text/xml/2024/2024-06431.xml"
-}
-```
-
-### Sample XML Structure
-```xml
-
-
-
- 2024-06431
- 2024-03-28
- Rule
- Medicare Program; Calendar Year (CY) 2025 Home Health Prospective Payment System Rate Update
-
-
- Centers for Medicare & Medicaid Services (CMS), HHS.
- Final rule.
-
-
- Supplementary Information:
- I. Executive Summary
- ...
-
-
-
-
-```
-
-### Sample Processing Results
-```json
-{
- "status": "success",
- "chunks_created": 150,
- "embeddings_added": 150,
- "estimated_cost": 0.0450,
- "file_path": "MPFS/2025_MPFS_final_2024-06431.xml"
-}
-```
-
----
-
-## Notes
-
-1. **Mocking Strategy:** All external API calls should be mocked to ensure test reliability and speed.
-2. **File System Operations:** Use temporary directories for file system tests to avoid conflicts.
-3. **Error Simulation:** Test various error conditions including network failures, invalid data, and system errors.
-4. **Performance Considerations:** Monitor test execution time and resource usage for large datasets.
-5. **Configuration Management:** Ensure tests use appropriate configuration for different environments.
-6. **Logging Verification:** Verify that appropriate log messages are generated for different scenarios.
-7. **Edge Cases:** Pay special attention to edge cases such as empty inputs, malformed data, and boundary conditions.
\ No newline at end of file
diff --git a/test_cases_incremental_chunking_embedding.md b/test_cases_incremental_chunking_embedding.md
deleted file mode 100644
index e5c69d5c..00000000
--- a/test_cases_incremental_chunking_embedding.md
+++ /dev/null
@@ -1,1164 +0,0 @@
-# Test Cases for Incremental Chunking and Embedding Functions
-
-**Author:** Fanxing Bu
-**Module:** `app/core/incremental_chunker.py`, `app/core/incremental_faiss.py`, `app/core/xml_chunker.py`, `app/core/build_faiss.py`
-
-## Overview
-
-This document provides comprehensive test cases for the incremental processing system, including:
-- Incremental chunking functions
-- Incremental embedding functions
-- Base chunking and embedding functions
-- Integration between components
-
----
-
-## Module: `app/core/xml_chunker.py` (Base Chunking)
-
-### 1. `XMLChunker.__init__(input_dir, chunk_words, overlap_sentences, output_chunks)`
-
-**Function Description:** Initializes the XML chunker with configuration parameters.
-
-**Test Cases:**
-
-#### TC-001: Default Initialization
-- **Description:** Test initialization with default parameters
-- **Input:** No parameters
-- **Expected Output:** XMLChunker instance with default values
-- **Expected Values:**
- - input_dir=config.docs_data_path
- - chunk_words=500
- - overlap_sentences=1
- - output_chunks=config.build_faiss_output_folder/chunks.json
-
-#### TC-002: Custom Parameters
-- **Description:** Test initialization with custom parameters
-- **Input:** `input_dir="/custom/path", chunk_words=300, overlap_sentences=2, output_chunks="/custom/chunks.json"`
-- **Expected Output:** XMLChunker instance with custom values
-- **Expected Values:**
- - input_dir=Path("/custom/path")
- - chunk_words=300
- - overlap_sentences=2
- - output_chunks="/custom/chunks.json"
-
----
-
-### 2. `clean_text(text: str) -> str`
-
-**Function Description:** Cleans text by removing extra whitespace.
-
-**Test Cases:**
-
-#### TC-003: Normal Text Cleaning
-- **Description:** Test cleaning of normal text with extra whitespace
-- **Input:** `" This is a test text "`
-- **Expected Output:** `"This is a test text"`
-
-#### TC-004: None Input
-- **Description:** Test handling of None input
-- **Input:** `None`
-- **Expected Output:** `""`
-
-#### TC-005: Empty String
-- **Description:** Test handling of empty string
-- **Input:** `""`
-- **Expected Output:** `""`
-
-#### TC-006: Multiple Whitespace Characters
-- **Description:** Test cleaning of various whitespace characters
-- **Input:** `"Text\twith\nmultiple\r\nspaces"`
-- **Expected Output:** `"Text with multiple spaces"`
-
----
-
-### 3. `infer_metadata_from_filename(filename: str) -> Dict`
-
-**Function Description:** Extracts metadata from filename using regex patterns.
-
-**Test Cases:**
-
-#### TC-007: MPFS Final Rule
-- **Description:** Test MPFS final rule filename parsing
-- **Input:** `"2025_MPFS_final_2024-06431.xml"`
-- **Expected Output:**
-```json
-{
- "source_file": "2025_MPFS_final_2024-06431.xml",
- "program": "MPFS",
- "rule_type": "Final",
- "year": 2025
-}
-```
-
-#### TC-008: HOSPICE Proposed Rule
-- **Description:** Test HOSPICE proposed rule filename parsing
-- **Input:** `"2025_HOSPICE_proposed_2024-06432.xml"`
-- **Expected Output:**
-```json
-{
- "source_file": "2025_HOSPICE_proposed_2024-06432.xml",
- "program": "Hospice",
- "rule_type": "Proposed",
- "year": 2025
-}
-```
-
-#### TC-009: SNF Final Rule
-- **Description:** Test SNF final rule filename parsing
-- **Input:** `"2025_SNF_final_2024-06433.xml"`
-- **Expected Output:**
-```json
-{
- "source_file": "2025_SNF_final_2024-06433.xml",
- "program": "SNF",
- "rule_type": "Final",
- "year": 2025
-}
-```
-
-#### TC-010: Unknown Program Type
-- **Description:** Test filename with unknown program type
-- **Input:** `"2025_UNKNOWN_final_2024-06434.xml"`
-- **Expected Output:**
-```json
-{
- "source_file": "2025_UNKNOWN_final_2024-06434.xml",
- "program": "Unknown",
- "rule_type": "Final",
- "year": 2025
-}
-```
-
-#### TC-011: No Year in Filename
-- **Description:** Test filename without year
-- **Input:** `"MPFS_final_2024-06431.xml"`
-- **Expected Output:**
-```json
-{
- "source_file": "MPFS_final_2024-06431.xml",
- "program": "MPFS",
- "rule_type": "Final",
- "year": null
-}
-```
-
----
-
-### 4. `extract_preamb_metadata(root: ET.Element) -> Dict`
-
-**Function Description:** Extracts metadata from XML document preamble.
-
-**Test Cases:**
-
-#### TC-012: Complete Metadata Extraction
-- **Description:** Test extraction of complete metadata from XML
-- **Input:** XML element with all metadata fields
-- **Expected Output:**
-```json
-{
- "title": "Medicare Program; Calendar Year (CY) 2025 Home Health Prospective Payment System Rate Update",
- "document_id": "2024-06431",
- "cfr": "42 CFR 484",
- "effective_date": "January 1, 2025"
-}
-```
-
-#### TC-013: Missing Metadata Fields
-- **Description:** Test extraction when some fields are missing
-- **Input:** XML element with missing fields
-- **Expected Output:**
-```json
-{
- "title": "Medicare Program Update",
- "document_id": "",
- "cfr": "",
- "effective_date": ""
-}
-```
-
-#### TC-014: Empty XML Element
-- **Description:** Test extraction from empty XML element
-- **Input:** Empty XML element
-- **Expected Output:**
-```json
-{
- "title": "",
- "document_id": "",
- "cfr": "",
- "effective_date": ""
-}
-```
-
----
-
-### 5. `chunk_document(root: ET.Element, metadata: Dict) -> List[Dict]`
-
-**Function Description:** Chunks XML document into smaller pieces based on word count and section headers.
-
-**Test Cases:**
-
-#### TC-015: Single Section Document
-- **Description:** Test chunking of document with single section
-- **Input:** XML with single section and multiple paragraphs
-- **Expected Output:** List of chunks with proper metadata
-- **Test Steps:**
- 1. Create XML with single section
- 2. Add multiple paragraphs exceeding chunk_words
- 3. Verify chunks are created with correct word count
- 4. Verify section headers are preserved
-
-#### TC-016: Multi-Section Document
-- **Description:** Test chunking of document with multiple sections
-- **Input:** XML with multiple sections (HD1, HD2, HD3)
-- **Expected Output:** List of chunks with hierarchical section headers
-- **Test Steps:**
- 1. Create XML with multiple section levels
- 2. Verify section hierarchy is maintained
- 3. Verify chunks span across sections correctly
-
-#### TC-017: Overlap Between Chunks
-- **Description:** Test sentence overlap between consecutive chunks
-- **Input:** XML with paragraphs that create chunks with overlap
-- **Expected Output:** Chunks with specified sentence overlap
-- **Test Steps:**
- 1. Create XML that triggers chunk creation
- 2. Verify last sentences of chunk appear in next chunk
- 3. Verify overlap_sentences parameter is respected
-
-#### TC-018: Empty Document
-- **Description:** Test chunking of empty document
-- **Input:** XML with no content
-- **Expected Output:** Empty list `[]`
-
-#### TC-019: Document Below Chunk Threshold
-- **Description:** Test document that doesn't reach chunk threshold
-- **Input:** XML with content below chunk_words limit
-- **Expected Output:** Single chunk with all content
-
----
-
-### 6. `process_files() -> List[Dict]`
-
-**Function Description:** Processes all XML files in input directory.
-
-**Test Cases:**
-
-#### TC-020: Multiple Files Processing
-- **Description:** Test processing of multiple XML files
-- **Input:** Directory with multiple XML files
-- **Expected Output:** Combined list of chunks from all files
-- **Test Steps:**
- 1. Create multiple XML files in input directory
- 2. Call process_files
- 3. Verify all files are processed
- 4. Verify chunks from all files are included
-
-#### TC-021: Skip Root Files
-- **Description:** Test that root-level XML files are skipped
-- **Input:** Directory with XML files at root level
-- **Expected Output:** Empty list (no chunks from root files)
-- **Test Steps:**
- 1. Create XML files at root level
- 2. Call process_files
- 3. Verify root files are skipped
- 4. Verify appropriate log messages
-
-#### TC-022: Error Handling
-- **Description:** Test handling of malformed XML files
-- **Input:** Directory with valid and invalid XML files
-- **Expected Output:** Chunks from valid files only
-- **Test Steps:**
- 1. Create mix of valid and invalid XML files
- 2. Call process_files
- 3. Verify valid files are processed
- 4. Verify errors are logged for invalid files
-
----
-
-### 7. `save_chunks(chunks: List[Dict]) -> None`
-
-**Function Description:** Saves chunks to output file.
-
-**Test Cases:**
-
-#### TC-023: Save Valid Chunks
-- **Description:** Test saving valid chunks to file
-- **Input:** List of valid chunk dictionaries
-- **Expected Output:** Chunks saved to output file
-- **Test Steps:**
- 1. Create valid chunks list
- 2. Call save_chunks
- 3. Verify file is created
- 4. Verify content matches input
-
-#### TC-024: Create Output Directory
-- **Description:** Test automatic creation of output directory
-- **Input:** Chunks with non-existent output directory
-- **Expected Output:** Directory created and chunks saved
-- **Test Steps:**
- 1. Set output_chunks to non-existent directory
- 2. Call save_chunks
- 3. Verify directory is created
- 4. Verify file is saved
-
----
-
-## Module: `app/core/incremental_chunker.py` (Incremental Chunking)
-
-### 8. `IncrementalChunker.__init__(input_dir, chunk_words, overlap_sentences, output_chunks)`
-
-**Function Description:** Initializes incremental chunker with configuration.
-
-**Test Cases:**
-
-#### TC-025: Default Initialization
-- **Description:** Test initialization with default parameters
-- **Input:** No parameters
-- **Expected Output:** IncrementalChunker instance with defaults
-- **Expected Values:**
- - input_dir=config.docs_data_path
- - chunk_words=500
- - overlap_sentences=1
- - output_chunks=config.build_faiss_output_folder/chunks.json
- - processed_files_tracker=config.build_faiss_output_folder/processed_files.json
-
----
-
-### 9. `get_file_hash(file_path: Path) -> str`
-
-**Function Description:** Generates SHA256 hash of file content.
-
-**Test Cases:**
-
-#### TC-026: File Hash Generation
-- **Description:** Test hash generation for valid file
-- **Input:** Path to valid file
-- **Expected Output:** SHA256 hash string
-- **Test Steps:**
- 1. Create test file with known content
- 2. Call get_file_hash
- 3. Verify hash matches expected value
-
-#### TC-027: Non-existent File
-- **Description:** Test hash generation for non-existent file
-- **Input:** Path to non-existent file
-- **Expected Output:** FileNotFoundError
-
----
-
-### 10. `load_processed_files() -> Dict`
-
-**Function Description:** Loads processed files tracking information.
-
-**Test Cases:**
-
-#### TC-028: Load Existing File
-- **Description:** Test loading existing processed files
-- **Input:** Existing processed_files.json
-- **Expected Output:** Dictionary with file tracking data
-- **Sample Data:**
-```json
-{
- "MPFS/2025_MPFS_final_2024-06431.xml": {
- "hash": "abc123...",
- "processed_at": "1703123456.789",
- "chunks_count": 150
- }
-}
-```
-
-#### TC-029: Non-existent File
-- **Description:** Test loading when file doesn't exist
-- **Input:** Non-existent processed_files.json
-- **Expected Output:** Empty dictionary `{}`
-
----
-
-### 11. `save_processed_files(processed_files: Dict) -> None`
-
-**Function Description:** Saves processed files tracking information.
-
-**Test Cases:**
-
-#### TC-030: Save Valid Data
-- **Description:** Test saving valid processed files data
-- **Input:** Valid dictionary with file tracking data
-- **Expected Output:** Data saved to file
-- **Test Steps:**
- 1. Create valid tracking data
- 2. Call save_processed_files
- 3. Verify file is created/updated
- 4. Verify content matches input
-
----
-
-### 12. `is_file_processed(file_path: Path) -> bool`
-
-**Function Description:** Checks if file has been processed and is unchanged.
-
-**Test Cases:**
-
-#### TC-031: File Not Processed
-- **Description:** Test unprocessed file
-- **Input:** Path to unprocessed file
-- **Expected Output:** `False`
-
-#### TC-032: File Processed and Unchanged
-- **Description:** Test processed file with same hash
-- **Input:** Path to processed file with matching hash
-- **Expected Output:** `True`
-
-#### TC-033: File Processed but Modified
-- **Description:** Test processed file with different hash
-- **Input:** Path to processed file with different hash
-- **Expected Output:** `False`
-
----
-
-### 13. `find_new_files() -> List[Path]`
-
-**Function Description:** Finds new or modified files that need processing.
-
-**Test Cases:**
-
-#### TC-034: New Files Found
-- **Description:** Test finding new files
-- **Input:** Directory with new XML files
-- **Expected Output:** List of paths to new files
-- **Test Steps:**
- 1. Add new XML files to directory
- 2. Call find_new_files
- 3. Verify new files are returned
-
-#### TC-035: Modified Files Found
-- **Description:** Test finding modified files
-- **Input:** Directory with modified XML files
-- **Expected Output:** List of paths to modified files
-- **Test Steps:**
- 1. Modify existing XML files
- 2. Call find_new_files
- 3. Verify modified files are returned
-
-#### TC-036: No New Files
-- **Description:** Test when no new files exist
-- **Input:** Directory with all files processed
-- **Expected Output:** Empty list `[]`
-
----
-
-### 14. `find_deleted_files() -> List[str]`
-
-**Function Description:** Finds files that have been deleted.
-
-**Test Cases:**
-
-#### TC-037: Deleted Files Found
-- **Description:** Test finding deleted files
-- **Input:** processed_files.json with entries for deleted files
-- **Expected Output:** List of deleted file paths
-- **Test Steps:**
- 1. Create processed_files.json with file entries
- 2. Delete corresponding files
- 3. Call find_deleted_files
- 4. Verify deleted files are returned
-
-#### TC-038: No Deleted Files
-- **Description:** Test when no files are deleted
-- **Input:** All processed files still exist
-- **Expected Output:** Empty list `[]`
-
----
-
-### 15. `cleanup_deleted_files(deleted_files: List[str]) -> None`
-
-**Function Description:** Removes metadata for deleted files.
-
-**Test Cases:**
-
-#### TC-039: Cleanup Deleted Files
-- **Description:** Test cleanup of deleted files metadata
-- **Input:** List of deleted file paths
-- **Expected Output:** Metadata removed from chunks.json
-- **Test Steps:**
- 1. Create chunks.json with file metadata
- 2. Call cleanup_deleted_files
- 3. Verify metadata is removed
- 4. Verify processed_files.json is updated
-
----
-
-### 16. `process_single_file(file_path: Path) -> List[Dict]`
-
-**Function Description:** Processes a single XML file and returns chunks.
-
-**Test Cases:**
-
-#### TC-040: Valid File Processing
-- **Description:** Test processing of valid XML file
-- **Input:** Path to valid XML file
-- **Expected Output:** List of chunks with metadata
-- **Test Steps:**
- 1. Create valid XML file
- 2. Call process_single_file
- 3. Verify chunks are created
- 4. Verify metadata is correct
-
-#### TC-041: Root File Processing
-- **Description:** Test processing of root-level file
-- **Input:** Path to root-level XML file
-- **Expected Output:** Empty list `[]`
-- **Test Steps:**
- 1. Create XML file at root level
- 2. Call process_single_file
- 3. Verify empty list is returned
- 4. Verify appropriate log message
-
-#### TC-042: File Processing Error
-- **Description:** Test handling of file processing errors
-- **Input:** Path to malformed XML file
-- **Expected Output:** Empty list `[]`
-- **Test Steps:**
- 1. Create malformed XML file
- 2. Call process_single_file
- 3. Verify empty list is returned
- 4. Verify error is logged
-
----
-
-### 17. `update_chunks_database(new_chunks: List[Dict], file_path: Path) -> None`
-
-**Function Description:** Updates chunks database with new chunks.
-
-**Test Cases:**
-
-#### TC-043: Add New Chunks
-- **Description:** Test adding new chunks to database
-- **Input:** New chunks and file path
-- **Expected Output:** Database updated with new chunks
-- **Test Steps:**
- 1. Create new chunks
- 2. Call update_chunks_database
- 3. Verify chunks are added
- 4. Verify processed_files.json is updated
-
-#### TC-044: Replace Existing Chunks
-- **Description:** Test replacing existing chunks for same file
-- **Input:** New chunks for already processed file
-- **Expected Output:** Old chunks replaced with new ones
-- **Test Steps:**
- 1. Add chunks for existing file
- 2. Call update_chunks_database with new chunks
- 3. Verify old chunks are removed
- 4. Verify new chunks are added
-
----
-
-### 18. `process_file_incrementally(file_path: str) -> List[Dict]`
-
-**Function Description:** Processes a single file incrementally.
-
-**Test Cases:**
-
-#### TC-045: New File Processing
-- **Description:** Test processing of new file
-- **Input:** Path to new XML file
-- **Expected Output:** List of chunks and database updated
-- **Test Steps:**
- 1. Create new XML file
- 2. Call process_file_incrementally
- 3. Verify chunks are created
- 4. Verify database is updated
-
-#### TC-046: Already Processed File
-- **Description:** Test processing of already processed file
-- **Input:** Path to already processed file
-- **Expected Output:** Empty list `[]`
-- **Test Steps:**
- 1. Process file once
- 2. Call process_file_incrementally again
- 3. Verify empty list is returned
- 4. Verify appropriate log message
-
-#### TC-047: Modified File Processing
-- **Description:** Test processing of modified file
-- **Input:** Path to modified XML file
-- **Expected Output:** New chunks and database updated
-- **Test Steps:**
- 1. Process file once
- 2. Modify file content
- 3. Call process_file_incrementally
- 4. Verify new chunks are created
- 5. Verify database is updated
-
----
-
-## Module: `app/core/build_faiss.py` (Base Embedding)
-
-### 19. `setup_model(model_name) -> Tuple`
-
-**Function Description:** Sets up model configuration and tokenizer.
-
-**Test Cases:
-
-#### TC-048: Default Model Setup
-- **Description:** Test setup with default model
-- **Input:** `model_name=None`
-- **Expected Output:** Tuple of (encoding, model_config)
-- **Test Steps:**
- 1. Call setup_model with None
- 2. Verify encoding is created
- 3. Verify model_config is loaded
-
-#### TC-049: Custom Model Setup
-- **Description:** Test setup with custom model
-- **Input:** `model_name="text-embedding-3-small"`
-- **Expected Output:** Tuple of (encoding, model_config)
-- **Test Steps:**
- 1. Call setup_model with custom model
- 2. Verify encoding matches model
- 3. Verify model_config is correct
-
-#### TC-050: Invalid Model
-- **Description:** Test setup with invalid model
-- **Input:** `model_name="invalid-model"`
-- **Expected Output:** ValueError
-- **Test Steps:**
- 1. Call setup_model with invalid model
- 2. Verify ValueError is raised
-
----
-
-### 20. `count_tokens(text, encoding) -> int`
-
-**Function Description:** Counts tokens in text using specified encoding.
-
-**Test Cases:**
-
-#### TC-051: Normal Text Token Count
-- **Description:** Test token counting for normal text
-- **Input:** `text="Hello world", encoding=cl100k_base`
-- **Expected Output:** Integer token count
-- **Test Steps:**
- 1. Create test text
- 2. Call count_tokens
- 3. Verify token count is reasonable
-
-#### TC-052: Empty Text
-- **Description:** Test token counting for empty text
-- **Input:** `text="", encoding=cl100k_base`
-- **Expected Output:** `0`
-
-#### TC-053: Long Text
-- **Description:** Test token counting for long text
-- **Input:** Long text string
-- **Expected Output:** Large integer token count
-
----
-
-### 21. `split_into_chunks(text, max_tokens, encoding) -> List[str]`
-
-**Function Description:** Splits long text into smaller chunks by sentence.
-
-**Test Cases:**
-
-#### TC-054: Text Below Limit
-- **Description:** Test text below token limit
-- **Input:** Short text below max_tokens
-- **Expected Output:** List with single chunk
-- **Test Steps:**
- 1. Create short text
- 2. Call split_into_chunks
- 3. Verify single chunk is returned
-
-#### TC-055: Text Above Limit
-- **Description:** Test text above token limit
-- **Input:** Long text above max_tokens
-- **Expected Output:** List with multiple chunks
-- **Test Steps:**
- 1. Create long text
- 2. Call split_into_chunks
- 3. Verify multiple chunks are returned
- 4. Verify each chunk is within limit
-
-#### TC-056: Sentence Boundary Splitting
-- **Description:** Test splitting at sentence boundaries
-- **Input:** Text with multiple sentences
-- **Expected Output:** Chunks split at sentence boundaries
-- **Test Steps:**
- 1. Create text with multiple sentences
- 2. Call split_into_chunks
- 3. Verify chunks end at sentence boundaries
-
----
-
-### 22. `get_openai_embeddings(texts, model, encoding) -> Tuple`
-
-**Function Description:** Generates embeddings for text chunks with batching.
-
-**Test Cases:**
-
-#### TC-057: Single Batch Processing
-- **Description:** Test processing of small text set
-- **Input:** Small list of texts
-- **Expected Output:** Tuple of (embeddings, total_tokens)
-- **Test Steps:**
- 1. Create small text list
- 2. Call get_openai_embeddings
- 3. Verify embeddings are generated
- 4. Verify token count is correct
-
-#### TC-058: Multiple Batch Processing
-- **Description:** Test processing of large text set requiring multiple batches
-- **Input:** Large list of texts
-- **Expected Output:** Tuple of (embeddings, total_tokens)
-- **Test Steps:**
- 1. Create large text list
- 2. Call get_openai_embeddings
- 3. Verify all batches are processed
- 4. Verify all embeddings are generated
-
-#### TC-059: Long Chunk Splitting
-- **Description:** Test splitting of long chunks during processing
-- **Input:** Texts with some exceeding token limit
-- **Expected Output:** Embeddings for all chunks including split ones
-- **Test Steps:**
- 1. Create texts with some long chunks
- 2. Call get_openai_embeddings
- 3. Verify long chunks are split
- 4. Verify embeddings for all chunks
-
-#### TC-060: API Error Handling
-- **Description:** Test handling of OpenAI API errors
-- **Input:** Valid texts with mocked API failure
-- **Expected Output:** Exception or error handling
-- **Test Steps:**
- 1. Mock OpenAI API to fail
- 2. Call get_openai_embeddings
- 3. Verify error is handled appropriately
-
----
-
-## Module: `app/core/incremental_faiss.py` (Incremental Embedding)
-
-### 23. `IncrementalFAISS.__init__(model)`
-
-**Function Description:** Initializes incremental FAISS updater.
-
-**Test Cases:**
-
-#### TC-061: Default Model Initialization
-- **Description:** Test initialization with default model
-- **Input:** `model=None`
-- **Expected Output:** IncrementalFAISS instance with default model
-- **Test Steps:**
- 1. Call __init__ with None
- 2. Verify default model is used
- 3. Verify OpenAI client is initialized
- 4. Verify tokenizer is set up
-
-#### TC-062: Custom Model Initialization
-- **Description:** Test initialization with custom model
-- **Input:** `model="text-embedding-3-small"`
-- **Expected Output:** IncrementalFAISS instance with custom model
-- **Test Steps:**
- 1. Call __init__ with custom model
- 2. Verify custom model is used
- 3. Verify configuration is loaded
-
-#### TC-063: Missing API Key
-- **Description:** Test initialization without API key
-- **Input:** No OPENAI_API_KEY environment variable
-- **Expected Output:** ValueError
-- **Test Steps:**
- 1. Remove OPENAI_API_KEY from environment
- 2. Call __init__
- 3. Verify ValueError is raised
-
----
-
-### 24. `count_tokens(text: str) -> int`
-
-**Function Description:** Counts tokens in text using instance encoding.
-
-**Test Cases:**
-
-#### TC-064: Token Counting
-- **Description:** Test token counting with instance encoding
-- **Input:** `text="Test text for token counting"`
-- **Expected Output:** Integer token count
-- **Test Steps:**
- 1. Create test text
- 2. Call count_tokens
- 3. Verify token count is correct
-
----
-
-### 25. `split_into_chunks(text: str, max_tokens: int) -> List[str]`
-
-**Function Description:** Splits text into chunks using instance encoding.
-
-**Test Cases:**
-
-#### TC-065: Text Splitting
-- **Description:** Test text splitting with instance encoding
-- **Input:** Long text and max_tokens limit
-- **Expected Output:** List of text chunks
-- **Test Steps:**
- 1. Create long text
- 2. Call split_into_chunks
- 3. Verify chunks are within limit
- 4. Verify sentence boundaries are respected
-
----
-
-### 26. `get_embeddings_for_chunks(chunks: List[str], model: str) -> List[List[float]]`
-
-**Function Description:** Generates embeddings for text chunks with robust error handling.
-
-**Test Cases:**
-
-#### TC-066: Successful Embedding Generation
-- **Description:** Test successful embedding generation
-- **Input:** List of valid text chunks
-- **Expected Output:** List of embedding vectors
-- **Test Steps:**
- 1. Create valid text chunks
- 2. Call get_embeddings_for_chunks
- 3. Verify embeddings are generated
- 4. Verify all chunks are processed
-
-#### TC-067: Rate Limit Handling
-- **Description:** Test handling of rate limit errors
-- **Input:** Text chunks with mocked rate limit
-- **Expected Output:** Embeddings after retry
-- **Test Steps:**
- 1. Mock rate limit error
- 2. Call get_embeddings_for_chunks
- 3. Verify exponential backoff is used
- 4. Verify embeddings are eventually generated
-
-#### TC-068: Large Batch Splitting
-- **Description:** Test splitting of large batches
-- **Input:** Large batch exceeding token limit
-- **Expected Output:** Embeddings for all chunks
-- **Test Steps:**
- 1. Create large batch
- 2. Call get_embeddings_for_chunks
- 3. Verify batch is split
- 4. Verify all chunks are processed
-
-#### TC-069: Empty Chunks List
-- **Description:** Test handling of empty chunks list
-- **Input:** Empty list `[]`
-- **Expected Output:** Empty list `[]`
-
-#### TC-070: Failed Embedding Generation
-- **Description:** Test handling of embedding generation failure
-- **Input:** Text chunks with mocked API failure
-- **Expected Output:** `None`
-- **Test Steps:**
- 1. Mock API to fail consistently
- 2. Call get_embeddings_for_chunks
- 3. Verify None is returned
- 4. Verify error is logged
-
----
-
-### 27. `load_existing_index() -> Optional[faiss.Index]`
-
-**Function Description:** Loads existing FAISS index if it exists.
-
-**Test Cases:**
-
-#### TC-071: Existing Index Load
-- **Description:** Test loading existing index
-- **Input:** Existing FAISS index file
-- **Expected Output:** FAISS index object
-- **Test Steps:**
- 1. Create FAISS index file
- 2. Call load_existing_index
- 3. Verify index is loaded
-
-#### TC-072: No Existing Index
-- **Description:** Test when no index exists
-- **Input:** No FAISS index file
-- **Expected Output:** `None`
-- **Test Steps:**
- 1. Ensure no index file exists
- 2. Call load_existing_index
- 3. Verify None is returned
-
----
-
-### 28. `load_existing_metadata() -> List[Dict]`
-
-**Function Description:** Loads existing metadata if it exists.
-
-**Test Cases:**
-
-#### TC-073: Existing Metadata Load
-- **Description:** Test loading existing metadata
-- **Input:** Existing metadata file
-- **Expected Output:** List of metadata dictionaries
-- **Test Steps:**
- 1. Create metadata file
- 2. Call load_existing_metadata
- 3. Verify metadata is loaded
-
-#### TC-074: No Existing Metadata
-- **Description:** Test when no metadata exists
-- **Input:** No metadata file
-- **Expected Output:** Empty list `[]`
-- **Test Steps:**
- 1. Ensure no metadata file exists
- 2. Call load_existing_metadata
- 3. Verify empty list is returned
-
----
-
-### 29. `create_new_index(dimension: int) -> faiss.Index`
-
-**Function Description:** Creates a new FAISS index.
-
-**Test Cases:**
-
-#### TC-075: New Index Creation
-- **Description:** Test creation of new index
-- **Input:** `dimension=1536`
-- **Expected Output:** FAISS index with specified dimension
-- **Test Steps:**
- 1. Call create_new_index
- 2. Verify index is created
- 3. Verify dimension is correct
-
----
-
-### 30. `update_index_with_new_chunks(new_chunks: List[Dict]) -> int`
-
-**Function Description:** Updates FAISS index with new chunks.
-
-**Test Cases:**
-
-#### TC-076: New Chunks Addition
-- **Description:** Test adding new chunks to index
-- **Input:** List of new chunk dictionaries
-- **Expected Output:** Number of embeddings added
-- **Test Steps:**
- 1. Create new chunks
- 2. Call update_index_with_new_chunks
- 3. Verify embeddings are generated
- 4. Verify index is updated
- 5. Verify metadata is updated
-
-#### TC-077: Empty Chunks List
-- **Description:** Test with empty chunks list
-- **Input:** Empty list `[]`
-- **Expected Output:** `0`
-
-#### TC-078: Long Chunk Processing
-- **Description:** Test processing of chunks that need splitting
-- **Input:** Chunks with long text
-- **Expected Output:** Embeddings for all processed chunks
-- **Test Steps:**
- 1. Create chunks with long text
- 2. Call update_index_with_new_chunks
- 3. Verify long chunks are split
- 4. Verify all chunks are processed
-
-#### TC-079: Embedding Generation Failure
-- **Description:** Test handling of embedding generation failure
-- **Input:** Chunks that cause embedding failure
-- **Expected Output:** `0`
-- **Test Steps:**
- 1. Mock embedding generation to fail
- 2. Call update_index_with_new_chunks
- 3. Verify 0 is returned
- 4. Verify error is logged
-
----
-
-### 31. `update_metadata_with_new_chunks(new_chunks: List[Dict]) -> None`
-
-**Function Description:** Updates metadata file with new chunks.
-
-**Test Cases:**
-
-#### TC-080: Metadata Update
-- **Description:** Test updating metadata with new chunks
-- **Input:** List of new chunk dictionaries
-- **Expected Output:** Metadata file updated
-- **Test Steps:**
- 1. Create new chunks
- 2. Call update_metadata_with_new_chunks
- 3. Verify metadata file is updated
- 4. Verify all chunks are included
-
-#### TC-081: Chunk Splitting Consistency
-- **Description:** Test consistency between embedding and metadata chunk splitting
-- **Input:** Chunks that need splitting
-- **Expected Output:** Metadata matches embedding chunks
-- **Test Steps:**
- 1. Create chunks that need splitting
- 2. Call update_metadata_with_new_chunks
- 3. Verify splitting logic matches embedding logic
-
----
-
-### 32. `process_incremental_update(new_chunks: List[Dict]) -> Dict`
-
-**Function Description:** Processes incremental update with new chunks.
-
-**Test Cases:**
-
-#### TC-082: Successful Incremental Update
-- **Description:** Test successful incremental update
-- **Input:** List of new chunk dictionaries
-- **Expected Output:** Dictionary with update statistics
-- **Expected Statistics:**
- - new_chunks_processed
- - new_embeddings_added
- - total_tokens
- - estimated_cost
- - model_used
- - model_pricing
-
-#### TC-083: Empty Update
-- **Description:** Test update with empty chunks list
-- **Input:** Empty list `[]`
-- **Expected Output:** Dictionary with zero statistics
-- **Test Steps:**
- 1. Call process_incremental_update with empty list
- 2. Verify all statistics are zero
- 3. Verify cost calculation is correct
-
-#### TC-084: Cost Calculation
-- **Description:** Test accurate cost calculation
-- **Input:** Chunks with known token count
-- **Expected Output:** Accurate cost estimate
-- **Test Steps:**
- 1. Create chunks with known token count
- 2. Call process_incremental_update
- 3. Verify cost calculation is accurate
-
----
-
-## Integration Test Cases
-
-### TC-085: Complete Incremental Pipeline
-**Description:** Test complete incremental processing pipeline
-**Test Steps:**
-1. Initialize IncrementalPipeline
-2. Add new XML file
-3. Run process_single_file
-4. Verify chunks are created
-5. Verify embeddings are generated
-6. Verify FAISS index is updated
-7. Verify metadata is updated
-
-### TC-086: File Modification Handling
-**Description:** Test handling of file modifications
-**Test Steps:**
-1. Process file initially
-2. Modify file content
-3. Run incremental processing
-4. Verify old chunks are removed
-5. Verify new chunks are created
-6. Verify index is updated correctly
-
-### TC-087: Error Recovery
-**Description:** Test error recovery in pipeline
-**Test Steps:**
-1. Introduce errors at various points
-2. Run incremental processing
-3. Verify partial results are preserved
-4. Verify errors are handled gracefully
-5. Verify system remains consistent
-
-### TC-088: Performance Testing
-**Description:** Test performance with large datasets
-**Test Steps:**
-1. Use large number of chunks
-2. Measure processing time
-3. Verify memory usage is reasonable
-4. Verify system remains responsive
-
----
-
-## Test Data Requirements
-
-### Sample XML Structure
-```xml
-
-
-
- 2024-06431
- 2024-03-28
- Rule
- Medicare Program; Calendar Year (CY) 2025 Home Health Prospective Payment System Rate Update
-
-
- Centers for Medicare & Medicaid Services (CMS), HHS.
- Final rule.
-
-
- Supplementary Information:
- I. Executive Summary
- This is the first paragraph of the executive summary.
- This is the second paragraph with more content.
- A. Background
- Background information goes here.
-
-
-
-
-```
-
-### Sample Chunk Structure
-```json
-{
- "text": "This is the first paragraph of the executive summary. This is the second paragraph with more content.",
- "section_header": "I. Executive Summary",
- "chunk_index": 0,
- "hash": "abc123...",
- "metadata": {
- "source_file": "2025_MPFS_final_2024-06431.xml",
- "program": "MPFS",
- "rule_type": "Final",
- "year": 2025,
- "title": "Medicare Program; Calendar Year (CY) 2025 Home Health Prospective Payment System Rate Update",
- "subfolder": "MPFS",
- "full_path": "/path/to/file.xml"
- }
-}
-```
-
-### Sample Embedding Response
-```json
-{
- "data": [
- {
- "embedding": [0.1, 0.2, 0.3, ...],
- "index": 0,
- "object": "embedding"
- }
- ],
- "model": "text-embedding-3-small",
- "object": "list",
- "usage": {
- "prompt_tokens": 100,
- "total_tokens": 100
- }
-}
-```
-
----
-
-## Notes
-
-1. **Mocking Strategy:** All external API calls should be mocked for reliable testing
-2. **File System Operations:** Use temporary directories for file system tests
-3. **Error Simulation:** Test various error conditions including network failures and invalid data
-4. **Performance Monitoring:** Track processing time and resource usage for large datasets
-5. **Consistency Verification:** Ensure chunk splitting logic is consistent between components
-6. **Cost Tracking:** Verify accurate cost calculation and tracking
-7. **State Management:** Test proper state management during incremental updates
\ No newline at end of file
diff --git a/test_token_count.py b/test_token_count.py
deleted file mode 100644
index 50a5abd2..00000000
--- a/test_token_count.py
+++ /dev/null
@@ -1,64 +0,0 @@
-#!/usr/bin/env python3
-
-import json
-import sys
-import os
-
-# Add the current directory to path to import our modules
-sys.path.append(os.path.dirname(os.path.abspath(__file__)))
-
-try:
- from incremental_faiss import IncrementalFAISS
-except ImportError:
- print("Error: Could not import IncrementalFAISS")
- sys.exit(1)
-
-def test_token_counting():
- """Test token counting with actual data"""
- print("=== Token Counting Test ===")
-
- # Initialize the FAISS updater
- faiss_updater = IncrementalFAISS()
-
- # Load chunks
- chunks_file = "../../rag_data/chunks.json"
- with open(chunks_file, "r") as f:
- chunks = json.load(f)
-
- # Find MPFS 2024-14828 chunks
- mpfs_chunks = [c for c in chunks if '2024-14828' in c.get('metadata', {}).get('source_file', '')]
-
- print(f"Found {len(mpfs_chunks)} chunks for MPFS 2024-14828")
-
- if not mpfs_chunks:
- print("No chunks found!")
- return
-
- # Test token counting on first few chunks
- print("\nTesting token counting on first 5 chunks:")
- total_tokens = 0
- for i, chunk in enumerate(mpfs_chunks[:5]):
- text = chunk['text']
- tokens = faiss_updater.count_tokens(text)
- total_tokens += tokens
- print(f"Chunk {i+1}: {len(text)} chars, {tokens} tokens, ratio: {tokens/len(text):.3f}")
-
- print(f"\nFirst 5 chunks total: {total_tokens} tokens")
-
- # Calculate total tokens for all chunks
- print("\nCalculating total tokens for all chunks...")
- all_tokens = sum(faiss_updater.count_tokens(chunk['text']) for chunk in mpfs_chunks)
- print(f"Total tokens for all {len(mpfs_chunks)} chunks: {all_tokens:,}")
-
- # Compare with reported value
- reported_tokens = 14109286
- print(f"Reported tokens: {reported_tokens:,}")
- print(f"Difference: {abs(all_tokens - reported_tokens):,}")
- print(f"Match: {abs(all_tokens - reported_tokens) < 1000}")
-
- # Calculate cost
- estimated_cost = all_tokens / 1000 * 0.0001
- print(f"\nEstimated cost: ${estimated_cost:.4f}")
-
-if __name__ == "__main__":
- test_token_counting()
\ No newline at end of file
diff --git a/token_demo.py b/token_demo.py
deleted file mode 100644
index 114251c1..00000000
--- a/token_demo.py
+++ /dev/null
@@ -1,80 +0,0 @@
-#!/usr/bin/env python3
-"""
-Token获取过程演示
-"""
-
-import json
-import sys
-import os
-
-# 添加当前目录到路径
-sys.path.append(os.path.dirname(os.path.abspath(__file__)))
-
-def demo_token_counting():
- """演示token计数过程"""
- print("=== Token获取过程演示 ===\n")
-
- # 1. 加载chunks数据
- print("1. 加载chunks数据...")
- chunks_file = "../../rag_data/chunks.json"
- with open(chunks_file, "r") as f:
- chunks = json.load(f)
-
- # 2. 找到特定文件的chunks
- print("2. 找到MPFS 2024-14828文件的chunks...")
- mpfs_chunks = [c for c in chunks if '2024-14828' in c.get('metadata', {}).get('source_file', '')]
- print(f" 找到 {len(mpfs_chunks)} 个chunks\n")
-
- # 3. 演示token计数(模拟tiktoken)
- print("3. Token计数过程:")
- print(" - 使用 tiktoken.encoding_for_model('text-embedding-ada-002')")
- print(" - 对每个chunk的text进行encode")
- print(" - 计算encode后的长度作为token数量")
- print()
-
- # 4. 显示前几个chunks的详细信息
- print("4. 前3个chunks的详细信息:")
- total_chars = 0
- for i, chunk in enumerate(mpfs_chunks[:3]):
- text = chunk['text']
- chars = len(text)
- total_chars += chars
- print(f" Chunk {i+1}:")
- print(f" - 字符数: {chars:,}")
- print(f" - 文本预览: {text[:100]}...")
- print()
-
- # 5. 计算总token数
- print("5. 总token数计算:")
- print(" - 总chunks: 699")
- print(" - 总字符数: 70,002,413")
- print(" - 平均每chunk字符: 100,146")
- print(" - 估算token数: 14,109,286")
- print()
-
- # 6. 成本计算
- print("6. 成本计算:")
- tokens = 14109286
- cost = tokens / 1000 * 0.0001
- print(f" - 公式: {tokens:,} / 1000 * 0.0001")
- print(f" - 成本: ${cost:.4f}")
- print()
-
- # 7. 技术细节
- print("7. 技术实现细节:")
- print(" - Tokenizer: tiktoken (OpenAI官方)")
- print(" - 模型: text-embedding-ada-002")
- print(" - 方法: encoding.encode(text) -> len()")
- print(" - 特点: 与OpenAI API完全一致的tokenization")
- print()
-
- print("=== 总结 ===")
- print("Token数量是通过以下步骤获取的:")
- print("1. 从chunks.json加载文本数据")
- print("2. 使用tiktoken对每个chunk的text进行encode")
- print("3. 计算encode后的长度作为token数量")
- print("4. 累加所有chunks的token数量")
- print("5. 根据OpenAI定价计算成本")
-
-if __name__ == "__main__":
- demo_token_counting()
\ No newline at end of file
diff --git a/xml_label_analysis_results.json b/xml_label_analysis_results.json
deleted file mode 100644
index f39dc210..00000000
--- a/xml_label_analysis_results.json
+++ /dev/null
@@ -1,1575 +0,0 @@
-{
- "folder_results": {
- "HOSPICE": {
- "folder_name": "HOSPICE",
- "file_count": 8,
- "file_labels": {
- "2025_HOSPICE_final_2024-16910.xml": [
- "ACT",
- "AGENCY",
- "AGY",
- "AMDPAR",
- "AUTH",
- "BILCOD",
- "CFR",
- "DEPDOC",
- "E",
- "EFFDATE",
- "FP",
- "FRDOC",
- "FTNT",
- "FTREF",
- "FURINF",
- "GID",
- "GPH",
- "HD",
- "LSTSUB",
- "NAME",
- "P",
- "PART",
- "PREAMB",
- "PRTPAGE",
- "REGTEXT",
- "RIN",
- "RULE",
- "SECTION",
- "SECTNO",
- "SIG",
- "STARS",
- "SU",
- "SUBAGY",
- "SUBJECT",
- "SUM",
- "SUPLINF",
- "TITLE"
- ],
- "2026_HOSPICE_proposed_2025-06317.xml": [
- "ACT",
- "ADD",
- "AGENCY",
- "AGY",
- "AMDPAR",
- "AUTH",
- "BILCOD",
- "BOXHD",
- "CFR",
- "CHED",
- "DEPDOC",
- "E",
- "EFFDATE",
- "ENT",
- "FRDOC",
- "FTNT",
- "FTREF",
- "FURINF",
- "GPOTABLE",
- "HD",
- "LI",
- "LSTSUB",
- "NAME",
- "P",
- "PART",
- "PREAMB",
- "PRORULE",
- "PRTPAGE",
- "RIN",
- "ROW",
- "SECTION",
- "SECTNO",
- "SIG",
- "STARS",
- "SU",
- "SUBAGY",
- "SUBJECT",
- "SUM",
- "SUPLINF",
- "TITLE",
- "TNOTE",
- "TTITLE"
- ],
- "2024_HOSPICE_proposed_2023-06769.xml": [
- "ACT",
- "ADD",
- "AGENCY",
- "AGY",
- "AMDPAR",
- "AUTH",
- "BILCOD",
- "BOXHD",
- "CFR",
- "CHED",
- "DATED",
- "DEPDOC",
- "E",
- "EFFDATE",
- "ENT",
- "FP",
- "FRDOC",
- "FTNT",
- "FTREF",
- "FURINF",
- "GID",
- "GPH",
- "GPOTABLE",
- "HD",
- "LI",
- "LSTSUB",
- "NAME",
- "P",
- "PART",
- "PREAMB",
- "PRORULE",
- "PRTPAGE",
- "RIN",
- "ROW",
- "SECTION",
- "SECTNO",
- "SIG",
- "STARS",
- "SU",
- "SUBAGY",
- "SUBJECT",
- "SUM",
- "SUPLINF",
- "TDESC",
- "TITLE",
- "TNOTE",
- "TTITLE"
- ],
- "2025_HOSPICE_proposed_2024-06921.xml": [
- "ACT",
- "ADD",
- "AGENCY",
- "AGY",
- "AMDPAR",
- "AUTH",
- "BILCOD",
- "BOXHD",
- "CFR",
- "CHED",
- "DATES",
- "DEPDOC",
- "E",
- "ENT",
- "FRDOC",
- "FTNT",
- "FTREF",
- "FURINF",
- "GID",
- "GPH",
- "GPOTABLE",
- "HD",
- "LSTSUB",
- "NAME",
- "P",
- "PART",
- "PREAMB",
- "PRORULE",
- "PRTPAGE",
- "RIN",
- "ROW",
- "SECTION",
- "SECTNO",
- "SIG",
- "STARS",
- "SU",
- "SUBAGY",
- "SUBJECT",
- "SUM",
- "SUPLINF",
- "TITLE",
- "TTITLE"
- ],
- "2023_HOSPICE_proposed_2022-07030.xml": [
- "ACT",
- "ADD",
- "AGENCY",
- "AGY",
- "AMDPAR",
- "AUTH",
- "BILCOD",
- "BOXHD",
- "CFR",
- "CHED",
- "DATED",
- "DEPDOC",
- "E",
- "EFFDATE",
- "ENT",
- "FRDOC",
- "FTNT",
- "FTREF",
- "FURINF",
- "GPOTABLE",
- "HD",
- "LI",
- "LSTSUB",
- "NAME",
- "P",
- "PART",
- "PREAMB",
- "PRORULE",
- "PRTPAGE",
- "RIN",
- "ROW",
- "SECTION",
- "SECTNO",
- "SIG",
- "STARS",
- "SU",
- "SUBAGY",
- "SUBJECT",
- "SUM",
- "SUPLINF",
- "TITLE",
- "TNOTE",
- "TTITLE"
- ],
- "2024_HOSPICE_final_2023-16116.xml": [
- "ACT",
- "AGENCY",
- "AGY",
- "AMDPAR",
- "AUTH",
- "BILCOD",
- "CFR",
- "DEPDOC",
- "E",
- "EFFDATE",
- "FP",
- "FRDOC",
- "FTNT",
- "FTREF",
- "FURINF",
- "GID",
- "GPH",
- "HD",
- "LSTSUB",
- "NAME",
- "P",
- "PART",
- "PREAMB",
- "PRTPAGE",
- "REGTEXT",
- "RIN",
- "RULE",
- "SECTION",
- "SECTNO",
- "SIG",
- "STARS",
- "SU",
- "SUBAGY",
- "SUBJECT",
- "SUM",
- "SUPLINF",
- "TITLE"
- ],
- "2022_HOSPICE_final_2021-16311.xml": [
- "ACT",
- "AGENCY",
- "AGY",
- "AMDPAR",
- "AUTH",
- "BILCOD",
- "CFR",
- "DATED",
- "DEPDOC",
- "E",
- "EFFDATE",
- "FP",
- "FR",
- "FRDOC",
- "FTNT",
- "FTREF",
- "FURINF",
- "GID",
- "GPH",
- "HD",
- "LSTSUB",
- "NAME",
- "P",
- "PART",
- "PREAMB",
- "PRTPAGE",
- "REGTEXT",
- "RIN",
- "RULE",
- "SECTION",
- "SECTNO",
- "SIG",
- "STARS",
- "SU",
- "SUBAGY",
- "SUBJECT",
- "SUM",
- "SUPLINF",
- "TITLE"
- ],
- "2023_HOSPICE_final_2022-16214.xml": [
- "ACT",
- "AGENCY",
- "AGY",
- "AMDPAR",
- "AUTH",
- "BILCOD",
- "CFR",
- "DATED",
- "DEPDOC",
- "E",
- "EFFDATE",
- "FRDOC",
- "FTNT",
- "FTREF",
- "FURINF",
- "GID",
- "GPH",
- "HD",
- "LSTSUB",
- "NAME",
- "P",
- "PREAMB",
- "PRTPAGE",
- "REGTEXT",
- "RIN",
- "RULE",
- "SECTION",
- "SECTNO",
- "SIG",
- "STARS",
- "SU",
- "SUBAGY",
- "SUBJECT",
- "SUBPART",
- "SUM",
- "SUPLINF",
- "TITLE"
- ]
- },
- "all_labels": [
- "ACT",
- "ADD",
- "AGENCY",
- "AGY",
- "AMDPAR",
- "AUTH",
- "BILCOD",
- "BOXHD",
- "CFR",
- "CHED",
- "DATED",
- "DATES",
- "DEPDOC",
- "E",
- "EFFDATE",
- "ENT",
- "FP",
- "FR",
- "FRDOC",
- "FTNT",
- "FTREF",
- "FURINF",
- "GID",
- "GPH",
- "GPOTABLE",
- "HD",
- "LI",
- "LSTSUB",
- "NAME",
- "P",
- "PART",
- "PREAMB",
- "PRORULE",
- "PRTPAGE",
- "REGTEXT",
- "RIN",
- "ROW",
- "RULE",
- "SECTION",
- "SECTNO",
- "SIG",
- "STARS",
- "SU",
- "SUBAGY",
- "SUBJECT",
- "SUBPART",
- "SUM",
- "SUPLINF",
- "TDESC",
- "TITLE",
- "TNOTE",
- "TTITLE"
- ],
- "common_labels": [
- "ACT",
- "AGENCY",
- "AGY",
- "AMDPAR",
- "AUTH",
- "BILCOD",
- "CFR",
- "DEPDOC",
- "E",
- "FRDOC",
- "FTNT",
- "FTREF",
- "FURINF",
- "HD",
- "LSTSUB",
- "NAME",
- "P",
- "PREAMB",
- "PRTPAGE",
- "RIN",
- "SECTION",
- "SECTNO",
- "SIG",
- "STARS",
- "SU",
- "SUBAGY",
- "SUBJECT",
- "SUM",
- "SUPLINF",
- "TITLE"
- ],
- "unique_labels_per_file": {
- "2025_HOSPICE_final_2024-16910.xml": 37,
- "2026_HOSPICE_proposed_2025-06317.xml": 42,
- "2024_HOSPICE_proposed_2023-06769.xml": 47,
- "2025_HOSPICE_proposed_2024-06921.xml": 42,
- "2023_HOSPICE_proposed_2022-07030.xml": 43,
- "2024_HOSPICE_final_2023-16116.xml": 37,
- "2022_HOSPICE_final_2021-16311.xml": 39,
- "2023_HOSPICE_final_2022-16214.xml": 37
- }
- },
- "MPFS": {
- "folder_name": "MPFS",
- "file_count": 8,
- "file_labels": {
- "2025_MPFS_proposed_2024-14828.xml": [
- "ACT",
- "ADD",
- "AGENCY",
- "AGY",
- "AMDPAR",
- "AUTH",
- "BILCOD",
- "CFR",
- "CONTENTS",
- "DEPDOC",
- "E",
- "EFFDATE",
- "EXTRACT",
- "FP",
- "FR",
- "FRDOC",
- "FTNT",
- "FTREF",
- "FURINF",
- "GID",
- "GPH",
- "HD",
- "LSTSUB",
- "NAME",
- "NOTE",
- "P",
- "PART",
- "PREAMB",
- "PRORULE",
- "PRTPAGE",
- "RIN",
- "SECHD",
- "SECTION",
- "SECTNO",
- "SIG",
- "STARS",
- "SU",
- "SUBAGY",
- "SUBJECT",
- "SUBPART",
- "SUM",
- "SUPLINF",
- "TITLE"
- ],
- "2024_MPFS_final_2023-24184.xml": [
- "ACT",
- "AGENCY",
- "AGY",
- "AMDPAR",
- "AUTH",
- "BILCOD",
- "BOXHD",
- "CFR",
- "CHED",
- "DATES",
- "DEPDOC",
- "E",
- "ENT",
- "EXTRACT",
- "FP",
- "FR",
- "FRDOC",
- "FTNT",
- "FTREF",
- "FURINF",
- "GID",
- "GPH",
- "GPOTABLE",
- "HD",
- "LSTSUB",
- "NAME",
- "NOTE",
- "P",
- "PART",
- "PREAMB",
- "PRTPAGE",
- "REGTEXT",
- "RIN",
- "ROW",
- "RULE",
- "SECTION",
- "SECTNO",
- "SIG",
- "STARS",
- "SU",
- "SUBAGY",
- "SUBJECT",
- "SUM",
- "SUPLINF",
- "TITLE",
- "TTITLE"
- ],
- "2025_MPFS_final_2024-25382.xml": [
- "ACT",
- "AGENCY",
- "AGY",
- "AMDPAR",
- "AUTH",
- "BILCOD",
- "CFR",
- "CONTENTS",
- "DATES",
- "DEPDOC",
- "E",
- "EXTRACT",
- "FP",
- "FR",
- "FRDOC",
- "FTNT",
- "FTREF",
- "FURINF",
- "GID",
- "GPH",
- "HD",
- "LSTSUB",
- "NAME",
- "NOTE",
- "P",
- "PART",
- "PREAMB",
- "PRTPAGE",
- "REGTEXT",
- "RIN",
- "RULE",
- "SECHD",
- "SECTION",
- "SECTNO",
- "SIG",
- "STARS",
- "SU",
- "SUBAGY",
- "SUBJECT",
- "SUBPART",
- "SUM",
- "SUPLINF",
- "TITLE"
- ],
- "2022_MPFS_proposed_2021-14973.xml": [
- "ACT",
- "ADD",
- "AGENCY",
- "AGY",
- "AMDPAR",
- "AUTH",
- "BILCOD",
- "CFR",
- "DATED",
- "DEPDOC",
- "E",
- "EFFDATE",
- "EXTRACT",
- "FP",
- "FRDOC",
- "FTNT",
- "FTREF",
- "FURINF",
- "GID",
- "GPH",
- "HD",
- "LSTSUB",
- "NAME",
- "NOTE",
- "P",
- "PART",
- "PREAMB",
- "PRORULE",
- "PRTPAGE",
- "RIN",
- "SECTION",
- "SECTNO",
- "SIG",
- "STARS",
- "SU",
- "SUBAGY",
- "SUBJECT",
- "SUM",
- "SUPLINF",
- "TITLE"
- ],
- "2022_MPFS_final_2021-23972.xml": [
- "ACT",
- "AGENCY",
- "AGY",
- "AMDPAR",
- "AUTH",
- "BILCOD",
- "BOXHD",
- "CFR",
- "CHED",
- "DEPDOC",
- "E",
- "EFFDATE",
- "ENT",
- "EXTRACT",
- "FP",
- "FR",
- "FRDOC",
- "FTNT",
- "FTREF",
- "FURINF",
- "GID",
- "GPH",
- "GPOTABLE",
- "HD",
- "LI",
- "LSTSUB",
- "NAME",
- "NOTE",
- "P",
- "PART",
- "PREAMB",
- "PRTPAGE",
- "REGTEXT",
- "RIN",
- "ROW",
- "RULE",
- "SECTION",
- "SECTNO",
- "SIG",
- "STARS",
- "SU",
- "SUBAGY",
- "SUBJECT",
- "SUM",
- "SUPLINF",
- "TITLE",
- "TTITLE"
- ],
- "2024_MPFS_proposed_2023-14624.xml": [
- "ACT",
- "ADD",
- "AGENCY",
- "AGY",
- "AMDPAR",
- "AUTH",
- "BILCOD",
- "CFR",
- "DEPDOC",
- "E",
- "EFFDATE",
- "EXTRACT",
- "FP",
- "FR",
- "FRDOC",
- "FTNT",
- "FTREF",
- "FURINF",
- "GID",
- "GPH",
- "HD",
- "LSTSUB",
- "NAME",
- "P",
- "PART",
- "PREAMB",
- "PRORULE",
- "PRTPAGE",
- "RIN",
- "SECTION",
- "SECTNO",
- "SIG",
- "STARS",
- "SU",
- "SUBAGY",
- "SUBJECT",
- "SUM",
- "SUPLINF",
- "TITLE"
- ],
- "2023_MPFS_proposed_2022-14562.xml": [
- "ACT",
- "ADD",
- "AGENCY",
- "AGY",
- "AMDPAR",
- "APPENDIX",
- "AUTH",
- "BILCOD",
- "BOXHD",
- "CFR",
- "CHED",
- "DATED",
- "DATES",
- "DEPDOC",
- "E",
- "ENT",
- "EXTRACT",
- "FP",
- "FR",
- "FRDOC",
- "FTNT",
- "FTREF",
- "FURINF",
- "GID",
- "GPH",
- "GPOTABLE",
- "HD",
- "LSTSUB",
- "NAME",
- "NOTE",
- "P",
- "PART",
- "PREAMB",
- "PRORULE",
- "PRTPAGE",
- "RIN",
- "ROW",
- "SECTION",
- "SECTNO",
- "SIG",
- "STARS",
- "SU",
- "SUBAGY",
- "SUBJECT",
- "SUBPART",
- "SUM",
- "SUPLINF",
- "TITLE",
- "TTITLE"
- ],
- "2023_MPFS_final_2022-23873.xml": [
- "ACT",
- "AGENCY",
- "AGY",
- "AMDPAR",
- "AUTH",
- "BILCOD",
- "BOXHD",
- "CFR",
- "CHED",
- "DATED",
- "DATES",
- "DEPDOC",
- "E",
- "ENT",
- "EXTRACT",
- "FP",
- "FR",
- "FRDOC",
- "FTNT",
- "FTREF",
- "FURINF",
- "GID",
- "GPH",
- "GPOTABLE",
- "HD",
- "LSTSUB",
- "NAME",
- "NOTE",
- "P",
- "PART",
- "PREAMB",
- "PRTPAGE",
- "REGTEXT",
- "RIN",
- "ROW",
- "RULE",
- "SECTION",
- "SECTNO",
- "SIG",
- "STARS",
- "SU",
- "SUBAGY",
- "SUBJECT",
- "SUM",
- "SUPLINF",
- "TITLE",
- "TNOTE",
- "TTITLE"
- ]
- },
- "all_labels": [
- "ACT",
- "ADD",
- "AGENCY",
- "AGY",
- "AMDPAR",
- "APPENDIX",
- "AUTH",
- "BILCOD",
- "BOXHD",
- "CFR",
- "CHED",
- "CONTENTS",
- "DATED",
- "DATES",
- "DEPDOC",
- "E",
- "EFFDATE",
- "ENT",
- "EXTRACT",
- "FP",
- "FR",
- "FRDOC",
- "FTNT",
- "FTREF",
- "FURINF",
- "GID",
- "GPH",
- "GPOTABLE",
- "HD",
- "LI",
- "LSTSUB",
- "NAME",
- "NOTE",
- "P",
- "PART",
- "PREAMB",
- "PRORULE",
- "PRTPAGE",
- "REGTEXT",
- "RIN",
- "ROW",
- "RULE",
- "SECHD",
- "SECTION",
- "SECTNO",
- "SIG",
- "STARS",
- "SU",
- "SUBAGY",
- "SUBJECT",
- "SUBPART",
- "SUM",
- "SUPLINF",
- "TITLE",
- "TNOTE",
- "TTITLE"
- ],
- "common_labels": [
- "ACT",
- "AGENCY",
- "AGY",
- "AMDPAR",
- "AUTH",
- "BILCOD",
- "CFR",
- "DEPDOC",
- "E",
- "EXTRACT",
- "FP",
- "FRDOC",
- "FTNT",
- "FTREF",
- "FURINF",
- "GID",
- "GPH",
- "HD",
- "LSTSUB",
- "NAME",
- "P",
- "PART",
- "PREAMB",
- "PRTPAGE",
- "RIN",
- "SECTION",
- "SECTNO",
- "SIG",
- "STARS",
- "SU",
- "SUBAGY",
- "SUBJECT",
- "SUM",
- "SUPLINF",
- "TITLE"
- ],
- "unique_labels_per_file": {
- "2025_MPFS_proposed_2024-14828.xml": 43,
- "2024_MPFS_final_2023-24184.xml": 46,
- "2025_MPFS_final_2024-25382.xml": 43,
- "2022_MPFS_proposed_2021-14973.xml": 40,
- "2022_MPFS_final_2021-23972.xml": 47,
- "2024_MPFS_proposed_2023-14624.xml": 39,
- "2023_MPFS_proposed_2022-14562.xml": 49,
- "2023_MPFS_final_2022-23873.xml": 48
- }
- },
- "SNF": {
- "folder_name": "SNF",
- "file_count": 7,
- "file_labels": {
- "2023_SNF_final_2022-16457.xml": [
- "ACT",
- "AGENCY",
- "AGY",
- "AMDPAR",
- "AUTH",
- "BILCOD",
- "CFR",
- "DEPDOC",
- "E",
- "EFFDATE",
- "EXTRACT",
- "FP",
- "FRDOC",
- "FTNT",
- "FTREF",
- "FURINF",
- "GID",
- "GPH",
- "HD",
- "LSTSUB",
- "NAME",
- "P",
- "PART",
- "PREAMB",
- "PRTPAGE",
- "REGTEXT",
- "RIN",
- "RULE",
- "SECTION",
- "SECTNO",
- "SIG",
- "STARS",
- "SU",
- "SUBAGY",
- "SUBJECT",
- "SUM",
- "SUPLINF",
- "TITLE"
- ],
- "2023_SNF_proposed_2022-07906.xml": [
- "ACT",
- "ADD",
- "AGENCY",
- "AGY",
- "AMDPAR",
- "AUTH",
- "BILCOD",
- "CFR",
- "DATED",
- "DATES",
- "DEPDOC",
- "E",
- "EXTRACT",
- "FP",
- "FRDOC",
- "FTNT",
- "FTREF",
- "FURINF",
- "GID",
- "GPH",
- "HD",
- "LSTSUB",
- "NAME",
- "P",
- "PART",
- "PREAMB",
- "PRORULE",
- "PRTPAGE",
- "RIN",
- "SECTION",
- "SECTNO",
- "SIG",
- "STARS",
- "SU",
- "SUBAGY",
- "SUBJECT",
- "SUM",
- "SUPLINF",
- "TITLE"
- ],
- "2025_SNF_proposed_2024-06812.xml": [
- "ACT",
- "ADD",
- "AGENCY",
- "AGY",
- "AMDPAR",
- "AUTH",
- "BILCOD",
- "BOXHD",
- "CFR",
- "CHED",
- "DEPDOC",
- "E",
- "EFFDATE",
- "ENT",
- "EXTRACT",
- "FP",
- "FRDOC",
- "FTNT",
- "FTREF",
- "FURINF",
- "GPOTABLE",
- "HD",
- "LI",
- "LSTSUB",
- "NAME",
- "P",
- "PART",
- "PREAMB",
- "PRORULE",
- "PRTPAGE",
- "RIN",
- "ROW",
- "SECTION",
- "SECTNO",
- "SIG",
- "STARS",
- "SU",
- "SUBAGY",
- "SUBJECT",
- "SUM",
- "SUPLINF",
- "TDESC",
- "TITLE",
- "TNOTE",
- "TTITLE"
- ],
- "2024_SNF_final_2023-16249.xml": [
- "ACT",
- "AGENCY",
- "AGY",
- "AMDPAR",
- "AUTH",
- "BILCOD",
- "BOXHD",
- "CFR",
- "CHED",
- "DATES",
- "DEPDOC",
- "E",
- "ENT",
- "EXTRACT",
- "FP",
- "FRDOC",
- "FTNT",
- "FTREF",
- "FURINF",
- "GID",
- "GPH",
- "GPOTABLE",
- "HD",
- "LI",
- "LSTSUB",
- "NAME",
- "P",
- "PART",
- "PREAMB",
- "PRTPAGE",
- "REGTEXT",
- "RIN",
- "ROW",
- "RULE",
- "SECTION",
- "SECTNO",
- "SIG",
- "STARS",
- "SU",
- "SUBAGY",
- "SUBJECT",
- "SUM",
- "SUPLINF",
- "TITLE",
- "TNOTE",
- "TTITLE"
- ],
- "2026_SNF_proposed_2025-06348.xml": [
- "ACT",
- "ADD",
- "AGENCY",
- "AGY",
- "AMDPAR",
- "AUTH",
- "BILCOD",
- "BOXHD",
- "CFR",
- "CHED",
- "DEPDOC",
- "E",
- "EFFDATE",
- "ENT",
- "EXTRACT",
- "FP",
- "FRDOC",
- "FTNT",
- "FTREF",
- "FURINF",
- "GPOTABLE",
- "HD",
- "LI",
- "LSTSUB",
- "NAME",
- "P",
- "PART",
- "PREAMB",
- "PRORULE",
- "PRTPAGE",
- "RIN",
- "ROW",
- "SECTION",
- "SECTNO",
- "SIG",
- "STARS",
- "SU",
- "SUBAGY",
- "SUBJECT",
- "SUM",
- "SUPLINF",
- "TITLE",
- "TNOTE",
- "TTITLE"
- ],
- "2024_SNF_proposed_2023-07137.xml": [
- "ACT",
- "ADD",
- "AGENCY",
- "AGY",
- "AMDPAR",
- "AUTH",
- "BILCOD",
- "BOXHD",
- "CFR",
- "CHED",
- "DATED",
- "DEPDOC",
- "E",
- "EFFDATE",
- "ENT",
- "EXTRACT",
- "FP",
- "FRDOC",
- "FTNT",
- "FTREF",
- "FURINF",
- "GID",
- "GPH",
- "GPOTABLE",
- "HD",
- "LI",
- "LSTSUB",
- "NAME",
- "P",
- "PART",
- "PREAMB",
- "PRORULE",
- "PRTPAGE",
- "RIN",
- "ROW",
- "SECTION",
- "SECTNO",
- "SIG",
- "STARS",
- "SU",
- "SUBAGY",
- "SUBJECT",
- "SUM",
- "SUPLINF",
- "TDESC",
- "TITLE",
- "TNOTE",
- "TTITLE"
- ],
- "2025_SNF_final_2024-16907.xml": [
- "ACT",
- "AGENCY",
- "AGY",
- "AMDPAR",
- "AUTH",
- "BILCOD",
- "CFR",
- "DEPDOC",
- "E",
- "EFFDATE",
- "EXTRACT",
- "FP",
- "FRDOC",
- "FTNT",
- "FTREF",
- "FURINF",
- "GID",
- "GPH",
- "HD",
- "LSTSUB",
- "NAME",
- "P",
- "PART",
- "PREAMB",
- "PRTPAGE",
- "REGTEXT",
- "RIN",
- "RULE",
- "SECTION",
- "SECTNO",
- "SIG",
- "STARS",
- "SU",
- "SUBAGY",
- "SUBJECT",
- "SUM",
- "SUPLINF",
- "TITLE"
- ]
- },
- "all_labels": [
- "ACT",
- "ADD",
- "AGENCY",
- "AGY",
- "AMDPAR",
- "AUTH",
- "BILCOD",
- "BOXHD",
- "CFR",
- "CHED",
- "DATED",
- "DATES",
- "DEPDOC",
- "E",
- "EFFDATE",
- "ENT",
- "EXTRACT",
- "FP",
- "FRDOC",
- "FTNT",
- "FTREF",
- "FURINF",
- "GID",
- "GPH",
- "GPOTABLE",
- "HD",
- "LI",
- "LSTSUB",
- "NAME",
- "P",
- "PART",
- "PREAMB",
- "PRORULE",
- "PRTPAGE",
- "REGTEXT",
- "RIN",
- "ROW",
- "RULE",
- "SECTION",
- "SECTNO",
- "SIG",
- "STARS",
- "SU",
- "SUBAGY",
- "SUBJECT",
- "SUM",
- "SUPLINF",
- "TDESC",
- "TITLE",
- "TNOTE",
- "TTITLE"
- ],
- "common_labels": [
- "ACT",
- "AGENCY",
- "AGY",
- "AMDPAR",
- "AUTH",
- "BILCOD",
- "CFR",
- "DEPDOC",
- "E",
- "EXTRACT",
- "FP",
- "FRDOC",
- "FTNT",
- "FTREF",
- "FURINF",
- "HD",
- "LSTSUB",
- "NAME",
- "P",
- "PART",
- "PREAMB",
- "PRTPAGE",
- "RIN",
- "SECTION",
- "SECTNO",
- "SIG",
- "STARS",
- "SU",
- "SUBAGY",
- "SUBJECT",
- "SUM",
- "SUPLINF",
- "TITLE"
- ],
- "unique_labels_per_file": {
- "2023_SNF_final_2022-16457.xml": 38,
- "2023_SNF_proposed_2022-07906.xml": 39,
- "2025_SNF_proposed_2024-06812.xml": 45,
- "2024_SNF_final_2023-16249.xml": 46,
- "2026_SNF_proposed_2025-06348.xml": 44,
- "2024_SNF_proposed_2023-07137.xml": 48,
- "2025_SNF_final_2024-16907.xml": 38
- }
- }
- },
- "all_folder_labels": {
- "HOSPICE": [
- "ACT",
- "ADD",
- "AGENCY",
- "AGY",
- "AMDPAR",
- "AUTH",
- "BILCOD",
- "BOXHD",
- "CFR",
- "CHED",
- "DATED",
- "DATES",
- "DEPDOC",
- "E",
- "EFFDATE",
- "ENT",
- "FP",
- "FR",
- "FRDOC",
- "FTNT",
- "FTREF",
- "FURINF",
- "GID",
- "GPH",
- "GPOTABLE",
- "HD",
- "LI",
- "LSTSUB",
- "NAME",
- "P",
- "PART",
- "PREAMB",
- "PRORULE",
- "PRTPAGE",
- "REGTEXT",
- "RIN",
- "ROW",
- "RULE",
- "SECTION",
- "SECTNO",
- "SIG",
- "STARS",
- "SU",
- "SUBAGY",
- "SUBJECT",
- "SUBPART",
- "SUM",
- "SUPLINF",
- "TDESC",
- "TITLE",
- "TNOTE",
- "TTITLE"
- ],
- "MPFS": [
- "ACT",
- "ADD",
- "AGENCY",
- "AGY",
- "AMDPAR",
- "APPENDIX",
- "AUTH",
- "BILCOD",
- "BOXHD",
- "CFR",
- "CHED",
- "CONTENTS",
- "DATED",
- "DATES",
- "DEPDOC",
- "E",
- "EFFDATE",
- "ENT",
- "EXTRACT",
- "FP",
- "FR",
- "FRDOC",
- "FTNT",
- "FTREF",
- "FURINF",
- "GID",
- "GPH",
- "GPOTABLE",
- "HD",
- "LI",
- "LSTSUB",
- "NAME",
- "NOTE",
- "P",
- "PART",
- "PREAMB",
- "PRORULE",
- "PRTPAGE",
- "REGTEXT",
- "RIN",
- "ROW",
- "RULE",
- "SECHD",
- "SECTION",
- "SECTNO",
- "SIG",
- "STARS",
- "SU",
- "SUBAGY",
- "SUBJECT",
- "SUBPART",
- "SUM",
- "SUPLINF",
- "TITLE",
- "TNOTE",
- "TTITLE"
- ],
- "SNF": [
- "ACT",
- "ADD",
- "AGENCY",
- "AGY",
- "AMDPAR",
- "AUTH",
- "BILCOD",
- "BOXHD",
- "CFR",
- "CHED",
- "DATED",
- "DATES",
- "DEPDOC",
- "E",
- "EFFDATE",
- "ENT",
- "EXTRACT",
- "FP",
- "FRDOC",
- "FTNT",
- "FTREF",
- "FURINF",
- "GID",
- "GPH",
- "GPOTABLE",
- "HD",
- "LI",
- "LSTSUB",
- "NAME",
- "P",
- "PART",
- "PREAMB",
- "PRORULE",
- "PRTPAGE",
- "REGTEXT",
- "RIN",
- "ROW",
- "RULE",
- "SECTION",
- "SECTNO",
- "SIG",
- "STARS",
- "SU",
- "SUBAGY",
- "SUBJECT",
- "SUM",
- "SUPLINF",
- "TDESC",
- "TITLE",
- "TNOTE",
- "TTITLE"
- ]
- },
- "common_across_folders": [
- "ACT",
- "ADD",
- "AGENCY",
- "AGY",
- "AMDPAR",
- "AUTH",
- "BILCOD",
- "BOXHD",
- "CFR",
- "CHED",
- "DATED",
- "DATES",
- "DEPDOC",
- "E",
- "EFFDATE",
- "ENT",
- "FP",
- "FRDOC",
- "FTNT",
- "FTREF",
- "FURINF",
- "GID",
- "GPH",
- "GPOTABLE",
- "HD",
- "LI",
- "LSTSUB",
- "NAME",
- "P",
- "PART",
- "PREAMB",
- "PRORULE",
- "PRTPAGE",
- "REGTEXT",
- "RIN",
- "ROW",
- "RULE",
- "SECTION",
- "SECTNO",
- "SIG",
- "STARS",
- "SU",
- "SUBAGY",
- "SUBJECT",
- "SUM",
- "SUPLINF",
- "TITLE",
- "TNOTE",
- "TTITLE"
- ],
- "unique_per_folder": {
- "HOSPICE": [],
- "MPFS": [
- "APPENDIX",
- "CONTENTS",
- "NOTE",
- "SECHD"
- ],
- "SNF": []
- },
- "consistency_check": {
- "all_folders_have_same_labels": false,
- "label_sets_are_equal": false
- }
-}
\ No newline at end of file