Spaces:
Running
Running
Bohaska
commited on
Commit
·
7392937
1
Parent(s):
a8087d6
update GA resolution scripts to use API
Browse files- ns_ga_resolutions_loose_bge-m3.npy +2 -2
- ns_ga_resolutions_semantic_bge-m3.npy +2 -2
- parsed_ga_resolutions.json +0 -0
- small_scripts/ga_resolutions.json +0 -0
- small_scripts/make_embedding/embedding_ga_resolutions.py +104 -54
- small_scripts/make_embedding/embeddings_manifest.json +804 -0
- small_scripts/parse_ga_resolutions.py +202 -218
ns_ga_resolutions_loose_bge-m3.npy
CHANGED
|
@@ -1,3 +1,3 @@
|
|
| 1 |
version https://git-lfs.github.com/spec/v1
|
| 2 |
-
oid sha256:
|
| 3 |
-
size
|
|
|
|
| 1 |
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:1df930cf80b890d10c3f95f9a4fd520d16a7d3a76726bf871e394345cf6d6e11
|
| 3 |
+
size 3555265
|
ns_ga_resolutions_semantic_bge-m3.npy
CHANGED
|
@@ -1,3 +1,3 @@
|
|
| 1 |
version https://git-lfs.github.com/spec/v1
|
| 2 |
-
oid sha256:
|
| 3 |
-
size
|
|
|
|
| 1 |
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:76872864f28f8812bad14c5e1bcf48bf513dbd760bec937385bf8cfa4cc45de4
|
| 3 |
+
size 1642624
|
parsed_ga_resolutions.json
CHANGED
|
The diff for this file is too large to render.
See raw diff
|
|
|
small_scripts/ga_resolutions.json
DELETED
|
The diff for this file is too large to render.
See raw diff
|
|
|
small_scripts/make_embedding/embedding_ga_resolutions.py
CHANGED
|
@@ -13,85 +13,135 @@ MODEL_PATH = '../../../../Downloads/bge-m3'
|
|
| 13 |
# Path to the input JSON file for GA resolutions.
|
| 14 |
GA_RESOLUTIONS_JSON_PATH = os.path.join(script_dir, '..', '..', 'parsed_ga_resolutions.json')
|
| 15 |
|
| 16 |
-
# Output directory for the generated
|
| 17 |
-
# Assuming output files should go to the parent directory of this script.
|
| 18 |
OUTPUT_DIR = os.path.join(script_dir, '..', '..')
|
| 19 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 20 |
# --- Main Embedding Function ---
|
| 21 |
-
def
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 22 |
print("Initializing BGEM3FlagModel...")
|
| 23 |
try:
|
| 24 |
model = BGEM3FlagModel(MODEL_PATH, use_fp16=True)
|
| 25 |
print("Model loaded.")
|
| 26 |
except Exception as e:
|
| 27 |
print(f"Error loading model from {MODEL_PATH}: {e}")
|
| 28 |
-
print("Please ensure the model is downloaded to the specified path.")
|
| 29 |
return
|
| 30 |
|
| 31 |
-
print(f"
|
| 32 |
try:
|
| 33 |
-
|
| 34 |
-
|
| 35 |
-
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
|
| 39 |
-
print(f"Error decoding JSON from {GA_RESOLUTIONS_JSON_PATH}: {e}")
|
| 40 |
-
return
|
| 41 |
except Exception as e:
|
| 42 |
-
print(f"An
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
# Extract the 'body' of each resolution to be encoded
|
| 46 |
-
resolutions_text = [r['body'] for r in resolutions_data if 'body' in r and r['body'].strip()]
|
| 47 |
-
|
| 48 |
-
if not resolutions_text:
|
| 49 |
-
print("No valid resolution bodies found to encode. Exiting.")
|
| 50 |
return
|
| 51 |
|
| 52 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 53 |
|
| 54 |
-
print("Encoding resolutions (dense, sparse)...") # <--- Updated print statement
|
| 55 |
try:
|
| 56 |
-
embeddings = model.encode(resolutions_text,
|
| 57 |
-
batch_size=8, # Adjust batch_size based on your GPU/CPU memory
|
| 58 |
-
max_length=8192, # Max length of input sequence
|
| 59 |
-
return_dense=True,
|
| 60 |
-
return_sparse=True, # This will return 'lexical_weights' for BGE-M3
|
| 61 |
-
return_colbert_vecs=False) # <--- REMOVED COLBERT GENERATION
|
| 62 |
-
|
| 63 |
# Ensure output directory exists
|
| 64 |
os.makedirs(OUTPUT_DIR, exist_ok=True)
|
| 65 |
|
| 66 |
-
#
|
| 67 |
-
|
| 68 |
-
|
| 69 |
-
np.save(dense_output_path, dense_embeddings)
|
| 70 |
-
print(f"Saved semantic embeddings to {dense_output_path} (Shape: {dense_embeddings.shape})") # Renamed type and file
|
| 71 |
-
|
| 72 |
-
# --- Save Loose (Sparse) Embeddings ---
|
| 73 |
-
# 'lexical_weights' is a list of dictionaries, one for each item in the batch
|
| 74 |
-
sparse_list_of_dicts = embeddings['lexical_weights']
|
| 75 |
-
|
| 76 |
-
# Save this list of sparse dictionaries as a NumPy object array
|
| 77 |
-
sparse_output_path = os.path.join(OUTPUT_DIR, 'ns_ga_resolutions_loose_bge-m3.npy') # Renamed file
|
| 78 |
-
np.save(sparse_output_path, np.array(sparse_list_of_dicts, dtype=object), allow_pickle=True) # allow_pickle is essential for storing Python objects
|
| 79 |
-
print(f"Saved loose embeddings to {sparse_output_path} (Total objects: {len(sparse_list_of_dicts)})") # Renamed type and file
|
| 80 |
|
|
|
|
|
|
|
|
|
|
| 81 |
|
| 82 |
-
#
|
| 83 |
-
|
| 84 |
-
|
| 85 |
-
|
| 86 |
-
# print(f"Saved ColBERT embeddings to {colbert_output_path} (Total objects: {len(colbert_list_of_arrays)})")
|
| 87 |
|
| 88 |
-
print("\nGA Resolution embedding
|
| 89 |
|
| 90 |
except Exception as e:
|
| 91 |
-
print(f"An error occurred
|
| 92 |
-
|
| 93 |
-
traceback.print_exc() # Print full traceback for debugging
|
| 94 |
|
| 95 |
# Call the function to start the embedding process
|
| 96 |
if __name__ == "__main__":
|
| 97 |
-
|
|
|
|
| 13 |
# Path to the input JSON file for GA resolutions.
|
| 14 |
GA_RESOLUTIONS_JSON_PATH = os.path.join(script_dir, '..', '..', 'parsed_ga_resolutions.json')
|
| 15 |
|
| 16 |
+
# Output directory for the generated files.
|
|
|
|
| 17 |
OUTPUT_DIR = os.path.join(script_dir, '..', '..')
|
| 18 |
|
| 19 |
+
# --- Output and Cache File Paths ---
|
| 20 |
+
DENSE_OUTPUT_PATH = os.path.join(OUTPUT_DIR, 'ns_ga_resolutions_semantic_bge-m3.npy')
|
| 21 |
+
SPARSE_OUTPUT_PATH = os.path.join(OUTPUT_DIR, 'ns_ga_resolutions_loose_bge-m3.npy')
|
| 22 |
+
MANIFEST_PATH = os.path.join(script_dir, 'embeddings_manifest.json') # New manifest file
|
| 23 |
+
|
| 24 |
+
|
| 25 |
# --- Main Embedding Function ---
|
| 26 |
+
def encode_ga_resolutions_with_caching():
|
| 27 |
+
# 1. --- Load the source of truth: all resolutions ---
|
| 28 |
+
print(f"Loading all GA resolutions from: {GA_RESOLUTIONS_JSON_PATH}")
|
| 29 |
+
try:
|
| 30 |
+
with open(GA_RESOLUTIONS_JSON_PATH, 'r', encoding='utf-8') as file:
|
| 31 |
+
all_resolutions_data = json.load(file)
|
| 32 |
+
# Filter out resolutions without a valid body
|
| 33 |
+
all_resolutions_data = [
|
| 34 |
+
r for r in all_resolutions_data if 'id' in r and 'body' in r and r['body'].strip()
|
| 35 |
+
]
|
| 36 |
+
except (FileNotFoundError, json.JSONDecodeError, Exception) as e:
|
| 37 |
+
print(f"Fatal Error: Could not load or parse the source resolutions file. Cannot proceed. Error: {e}")
|
| 38 |
+
return
|
| 39 |
+
|
| 40 |
+
# 2. --- Load existing cache (manifest and embeddings) ---
|
| 41 |
+
cached_manifest = {}
|
| 42 |
+
old_dense_embeddings = None
|
| 43 |
+
old_sparse_embeddings = None
|
| 44 |
+
|
| 45 |
+
if os.path.exists(MANIFEST_PATH) and os.path.exists(DENSE_OUTPUT_PATH) and os.path.exists(SPARSE_OUTPUT_PATH):
|
| 46 |
+
print("Found existing cache. Loading manifest and embeddings.")
|
| 47 |
+
try:
|
| 48 |
+
with open(MANIFEST_PATH, 'r', encoding='utf-8') as f:
|
| 49 |
+
cached_manifest = json.load(f)
|
| 50 |
+
# Convert string keys from JSON back to integers if necessary
|
| 51 |
+
cached_manifest = {int(k): v for k, v in cached_manifest.items()}
|
| 52 |
+
|
| 53 |
+
old_dense_embeddings = np.load(DENSE_OUTPUT_PATH)
|
| 54 |
+
old_sparse_embeddings = np.load(SPARSE_OUTPUT_PATH, allow_pickle=True)
|
| 55 |
+
print(f"Successfully loaded cache for {len(cached_manifest)} resolutions.")
|
| 56 |
+
except Exception as e:
|
| 57 |
+
print(f"Warning: Could not load cache files correctly: {e}. Re-embedding all resolutions.")
|
| 58 |
+
cached_manifest = {} # Reset if cache is corrupt
|
| 59 |
+
else:
|
| 60 |
+
print("No existing cache found. Will generate embeddings for all resolutions.")
|
| 61 |
+
|
| 62 |
+
# 3. --- Identify new resolutions to be encoded ---
|
| 63 |
+
all_res_ids = {r['id'] for r in all_resolutions_data}
|
| 64 |
+
cached_res_ids = set(cached_manifest.keys())
|
| 65 |
+
new_res_ids = all_res_ids - cached_res_ids
|
| 66 |
+
|
| 67 |
+
if not new_res_ids:
|
| 68 |
+
print("All resolutions are already embedded. Nothing to do. Exiting.")
|
| 69 |
+
return
|
| 70 |
+
|
| 71 |
+
print(f"Found {len(new_res_ids)} new resolutions to embed.")
|
| 72 |
+
resolutions_to_encode = [r for r in all_resolutions_data if r['id'] in new_res_ids]
|
| 73 |
+
# Sort by ID to ensure a consistent order
|
| 74 |
+
resolutions_to_encode.sort(key=lambda x: x['id'])
|
| 75 |
+
|
| 76 |
+
new_texts = [r['body'] for r in resolutions_to_encode]
|
| 77 |
+
|
| 78 |
+
# 4. --- Initialize model and encode ONLY the new data ---
|
| 79 |
print("Initializing BGEM3FlagModel...")
|
| 80 |
try:
|
| 81 |
model = BGEM3FlagModel(MODEL_PATH, use_fp16=True)
|
| 82 |
print("Model loaded.")
|
| 83 |
except Exception as e:
|
| 84 |
print(f"Error loading model from {MODEL_PATH}: {e}")
|
|
|
|
| 85 |
return
|
| 86 |
|
| 87 |
+
print(f"Encoding {len(new_texts)} new resolutions (dense, sparse)...")
|
| 88 |
try:
|
| 89 |
+
new_embeddings = model.encode(new_texts,
|
| 90 |
+
batch_size=8,
|
| 91 |
+
max_length=8192,
|
| 92 |
+
return_dense=True,
|
| 93 |
+
return_sparse=True,
|
| 94 |
+
return_colbert_vecs=False)
|
|
|
|
|
|
|
| 95 |
except Exception as e:
|
| 96 |
+
print(f"An error occurred during embedding generation: {e}")
|
| 97 |
+
import traceback
|
| 98 |
+
traceback.print_exc()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 99 |
return
|
| 100 |
|
| 101 |
+
# 5. --- Combine old and new embeddings ---
|
| 102 |
+
new_dense_vecs = new_embeddings['dense_vecs']
|
| 103 |
+
new_sparse_list = new_embeddings['lexical_weights']
|
| 104 |
+
new_sparse_vecs = np.array(new_sparse_list, dtype=object)
|
| 105 |
+
|
| 106 |
+
if old_dense_embeddings is not None and old_sparse_embeddings is not None:
|
| 107 |
+
print("Combining new embeddings with cached ones...")
|
| 108 |
+
combined_dense_embeddings = np.vstack([old_dense_embeddings, new_dense_vecs])
|
| 109 |
+
combined_sparse_embeddings = np.concatenate([old_sparse_embeddings, new_sparse_vecs])
|
| 110 |
+
else:
|
| 111 |
+
# This branch is for the first run when no cache exists
|
| 112 |
+
combined_dense_embeddings = new_dense_vecs
|
| 113 |
+
combined_sparse_embeddings = new_sparse_vecs
|
| 114 |
+
|
| 115 |
+
# 6. --- Update manifest and save everything ---
|
| 116 |
+
print("Updating manifest file...")
|
| 117 |
+
start_index = len(cached_manifest)
|
| 118 |
+
updated_manifest = cached_manifest.copy()
|
| 119 |
+
for i, res in enumerate(resolutions_to_encode):
|
| 120 |
+
updated_manifest[res['id']] = start_index + i
|
| 121 |
|
|
|
|
| 122 |
try:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 123 |
# Ensure output directory exists
|
| 124 |
os.makedirs(OUTPUT_DIR, exist_ok=True)
|
| 125 |
|
| 126 |
+
# Save combined embeddings
|
| 127 |
+
np.save(DENSE_OUTPUT_PATH, combined_dense_embeddings)
|
| 128 |
+
print(f"Saved combined semantic embeddings to {DENSE_OUTPUT_PATH} (Shape: {combined_dense_embeddings.shape})")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 129 |
|
| 130 |
+
np.save(SPARSE_OUTPUT_PATH, combined_sparse_embeddings, allow_pickle=True)
|
| 131 |
+
print(
|
| 132 |
+
f"Saved combined loose embeddings to {SPARSE_OUTPUT_PATH} (Total objects: {len(combined_sparse_embeddings)})")
|
| 133 |
|
| 134 |
+
# Save the updated manifest
|
| 135 |
+
with open(MANIFEST_PATH, 'w', encoding='utf-8') as f:
|
| 136 |
+
json.dump(updated_manifest, f, indent=2)
|
| 137 |
+
print(f"Saved updated manifest to {MANIFEST_PATH}")
|
|
|
|
| 138 |
|
| 139 |
+
print("\nGA Resolution embedding process complete!")
|
| 140 |
|
| 141 |
except Exception as e:
|
| 142 |
+
print(f"An error occurred while saving the files: {e}")
|
| 143 |
+
|
|
|
|
| 144 |
|
| 145 |
# Call the function to start the embedding process
|
| 146 |
if __name__ == "__main__":
|
| 147 |
+
encode_ga_resolutions_with_caching()
|
small_scripts/make_embedding/embeddings_manifest.json
ADDED
|
@@ -0,0 +1,804 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"1": 0,
|
| 3 |
+
"2": 1,
|
| 4 |
+
"3": 2,
|
| 5 |
+
"4": 3,
|
| 6 |
+
"5": 4,
|
| 7 |
+
"6": 5,
|
| 8 |
+
"7": 6,
|
| 9 |
+
"8": 7,
|
| 10 |
+
"9": 8,
|
| 11 |
+
"10": 9,
|
| 12 |
+
"11": 10,
|
| 13 |
+
"12": 11,
|
| 14 |
+
"13": 12,
|
| 15 |
+
"14": 13,
|
| 16 |
+
"15": 14,
|
| 17 |
+
"16": 15,
|
| 18 |
+
"17": 16,
|
| 19 |
+
"18": 17,
|
| 20 |
+
"19": 18,
|
| 21 |
+
"20": 19,
|
| 22 |
+
"21": 20,
|
| 23 |
+
"22": 21,
|
| 24 |
+
"23": 22,
|
| 25 |
+
"24": 23,
|
| 26 |
+
"25": 24,
|
| 27 |
+
"26": 25,
|
| 28 |
+
"27": 26,
|
| 29 |
+
"28": 27,
|
| 30 |
+
"29": 28,
|
| 31 |
+
"30": 29,
|
| 32 |
+
"31": 30,
|
| 33 |
+
"32": 31,
|
| 34 |
+
"33": 32,
|
| 35 |
+
"34": 33,
|
| 36 |
+
"35": 34,
|
| 37 |
+
"36": 35,
|
| 38 |
+
"37": 36,
|
| 39 |
+
"38": 37,
|
| 40 |
+
"39": 38,
|
| 41 |
+
"40": 39,
|
| 42 |
+
"41": 40,
|
| 43 |
+
"42": 41,
|
| 44 |
+
"43": 42,
|
| 45 |
+
"44": 43,
|
| 46 |
+
"45": 44,
|
| 47 |
+
"46": 45,
|
| 48 |
+
"47": 46,
|
| 49 |
+
"48": 47,
|
| 50 |
+
"49": 48,
|
| 51 |
+
"50": 49,
|
| 52 |
+
"53": 50,
|
| 53 |
+
"54": 51,
|
| 54 |
+
"55": 52,
|
| 55 |
+
"57": 53,
|
| 56 |
+
"59": 54,
|
| 57 |
+
"61": 55,
|
| 58 |
+
"62": 56,
|
| 59 |
+
"63": 57,
|
| 60 |
+
"65": 58,
|
| 61 |
+
"66": 59,
|
| 62 |
+
"67": 60,
|
| 63 |
+
"68": 61,
|
| 64 |
+
"69": 62,
|
| 65 |
+
"70": 63,
|
| 66 |
+
"71": 64,
|
| 67 |
+
"72": 65,
|
| 68 |
+
"74": 66,
|
| 69 |
+
"75": 67,
|
| 70 |
+
"77": 68,
|
| 71 |
+
"78": 69,
|
| 72 |
+
"79": 70,
|
| 73 |
+
"81": 71,
|
| 74 |
+
"84": 72,
|
| 75 |
+
"87": 73,
|
| 76 |
+
"90": 74,
|
| 77 |
+
"92": 75,
|
| 78 |
+
"93": 76,
|
| 79 |
+
"94": 77,
|
| 80 |
+
"95": 78,
|
| 81 |
+
"96": 79,
|
| 82 |
+
"97": 80,
|
| 83 |
+
"99": 81,
|
| 84 |
+
"100": 82,
|
| 85 |
+
"102": 83,
|
| 86 |
+
"104": 84,
|
| 87 |
+
"106": 85,
|
| 88 |
+
"108": 86,
|
| 89 |
+
"110": 87,
|
| 90 |
+
"111": 88,
|
| 91 |
+
"113": 89,
|
| 92 |
+
"114": 90,
|
| 93 |
+
"115": 91,
|
| 94 |
+
"116": 92,
|
| 95 |
+
"118": 93,
|
| 96 |
+
"121": 94,
|
| 97 |
+
"122": 95,
|
| 98 |
+
"123": 96,
|
| 99 |
+
"124": 97,
|
| 100 |
+
"125": 98,
|
| 101 |
+
"126": 99,
|
| 102 |
+
"127": 100,
|
| 103 |
+
"128": 101,
|
| 104 |
+
"129": 102,
|
| 105 |
+
"130": 103,
|
| 106 |
+
"132": 104,
|
| 107 |
+
"133": 105,
|
| 108 |
+
"134": 106,
|
| 109 |
+
"135": 107,
|
| 110 |
+
"137": 108,
|
| 111 |
+
"139": 109,
|
| 112 |
+
"141": 110,
|
| 113 |
+
"142": 111,
|
| 114 |
+
"143": 112,
|
| 115 |
+
"145": 113,
|
| 116 |
+
"146": 114,
|
| 117 |
+
"148": 115,
|
| 118 |
+
"149": 116,
|
| 119 |
+
"151": 117,
|
| 120 |
+
"154": 118,
|
| 121 |
+
"156": 119,
|
| 122 |
+
"157": 120,
|
| 123 |
+
"159": 121,
|
| 124 |
+
"161": 122,
|
| 125 |
+
"163": 123,
|
| 126 |
+
"165": 124,
|
| 127 |
+
"166": 125,
|
| 128 |
+
"168": 126,
|
| 129 |
+
"170": 127,
|
| 130 |
+
"172": 128,
|
| 131 |
+
"173": 129,
|
| 132 |
+
"175": 130,
|
| 133 |
+
"177": 131,
|
| 134 |
+
"178": 132,
|
| 135 |
+
"179": 133,
|
| 136 |
+
"180": 134,
|
| 137 |
+
"181": 135,
|
| 138 |
+
"183": 136,
|
| 139 |
+
"184": 137,
|
| 140 |
+
"185": 138,
|
| 141 |
+
"186": 139,
|
| 142 |
+
"188": 140,
|
| 143 |
+
"190": 141,
|
| 144 |
+
"193": 142,
|
| 145 |
+
"197": 143,
|
| 146 |
+
"199": 144,
|
| 147 |
+
"200": 145,
|
| 148 |
+
"202": 146,
|
| 149 |
+
"203": 147,
|
| 150 |
+
"204": 148,
|
| 151 |
+
"206": 149,
|
| 152 |
+
"207": 150,
|
| 153 |
+
"208": 151,
|
| 154 |
+
"210": 152,
|
| 155 |
+
"211": 153,
|
| 156 |
+
"213": 154,
|
| 157 |
+
"216": 155,
|
| 158 |
+
"219": 156,
|
| 159 |
+
"220": 157,
|
| 160 |
+
"221": 158,
|
| 161 |
+
"224": 159,
|
| 162 |
+
"225": 160,
|
| 163 |
+
"228": 161,
|
| 164 |
+
"229": 162,
|
| 165 |
+
"230": 163,
|
| 166 |
+
"233": 164,
|
| 167 |
+
"235": 165,
|
| 168 |
+
"237": 166,
|
| 169 |
+
"238": 167,
|
| 170 |
+
"239": 168,
|
| 171 |
+
"242": 169,
|
| 172 |
+
"244": 170,
|
| 173 |
+
"246": 171,
|
| 174 |
+
"248": 172,
|
| 175 |
+
"249": 173,
|
| 176 |
+
"250": 174,
|
| 177 |
+
"252": 175,
|
| 178 |
+
"254": 176,
|
| 179 |
+
"255": 177,
|
| 180 |
+
"256": 178,
|
| 181 |
+
"257": 179,
|
| 182 |
+
"259": 180,
|
| 183 |
+
"260": 181,
|
| 184 |
+
"261": 182,
|
| 185 |
+
"263": 183,
|
| 186 |
+
"266": 184,
|
| 187 |
+
"267": 185,
|
| 188 |
+
"269": 186,
|
| 189 |
+
"270": 187,
|
| 190 |
+
"272": 188,
|
| 191 |
+
"275": 189,
|
| 192 |
+
"277": 190,
|
| 193 |
+
"278": 191,
|
| 194 |
+
"279": 192,
|
| 195 |
+
"280": 193,
|
| 196 |
+
"281": 194,
|
| 197 |
+
"282": 195,
|
| 198 |
+
"283": 196,
|
| 199 |
+
"284": 197,
|
| 200 |
+
"287": 198,
|
| 201 |
+
"289": 199,
|
| 202 |
+
"291": 200,
|
| 203 |
+
"292": 201,
|
| 204 |
+
"293": 202,
|
| 205 |
+
"295": 203,
|
| 206 |
+
"296": 204,
|
| 207 |
+
"297": 205,
|
| 208 |
+
"299": 206,
|
| 209 |
+
"301": 207,
|
| 210 |
+
"304": 208,
|
| 211 |
+
"306": 209,
|
| 212 |
+
"308": 210,
|
| 213 |
+
"309": 211,
|
| 214 |
+
"310": 212,
|
| 215 |
+
"311": 213,
|
| 216 |
+
"312": 214,
|
| 217 |
+
"314": 215,
|
| 218 |
+
"315": 216,
|
| 219 |
+
"316": 217,
|
| 220 |
+
"317": 218,
|
| 221 |
+
"319": 219,
|
| 222 |
+
"320": 220,
|
| 223 |
+
"321": 221,
|
| 224 |
+
"324": 222,
|
| 225 |
+
"326": 223,
|
| 226 |
+
"328": 224,
|
| 227 |
+
"329": 225,
|
| 228 |
+
"330": 226,
|
| 229 |
+
"332": 227,
|
| 230 |
+
"333": 228,
|
| 231 |
+
"335": 229,
|
| 232 |
+
"336": 230,
|
| 233 |
+
"338": 231,
|
| 234 |
+
"339": 232,
|
| 235 |
+
"340": 233,
|
| 236 |
+
"341": 234,
|
| 237 |
+
"343": 235,
|
| 238 |
+
"344": 236,
|
| 239 |
+
"346": 237,
|
| 240 |
+
"347": 238,
|
| 241 |
+
"348": 239,
|
| 242 |
+
"349": 240,
|
| 243 |
+
"350": 241,
|
| 244 |
+
"351": 242,
|
| 245 |
+
"352": 243,
|
| 246 |
+
"354": 244,
|
| 247 |
+
"356": 245,
|
| 248 |
+
"357": 246,
|
| 249 |
+
"358": 247,
|
| 250 |
+
"360": 248,
|
| 251 |
+
"362": 249,
|
| 252 |
+
"364": 250,
|
| 253 |
+
"367": 251,
|
| 254 |
+
"370": 252,
|
| 255 |
+
"371": 253,
|
| 256 |
+
"372": 254,
|
| 257 |
+
"373": 255,
|
| 258 |
+
"375": 256,
|
| 259 |
+
"377": 257,
|
| 260 |
+
"378": 258,
|
| 261 |
+
"379": 259,
|
| 262 |
+
"380": 260,
|
| 263 |
+
"381": 261,
|
| 264 |
+
"382": 262,
|
| 265 |
+
"384": 263,
|
| 266 |
+
"385": 264,
|
| 267 |
+
"386": 265,
|
| 268 |
+
"389": 266,
|
| 269 |
+
"390": 267,
|
| 270 |
+
"391": 268,
|
| 271 |
+
"392": 269,
|
| 272 |
+
"395": 270,
|
| 273 |
+
"396": 271,
|
| 274 |
+
"397": 272,
|
| 275 |
+
"398": 273,
|
| 276 |
+
"400": 274,
|
| 277 |
+
"402": 275,
|
| 278 |
+
"403": 276,
|
| 279 |
+
"407": 277,
|
| 280 |
+
"408": 278,
|
| 281 |
+
"409": 279,
|
| 282 |
+
"411": 280,
|
| 283 |
+
"413": 281,
|
| 284 |
+
"415": 282,
|
| 285 |
+
"416": 283,
|
| 286 |
+
"418": 284,
|
| 287 |
+
"419": 285,
|
| 288 |
+
"420": 286,
|
| 289 |
+
"423": 287,
|
| 290 |
+
"431": 288,
|
| 291 |
+
"432": 289,
|
| 292 |
+
"435": 290,
|
| 293 |
+
"438": 291,
|
| 294 |
+
"439": 292,
|
| 295 |
+
"441": 293,
|
| 296 |
+
"442": 294,
|
| 297 |
+
"443": 295,
|
| 298 |
+
"445": 296,
|
| 299 |
+
"447": 297,
|
| 300 |
+
"449": 298,
|
| 301 |
+
"450": 299,
|
| 302 |
+
"455": 300,
|
| 303 |
+
"458": 301,
|
| 304 |
+
"460": 302,
|
| 305 |
+
"461": 303,
|
| 306 |
+
"464": 304,
|
| 307 |
+
"466": 305,
|
| 308 |
+
"473": 306,
|
| 309 |
+
"474": 307,
|
| 310 |
+
"476": 308,
|
| 311 |
+
"477": 309,
|
| 312 |
+
"479": 310,
|
| 313 |
+
"480": 311,
|
| 314 |
+
"481": 312,
|
| 315 |
+
"483": 313,
|
| 316 |
+
"484": 314,
|
| 317 |
+
"485": 315,
|
| 318 |
+
"487": 316,
|
| 319 |
+
"488": 317,
|
| 320 |
+
"490": 318,
|
| 321 |
+
"491": 319,
|
| 322 |
+
"492": 320,
|
| 323 |
+
"493": 321,
|
| 324 |
+
"495": 322,
|
| 325 |
+
"497": 323,
|
| 326 |
+
"503": 324,
|
| 327 |
+
"504": 325,
|
| 328 |
+
"505": 326,
|
| 329 |
+
"506": 327,
|
| 330 |
+
"507": 328,
|
| 331 |
+
"509": 329,
|
| 332 |
+
"510": 330,
|
| 333 |
+
"512": 331,
|
| 334 |
+
"514": 332,
|
| 335 |
+
"515": 333,
|
| 336 |
+
"517": 334,
|
| 337 |
+
"518": 335,
|
| 338 |
+
"519": 336,
|
| 339 |
+
"520": 337,
|
| 340 |
+
"521": 338,
|
| 341 |
+
"522": 339,
|
| 342 |
+
"523": 340,
|
| 343 |
+
"524": 341,
|
| 344 |
+
"525": 342,
|
| 345 |
+
"526": 343,
|
| 346 |
+
"527": 344,
|
| 347 |
+
"528": 345,
|
| 348 |
+
"529": 346,
|
| 349 |
+
"531": 347,
|
| 350 |
+
"534": 348,
|
| 351 |
+
"536": 349,
|
| 352 |
+
"537": 350,
|
| 353 |
+
"538": 351,
|
| 354 |
+
"541": 352,
|
| 355 |
+
"543": 353,
|
| 356 |
+
"544": 354,
|
| 357 |
+
"546": 355,
|
| 358 |
+
"548": 356,
|
| 359 |
+
"549": 357,
|
| 360 |
+
"550": 358,
|
| 361 |
+
"551": 359,
|
| 362 |
+
"553": 360,
|
| 363 |
+
"555": 361,
|
| 364 |
+
"557": 362,
|
| 365 |
+
"559": 363,
|
| 366 |
+
"560": 364,
|
| 367 |
+
"562": 365,
|
| 368 |
+
"563": 366,
|
| 369 |
+
"564": 367,
|
| 370 |
+
"565": 368,
|
| 371 |
+
"566": 369,
|
| 372 |
+
"567": 370,
|
| 373 |
+
"568": 371,
|
| 374 |
+
"570": 372,
|
| 375 |
+
"572": 373,
|
| 376 |
+
"573": 374,
|
| 377 |
+
"576": 375,
|
| 378 |
+
"577": 376,
|
| 379 |
+
"578": 377,
|
| 380 |
+
"579": 378,
|
| 381 |
+
"581": 379,
|
| 382 |
+
"583": 380,
|
| 383 |
+
"585": 381,
|
| 384 |
+
"587": 382,
|
| 385 |
+
"588": 383,
|
| 386 |
+
"589": 384,
|
| 387 |
+
"593": 385,
|
| 388 |
+
"596": 386,
|
| 389 |
+
"597": 387,
|
| 390 |
+
"599": 388,
|
| 391 |
+
"601": 389,
|
| 392 |
+
"602": 390,
|
| 393 |
+
"603": 391,
|
| 394 |
+
"609": 392,
|
| 395 |
+
"612": 393,
|
| 396 |
+
"613": 394,
|
| 397 |
+
"614": 395,
|
| 398 |
+
"615": 396,
|
| 399 |
+
"616": 397,
|
| 400 |
+
"618": 398,
|
| 401 |
+
"621": 399,
|
| 402 |
+
"623": 400,
|
| 403 |
+
"625": 401,
|
| 404 |
+
"627": 402,
|
| 405 |
+
"628": 403,
|
| 406 |
+
"629": 404,
|
| 407 |
+
"631": 405,
|
| 408 |
+
"637": 406,
|
| 409 |
+
"639": 407,
|
| 410 |
+
"641": 408,
|
| 411 |
+
"642": 409,
|
| 412 |
+
"644": 410,
|
| 413 |
+
"646": 411,
|
| 414 |
+
"648": 412,
|
| 415 |
+
"649": 413,
|
| 416 |
+
"651": 414,
|
| 417 |
+
"653": 415,
|
| 418 |
+
"654": 416,
|
| 419 |
+
"655": 417,
|
| 420 |
+
"658": 418,
|
| 421 |
+
"660": 419,
|
| 422 |
+
"661": 420,
|
| 423 |
+
"665": 421,
|
| 424 |
+
"666": 422,
|
| 425 |
+
"667": 423,
|
| 426 |
+
"668": 424,
|
| 427 |
+
"670": 425,
|
| 428 |
+
"676": 426,
|
| 429 |
+
"677": 427,
|
| 430 |
+
"683": 428,
|
| 431 |
+
"685": 429,
|
| 432 |
+
"686": 430,
|
| 433 |
+
"687": 431,
|
| 434 |
+
"690": 432,
|
| 435 |
+
"692": 433,
|
| 436 |
+
"694": 434,
|
| 437 |
+
"696": 435,
|
| 438 |
+
"697": 436,
|
| 439 |
+
"698": 437,
|
| 440 |
+
"699": 438,
|
| 441 |
+
"700": 439,
|
| 442 |
+
"701": 440,
|
| 443 |
+
"704": 441,
|
| 444 |
+
"705": 442,
|
| 445 |
+
"707": 443,
|
| 446 |
+
"708": 444,
|
| 447 |
+
"709": 445,
|
| 448 |
+
"711": 446,
|
| 449 |
+
"712": 447,
|
| 450 |
+
"714": 448,
|
| 451 |
+
"715": 449,
|
| 452 |
+
"717": 450,
|
| 453 |
+
"719": 451,
|
| 454 |
+
"727": 452,
|
| 455 |
+
"730": 453,
|
| 456 |
+
"731": 454,
|
| 457 |
+
"733": 455,
|
| 458 |
+
"734": 456,
|
| 459 |
+
"735": 457,
|
| 460 |
+
"736": 458,
|
| 461 |
+
"738": 459,
|
| 462 |
+
"739": 460,
|
| 463 |
+
"741": 461,
|
| 464 |
+
"742": 462,
|
| 465 |
+
"743": 463,
|
| 466 |
+
"744": 464,
|
| 467 |
+
"748": 465,
|
| 468 |
+
"751": 466,
|
| 469 |
+
"752": 467,
|
| 470 |
+
"753": 468,
|
| 471 |
+
"757": 469,
|
| 472 |
+
"758": 470,
|
| 473 |
+
"759": 471,
|
| 474 |
+
"760": 472,
|
| 475 |
+
"761": 473,
|
| 476 |
+
"762": 474,
|
| 477 |
+
"763": 475,
|
| 478 |
+
"766": 476,
|
| 479 |
+
"770": 477,
|
| 480 |
+
"772": 478,
|
| 481 |
+
"773": 479,
|
| 482 |
+
"775": 480,
|
| 483 |
+
"777": 481,
|
| 484 |
+
"780": 482,
|
| 485 |
+
"782": 483,
|
| 486 |
+
"783": 484,
|
| 487 |
+
"787": 485,
|
| 488 |
+
"790": 486,
|
| 489 |
+
"795": 487,
|
| 490 |
+
"797": 488,
|
| 491 |
+
"798": 489,
|
| 492 |
+
"801": 490,
|
| 493 |
+
"803": 491,
|
| 494 |
+
"805": 492,
|
| 495 |
+
"806": 493,
|
| 496 |
+
"807": 494,
|
| 497 |
+
"810": 495,
|
| 498 |
+
"811": 496,
|
| 499 |
+
"812": 497,
|
| 500 |
+
"813": 498,
|
| 501 |
+
"814": 499,
|
| 502 |
+
"815": 500,
|
| 503 |
+
"818": 501,
|
| 504 |
+
"820": 502,
|
| 505 |
+
"821": 503,
|
| 506 |
+
"823": 504,
|
| 507 |
+
"825": 505,
|
| 508 |
+
"827": 506,
|
| 509 |
+
"828": 507,
|
| 510 |
+
"830": 508,
|
| 511 |
+
"832": 509,
|
| 512 |
+
"834": 510,
|
| 513 |
+
"836": 511,
|
| 514 |
+
"839": 512,
|
| 515 |
+
"840": 513,
|
| 516 |
+
"841": 514,
|
| 517 |
+
"843": 515,
|
| 518 |
+
"844": 516,
|
| 519 |
+
"846": 517,
|
| 520 |
+
"847": 518,
|
| 521 |
+
"848": 519,
|
| 522 |
+
"850": 520,
|
| 523 |
+
"852": 521,
|
| 524 |
+
"854": 522,
|
| 525 |
+
"855": 523,
|
| 526 |
+
"856": 524,
|
| 527 |
+
"858": 525,
|
| 528 |
+
"860": 526,
|
| 529 |
+
"862": 527,
|
| 530 |
+
"863": 528,
|
| 531 |
+
"867": 529,
|
| 532 |
+
"869": 530,
|
| 533 |
+
"870": 531,
|
| 534 |
+
"873": 532,
|
| 535 |
+
"874": 533,
|
| 536 |
+
"875": 534,
|
| 537 |
+
"876": 535,
|
| 538 |
+
"879": 536,
|
| 539 |
+
"881": 537,
|
| 540 |
+
"883": 538,
|
| 541 |
+
"885": 539,
|
| 542 |
+
"886": 540,
|
| 543 |
+
"888": 541,
|
| 544 |
+
"890": 542,
|
| 545 |
+
"891": 543,
|
| 546 |
+
"893": 544,
|
| 547 |
+
"894": 545,
|
| 548 |
+
"895": 546,
|
| 549 |
+
"897": 547,
|
| 550 |
+
"899": 548,
|
| 551 |
+
"900": 549,
|
| 552 |
+
"902": 550,
|
| 553 |
+
"903": 551,
|
| 554 |
+
"905": 552,
|
| 555 |
+
"907": 553,
|
| 556 |
+
"909": 554,
|
| 557 |
+
"911": 555,
|
| 558 |
+
"913": 556,
|
| 559 |
+
"914": 557,
|
| 560 |
+
"915": 558,
|
| 561 |
+
"916": 559,
|
| 562 |
+
"917": 560,
|
| 563 |
+
"918": 561,
|
| 564 |
+
"920": 562,
|
| 565 |
+
"923": 563,
|
| 566 |
+
"924": 564,
|
| 567 |
+
"926": 565,
|
| 568 |
+
"929": 566,
|
| 569 |
+
"930": 567,
|
| 570 |
+
"932": 568,
|
| 571 |
+
"934": 569,
|
| 572 |
+
"936": 570,
|
| 573 |
+
"937": 571,
|
| 574 |
+
"939": 572,
|
| 575 |
+
"942": 573,
|
| 576 |
+
"943": 574,
|
| 577 |
+
"944": 575,
|
| 578 |
+
"946": 576,
|
| 579 |
+
"947": 577,
|
| 580 |
+
"949": 578,
|
| 581 |
+
"952": 579,
|
| 582 |
+
"954": 580,
|
| 583 |
+
"956": 581,
|
| 584 |
+
"958": 582,
|
| 585 |
+
"959": 583,
|
| 586 |
+
"960": 584,
|
| 587 |
+
"961": 585,
|
| 588 |
+
"964": 586,
|
| 589 |
+
"966": 587,
|
| 590 |
+
"967": 588,
|
| 591 |
+
"970": 589,
|
| 592 |
+
"972": 590,
|
| 593 |
+
"974": 591,
|
| 594 |
+
"976": 592,
|
| 595 |
+
"978": 593,
|
| 596 |
+
"979": 594,
|
| 597 |
+
"981": 595,
|
| 598 |
+
"982": 596,
|
| 599 |
+
"984": 597,
|
| 600 |
+
"985": 598,
|
| 601 |
+
"988": 599,
|
| 602 |
+
"990": 600,
|
| 603 |
+
"992": 601,
|
| 604 |
+
"993": 602,
|
| 605 |
+
"998": 603,
|
| 606 |
+
"999": 604,
|
| 607 |
+
"1000": 605,
|
| 608 |
+
"1001": 606,
|
| 609 |
+
"1004": 607,
|
| 610 |
+
"1006": 608,
|
| 611 |
+
"1008": 609,
|
| 612 |
+
"1009": 610,
|
| 613 |
+
"1012": 611,
|
| 614 |
+
"1014": 612,
|
| 615 |
+
"1016": 613,
|
| 616 |
+
"1021": 614,
|
| 617 |
+
"1024": 615,
|
| 618 |
+
"1025": 616,
|
| 619 |
+
"1026": 617,
|
| 620 |
+
"1027": 618,
|
| 621 |
+
"1030": 619,
|
| 622 |
+
"1033": 620,
|
| 623 |
+
"1036": 621,
|
| 624 |
+
"1039": 622,
|
| 625 |
+
"1040": 623,
|
| 626 |
+
"1042": 624,
|
| 627 |
+
"1044": 625,
|
| 628 |
+
"1045": 626,
|
| 629 |
+
"1048": 627,
|
| 630 |
+
"1049": 628,
|
| 631 |
+
"1052": 629,
|
| 632 |
+
"1054": 630,
|
| 633 |
+
"1056": 631,
|
| 634 |
+
"1058": 632,
|
| 635 |
+
"1059": 633,
|
| 636 |
+
"1060": 634,
|
| 637 |
+
"1061": 635,
|
| 638 |
+
"1062": 636,
|
| 639 |
+
"1063": 637,
|
| 640 |
+
"1065": 638,
|
| 641 |
+
"1067": 639,
|
| 642 |
+
"1068": 640,
|
| 643 |
+
"1070": 641,
|
| 644 |
+
"1072": 642,
|
| 645 |
+
"1074": 643,
|
| 646 |
+
"1077": 644,
|
| 647 |
+
"1078": 645,
|
| 648 |
+
"1079": 646,
|
| 649 |
+
"1081": 647,
|
| 650 |
+
"1083": 648,
|
| 651 |
+
"1086": 649,
|
| 652 |
+
"1088": 650,
|
| 653 |
+
"1091": 651,
|
| 654 |
+
"1092": 652,
|
| 655 |
+
"1093": 653,
|
| 656 |
+
"1095": 654,
|
| 657 |
+
"1096": 655,
|
| 658 |
+
"1097": 656,
|
| 659 |
+
"1100": 657,
|
| 660 |
+
"1102": 658,
|
| 661 |
+
"1104": 659,
|
| 662 |
+
"1106": 660,
|
| 663 |
+
"1108": 661,
|
| 664 |
+
"1109": 662,
|
| 665 |
+
"1111": 663,
|
| 666 |
+
"1113": 664,
|
| 667 |
+
"1115": 665,
|
| 668 |
+
"1118": 666,
|
| 669 |
+
"1120": 667,
|
| 670 |
+
"1122": 668,
|
| 671 |
+
"1124": 669,
|
| 672 |
+
"1126": 670,
|
| 673 |
+
"1130": 671,
|
| 674 |
+
"1132": 672,
|
| 675 |
+
"1134": 673,
|
| 676 |
+
"1136": 674,
|
| 677 |
+
"1138": 675,
|
| 678 |
+
"1139": 676,
|
| 679 |
+
"1141": 677,
|
| 680 |
+
"1143": 678,
|
| 681 |
+
"1144": 679,
|
| 682 |
+
"1146": 680,
|
| 683 |
+
"1148": 681,
|
| 684 |
+
"1150": 682,
|
| 685 |
+
"1151": 683,
|
| 686 |
+
"1152": 684,
|
| 687 |
+
"1154": 685,
|
| 688 |
+
"1156": 686,
|
| 689 |
+
"1158": 687,
|
| 690 |
+
"1160": 688,
|
| 691 |
+
"1161": 689,
|
| 692 |
+
"1163": 690,
|
| 693 |
+
"1166": 691,
|
| 694 |
+
"1168": 692,
|
| 695 |
+
"1171": 693,
|
| 696 |
+
"1173": 694,
|
| 697 |
+
"1176": 695,
|
| 698 |
+
"1178": 696,
|
| 699 |
+
"1180": 697,
|
| 700 |
+
"1182": 698,
|
| 701 |
+
"1184": 699,
|
| 702 |
+
"1186": 700,
|
| 703 |
+
"1188": 701,
|
| 704 |
+
"1190": 702,
|
| 705 |
+
"1191": 703,
|
| 706 |
+
"1193": 704,
|
| 707 |
+
"1195": 705,
|
| 708 |
+
"1197": 706,
|
| 709 |
+
"1198": 707,
|
| 710 |
+
"1199": 708,
|
| 711 |
+
"1201": 709,
|
| 712 |
+
"1203": 710,
|
| 713 |
+
"1205": 711,
|
| 714 |
+
"1207": 712,
|
| 715 |
+
"1208": 713,
|
| 716 |
+
"1209": 714,
|
| 717 |
+
"1211": 715,
|
| 718 |
+
"1212": 716,
|
| 719 |
+
"1214": 717,
|
| 720 |
+
"1216": 718,
|
| 721 |
+
"1217": 719,
|
| 722 |
+
"1218": 720,
|
| 723 |
+
"1220": 721,
|
| 724 |
+
"1223": 722,
|
| 725 |
+
"1224": 723,
|
| 726 |
+
"1225": 724,
|
| 727 |
+
"1228": 725,
|
| 728 |
+
"1229": 726,
|
| 729 |
+
"1230": 727,
|
| 730 |
+
"1231": 728,
|
| 731 |
+
"1232": 729,
|
| 732 |
+
"1233": 730,
|
| 733 |
+
"1234": 731,
|
| 734 |
+
"1235": 732,
|
| 735 |
+
"1236": 733,
|
| 736 |
+
"1237": 734,
|
| 737 |
+
"1240": 735,
|
| 738 |
+
"1242": 736,
|
| 739 |
+
"1244": 737,
|
| 740 |
+
"1245": 738,
|
| 741 |
+
"1246": 739,
|
| 742 |
+
"1247": 740,
|
| 743 |
+
"1248": 741,
|
| 744 |
+
"1250": 742,
|
| 745 |
+
"1252": 743,
|
| 746 |
+
"1254": 744,
|
| 747 |
+
"1257": 745,
|
| 748 |
+
"1258": 746,
|
| 749 |
+
"1259": 747,
|
| 750 |
+
"1260": 748,
|
| 751 |
+
"1263": 749,
|
| 752 |
+
"1266": 750,
|
| 753 |
+
"1270": 751,
|
| 754 |
+
"1272": 752,
|
| 755 |
+
"1273": 753,
|
| 756 |
+
"1275": 754,
|
| 757 |
+
"1277": 755,
|
| 758 |
+
"1278": 756,
|
| 759 |
+
"1280": 757,
|
| 760 |
+
"1281": 758,
|
| 761 |
+
"1283": 759,
|
| 762 |
+
"1285": 760,
|
| 763 |
+
"1287": 761,
|
| 764 |
+
"1291": 762,
|
| 765 |
+
"1293": 763,
|
| 766 |
+
"1296": 764,
|
| 767 |
+
"1297": 765,
|
| 768 |
+
"1298": 766,
|
| 769 |
+
"1299": 767,
|
| 770 |
+
"1300": 768,
|
| 771 |
+
"1303": 769,
|
| 772 |
+
"1304": 770,
|
| 773 |
+
"1305": 771,
|
| 774 |
+
"1307": 772,
|
| 775 |
+
"1310": 773,
|
| 776 |
+
"1312": 774,
|
| 777 |
+
"1315": 775,
|
| 778 |
+
"1316": 776,
|
| 779 |
+
"1317": 777,
|
| 780 |
+
"1318": 778,
|
| 781 |
+
"1324": 779,
|
| 782 |
+
"1325": 780,
|
| 783 |
+
"1327": 781,
|
| 784 |
+
"1329": 782,
|
| 785 |
+
"1330": 783,
|
| 786 |
+
"1332": 784,
|
| 787 |
+
"1333": 785,
|
| 788 |
+
"1336": 786,
|
| 789 |
+
"1338": 787,
|
| 790 |
+
"1339": 788,
|
| 791 |
+
"1340": 789,
|
| 792 |
+
"1341": 790,
|
| 793 |
+
"1343": 791,
|
| 794 |
+
"1345": 792,
|
| 795 |
+
"1347": 793,
|
| 796 |
+
"1350": 794,
|
| 797 |
+
"1351": 795,
|
| 798 |
+
"1352": 796,
|
| 799 |
+
"1354": 797,
|
| 800 |
+
"1355": 798,
|
| 801 |
+
"1357": 799,
|
| 802 |
+
"1359": 800,
|
| 803 |
+
"1361": 801
|
| 804 |
+
}
|
small_scripts/parse_ga_resolutions.py
CHANGED
|
@@ -1,228 +1,212 @@
|
|
|
|
|
|
|
|
| 1 |
import json
|
| 2 |
-
import
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 8 |
"""
|
| 9 |
-
Parses a single
|
| 10 |
-
into a structured Python dictionary.
|
| 11 |
|
| 12 |
Args:
|
| 13 |
-
|
| 14 |
|
| 15 |
Returns:
|
| 16 |
-
A dictionary representing the resolution data, or None if parsing fails.
|
| 17 |
"""
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 26 |
return None
|
| 27 |
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
|
| 31 |
-
|
| 32 |
-
|
| 33 |
-
|
| 34 |
-
|
| 35 |
-
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
|
| 39 |
-
'id': repealer_link.text.strip().replace('Repealed by GA#', '').strip(), # Extract just the number
|
| 40 |
-
'link': repealer_link.get('href')
|
| 41 |
-
}
|
| 42 |
else:
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
# ---
|
| 46 |
-
|
| 47 |
-
|
| 48 |
-
|
| 49 |
-
|
| 50 |
-
|
| 51 |
-
|
| 52 |
-
|
| 53 |
-
|
| 54 |
-
|
| 55 |
-
|
| 56 |
-
|
| 57 |
-
|
| 58 |
-
|
| 59 |
-
|
| 60 |
-
|
| 61 |
-
|
| 62 |
-
|
| 63 |
-
|
| 64 |
-
|
| 65 |
-
|
| 66 |
-
|
| 67 |
-
|
| 68 |
-
|
| 69 |
-
|
| 70 |
-
|
| 71 |
-
|
| 72 |
-
|
| 73 |
-
|
| 74 |
-
|
| 75 |
-
|
| 76 |
-
|
| 77 |
-
|
| 78 |
-
|
| 79 |
-
|
| 80 |
-
|
| 81 |
-
|
| 82 |
-
|
| 83 |
-
|
| 84 |
-
|
| 85 |
-
|
| 86 |
-
|
| 87 |
-
|
| 88 |
-
|
| 89 |
-
|
| 90 |
-
|
| 91 |
-
|
| 92 |
-
|
| 93 |
-
|
| 94 |
-
|
| 95 |
-
|
| 96 |
-
|
| 97 |
-
|
| 98 |
-
|
| 99 |
-
|
| 100 |
-
|
| 101 |
-
|
| 102 |
-
|
| 103 |
-
|
| 104 |
-
|
| 105 |
-
|
| 106 |
-
|
| 107 |
-
|
| 108 |
-
|
| 109 |
-
|
| 110 |
-
|
| 111 |
-
|
| 112 |
-
|
| 113 |
-
|
| 114 |
-
|
| 115 |
-
|
| 116 |
-
|
| 117 |
-
|
| 118 |
-
|
| 119 |
-
|
| 120 |
-
|
| 121 |
-
|
| 122 |
-
|
| 123 |
-
|
| 124 |
-
|
| 125 |
-
|
| 126 |
-
|
| 127 |
-
|
| 128 |
-
|
| 129 |
-
|
| 130 |
-
|
| 131 |
-
|
| 132 |
-
|
| 133 |
-
|
| 134 |
-
data['co_authors'] = coauthors_list
|
| 135 |
-
|
| 136 |
-
# --- Vote Counts and Dates ---
|
| 137 |
-
presbottom_div = thing_div.find('div', class_='WApresbottom')
|
| 138 |
-
if presbottom_div:
|
| 139 |
-
# Passed/Repealed Dates (in floatrightbox)
|
| 140 |
-
floatrightbox = presbottom_div.find('div', class_='floatrightbox')
|
| 141 |
-
if floatrightbox:
|
| 142 |
-
# Passed date
|
| 143 |
-
passed_leader_p = floatrightbox.find('p', class_='WA_leader', string='Passed:')
|
| 144 |
-
if passed_leader_p:
|
| 145 |
-
# Navigate up to the <td>, then find the next sibling <td>, then find the <p> inside it, then the <time>
|
| 146 |
-
passed_leader_td = passed_leader_p.find_parent('td')
|
| 147 |
-
if passed_leader_td: # Add a check here too just in case the structure is unexpected
|
| 148 |
-
date_td = passed_leader_td.find_next_sibling('td')
|
| 149 |
-
if date_td: # Check if the next <td> exists
|
| 150 |
-
date_p = date_td.find('p') # Find the paragraph inside that <td>
|
| 151 |
-
if date_p: # Check if the paragraph exists
|
| 152 |
-
passed_time_tag = date_p.find('time') # Find the time tag inside that paragraph
|
| 153 |
-
|
| 154 |
-
if passed_time_tag:
|
| 155 |
-
data['passed_date'] = {
|
| 156 |
-
'datetime': passed_time_tag.get('datetime'),
|
| 157 |
-
'text': passed_time_tag.text.strip()
|
| 158 |
-
}
|
| 159 |
-
|
| 160 |
-
# Repealed date (only present if repealed)
|
| 161 |
-
# Apply similar robust navigation here
|
| 162 |
-
repealed_leader_p = floatrightbox.find('p', class_='WA_leader')
|
| 163 |
-
if repealed_leader_p and repealed_leader_p.find('a', string='Repealed:'):
|
| 164 |
-
repealed_leader_td = repealed_leader_p.find_parent('td')
|
| 165 |
-
if repealed_leader_td:
|
| 166 |
-
date_td = repealed_leader_td.find_next_sibling('td')
|
| 167 |
-
if date_td:
|
| 168 |
-
date_p = date_td.find('p')
|
| 169 |
-
if date_p:
|
| 170 |
-
repealed_time_tag = date_p.find('time')
|
| 171 |
-
|
| 172 |
-
if repealed_time_tag:
|
| 173 |
-
# Ensure status is marked repealed even if WA_thing_repealed div was missed
|
| 174 |
-
if 'status' not in data or data['status'] != 'Repealed':
|
| 175 |
-
data['status'] = 'Repealed'
|
| 176 |
-
data['repealed_date'] = {
|
| 177 |
-
'datetime': repealed_time_tag.get('datetime'),
|
| 178 |
-
'text': repealed_time_tag.text.strip()
|
| 179 |
-
}
|
| 180 |
-
# Vote Counts (in WA_votecount table)
|
| 181 |
-
# This part of the logic seems mostly correct because you're navigating cell by cell within the row
|
| 182 |
-
votecount_table = presbottom_div.find('table', class_='WA_votecount')
|
| 183 |
-
if votecount_table:
|
| 184 |
-
for row in votecount_table.find_all('tr'):
|
| 185 |
-
leader_cell = row.find('p', class_='WA_leader')
|
| 186 |
-
if leader_cell:
|
| 187 |
-
label = leader_cell.text.strip().replace(':', '')
|
| 188 |
-
if label in ['For', 'Against']:
|
| 189 |
-
# Find the cells for count and percentage relative to the leader cell
|
| 190 |
-
# These navigations (find_parent('td').find_next_sibling('td')) are correct
|
| 191 |
-
count_cell = leader_cell.find_parent('td').find_next_sibling('td')
|
| 192 |
-
percentage_cell = count_cell.find_next_sibling('td') if count_cell else None
|
| 193 |
-
|
| 194 |
-
count_text = count_cell.find('span', class_='bigtext').text.strip().replace(',', '') if count_cell and count_cell.find('span', class_='bigtext') else '0'
|
| 195 |
-
percentage_text = percentage_cell.find('span', class_='smalltext').text.strip().replace('%', '') if percentage_cell and percentage_cell.find('span', class_='smalltext') else '0'
|
| 196 |
-
|
| 197 |
-
|
| 198 |
-
try:
|
| 199 |
-
data[label.lower() + '_votes'] = int(count_text)
|
| 200 |
-
except ValueError:
|
| 201 |
-
data[label.lower() + '_votes'] = 0 # Handle potential parsing errors
|
| 202 |
-
|
| 203 |
-
try:
|
| 204 |
-
data[label.lower() + '_percentage'] = float(percentage_text)
|
| 205 |
-
except ValueError:
|
| 206 |
-
data[label.lower() + '_percentage'] = 0.0
|
| 207 |
-
|
| 208 |
-
return data
|
| 209 |
-
|
| 210 |
-
|
| 211 |
-
def __main__():
|
| 212 |
-
resolutions = open("ga_resolutions.json", "r")
|
| 213 |
-
html_resolutions = json.load(resolutions)
|
| 214 |
-
resolutions.close()
|
| 215 |
-
json_resolutions = []
|
| 216 |
-
for resolution in html_resolutions:
|
| 217 |
-
json_resolutions.append(parse_resolution_html(resolution))
|
| 218 |
-
output = open("../parsed_ga_resolutions.json", "w")
|
| 219 |
-
json.dump(json_resolutions, output)
|
| 220 |
-
|
| 221 |
-
__main__()
|
| 222 |
-
|
| 223 |
-
def format_resoluton(resolution):
|
| 224 |
-
title = "<resolution>\n"
|
| 225 |
-
if resolution['status'] == "REPEALED":
|
| 226 |
-
title = f"[Repealed by GA#{resolution['repealed_by']} "
|
| 227 |
-
title += f"GA#{resolution['id']} {resolution['title']}"
|
| 228 |
-
return title + "\n\n" + resolution['body'] + "\n</resolution>"
|
|
|
|
| 1 |
+
import requests
|
| 2 |
+
import xml.etree.ElementTree as ET
|
| 3 |
import json
|
| 4 |
+
import time
|
| 5 |
+
import os
|
| 6 |
+
|
| 7 |
+
# --- Configuration ---
|
| 8 |
+
# Replace with your own nation name or contact info.
|
| 9 |
+
USER_AGENT = "NS Issue Search dev update script (Jiangbei)"
|
| 10 |
+
CACHE_FILE = "../parsed_ga_resolutions.json"
|
| 11 |
+
API_BASE_URL = "https://www.nationstates.net/cgi-bin/api.cgi"
|
| 12 |
+
COUNCIL_ID = 1 # 1 for General Assembly, 2 for Security Council
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
def load_cache(filename):
|
| 16 |
+
"""Loads existing resolutions from the JSON cache file."""
|
| 17 |
+
if not os.path.exists(filename):
|
| 18 |
+
print(f"Cache file '{filename}' not found. Will start from scratch.")
|
| 19 |
+
return {}
|
| 20 |
+
|
| 21 |
+
try:
|
| 22 |
+
with open(filename, 'r', encoding='utf-8') as f:
|
| 23 |
+
resolutions_list = json.load(f)
|
| 24 |
+
# Convert list to a dictionary keyed by resolution ID for fast lookups
|
| 25 |
+
return {res['id']: res for res in resolutions_list}
|
| 26 |
+
except (json.JSONDecodeError, IOError) as e:
|
| 27 |
+
print(f"Error reading cache file '{filename}': {e}. Starting from scratch.")
|
| 28 |
+
return {}
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
def save_cache(filename, resolutions_dict):
|
| 32 |
+
"""Saves the resolutions dictionary to the JSON cache file."""
|
| 33 |
+
try:
|
| 34 |
+
# Convert the dictionary values back to a list and sort by ID
|
| 35 |
+
sorted_resolutions = sorted(resolutions_dict.values(), key=lambda r: r['id'])
|
| 36 |
+
with open(filename, 'w', encoding='utf-8') as f:
|
| 37 |
+
json.dump(sorted_resolutions, f, indent=2)
|
| 38 |
+
print(f"Successfully saved {len(sorted_resolutions)} resolutions to '{filename}'.")
|
| 39 |
+
except IOError as e:
|
| 40 |
+
print(f"Error writing to cache file '{filename}': {e}")
|
| 41 |
+
|
| 42 |
+
|
| 43 |
+
def parse_resolution_xml(xml_string):
|
| 44 |
"""
|
| 45 |
+
Parses a single XML string from the NationStates API into a structured dictionary.
|
|
|
|
| 46 |
|
| 47 |
Args:
|
| 48 |
+
xml_string: The XML content from the API response.
|
| 49 |
|
| 50 |
Returns:
|
| 51 |
+
A dictionary representing the resolution data, or None if parsing fails or resolution is empty.
|
| 52 |
"""
|
| 53 |
+
try:
|
| 54 |
+
root = ET.fromstring(xml_string)
|
| 55 |
+
res_node = root.find('RESOLUTION')
|
| 56 |
+
|
| 57 |
+
# If the RESOLUTION tag is empty, it means the resolution doesn't exist.
|
| 58 |
+
if res_node is None or not list(res_node):
|
| 59 |
+
return None
|
| 60 |
+
|
| 61 |
+
data = {}
|
| 62 |
+
# Iterate through all direct child tags of <RESOLUTION>
|
| 63 |
+
for child in res_node:
|
| 64 |
+
# Special case for COAUTHOR, which has multiple <N> children
|
| 65 |
+
if child.tag == 'COAUTHOR':
|
| 66 |
+
co_authors = [n.text for n in child.findall('N')]
|
| 67 |
+
if co_authors:
|
| 68 |
+
data['co_authors'] = co_authors
|
| 69 |
+
continue # Skip to the next tag
|
| 70 |
+
|
| 71 |
+
key = child.tag.lower()
|
| 72 |
+
value = child.text
|
| 73 |
+
|
| 74 |
+
# Try to convert numeric values to integers
|
| 75 |
+
try:
|
| 76 |
+
data[key] = int(value)
|
| 77 |
+
except (ValueError, TypeError):
|
| 78 |
+
data[key] = value
|
| 79 |
+
|
| 80 |
+
# --- Map API fields to desired dictionary structure ---
|
| 81 |
+
# Keep required fields with consistent naming
|
| 82 |
+
if 'name' in data: data['title'] = data.pop('name')
|
| 83 |
+
if 'resid' in data: data['id'] = data.pop('resid')
|
| 84 |
+
if 'desc' in data: data['body'] = data.pop('desc') # Keep BBCode as text
|
| 85 |
+
if 'councilid' in data: data['council'] = data.pop('councilid')
|
| 86 |
+
|
| 87 |
+
# Determine status and structure repeal information
|
| 88 |
+
if 'repealed_by' in data:
|
| 89 |
+
data['status'] = 'Repealed'
|
| 90 |
+
data['repealed_by'] = {
|
| 91 |
+
'id': data.pop('repealed_by'),
|
| 92 |
+
'timestamp': data.pop('repealed', None)
|
| 93 |
+
}
|
| 94 |
+
else:
|
| 95 |
+
data['status'] = 'Active'
|
| 96 |
+
|
| 97 |
+
# Structure info for resolutions that ARE repeals
|
| 98 |
+
if 'repeals_resid' in data:
|
| 99 |
+
data['repeals'] = {
|
| 100 |
+
'id': data.pop('repeals_resid'),
|
| 101 |
+
'council': data.pop('repeals_councilid')
|
| 102 |
+
}
|
| 103 |
+
|
| 104 |
+
return data
|
| 105 |
+
|
| 106 |
+
except ET.ParseError as e:
|
| 107 |
+
print(f"Error parsing XML: {e}")
|
| 108 |
return None
|
| 109 |
|
| 110 |
+
|
| 111 |
+
def main():
|
| 112 |
+
"""Main function to fetch, parse, and cache resolutions."""
|
| 113 |
+
print("--- World Assembly Resolution Fetcher ---")
|
| 114 |
+
|
| 115 |
+
# Load existing resolutions from cache
|
| 116 |
+
cached_resolutions = load_cache(CACHE_FILE)
|
| 117 |
+
if cached_resolutions:
|
| 118 |
+
# Find the latest resolution ID we already have and start from the next one
|
| 119 |
+
start_id = max(cached_resolutions.keys()) + 1
|
| 120 |
+
print(f"Loaded {len(cached_resolutions)} resolutions from cache. Starting fetch from GA#{start_id}.")
|
|
|
|
|
|
|
|
|
|
| 121 |
else:
|
| 122 |
+
start_id = 1
|
| 123 |
+
|
| 124 |
+
# --- API Request Loop ---
|
| 125 |
+
session = requests.Session()
|
| 126 |
+
session.headers.update({'User-Agent': USER_AGENT})
|
| 127 |
+
|
| 128 |
+
current_id = start_id
|
| 129 |
+
newly_fetched = []
|
| 130 |
+
|
| 131 |
+
rate_limit_info = {
|
| 132 |
+
'remaining': 50,
|
| 133 |
+
'reset_in': 30
|
| 134 |
+
}
|
| 135 |
+
|
| 136 |
+
while True:
|
| 137 |
+
# Check if we are about to exceed the rate limit
|
| 138 |
+
if rate_limit_info['remaining'] < 2:
|
| 139 |
+
wait_time = rate_limit_info['reset_in'] + 1 # Add a small buffer
|
| 140 |
+
print(f"Rate limit approaching. Waiting for {wait_time} seconds...")
|
| 141 |
+
time.sleep(wait_time)
|
| 142 |
+
|
| 143 |
+
print(f"Fetching resolution GA#{current_id}...")
|
| 144 |
+
|
| 145 |
+
params = {'wa': COUNCIL_ID, 'id': current_id, 'q': 'resolution'}
|
| 146 |
+
try:
|
| 147 |
+
response = session.get(API_BASE_URL, params=params, timeout=15)
|
| 148 |
+
|
| 149 |
+
# Update rate limit info from headers after every request
|
| 150 |
+
rate_limit_info['remaining'] = int(response.headers.get('RateLimit-Remaining', 50))
|
| 151 |
+
rate_limit_info['reset_in'] = int(response.headers.get('RateLimit-Reset', 30))
|
| 152 |
+
|
| 153 |
+
# Handle API responses
|
| 154 |
+
if response.status_code == 429:
|
| 155 |
+
retry_after = int(response.headers.get('Retry-After', 30))
|
| 156 |
+
print(f"Rate limit exceeded (429). Waiting for {retry_after} seconds as requested by API.")
|
| 157 |
+
time.sleep(retry_after)
|
| 158 |
+
continue # Retry the same ID
|
| 159 |
+
|
| 160 |
+
response.raise_for_status() # Raises an error for other bad responses (4xx or 5xx)
|
| 161 |
+
|
| 162 |
+
except requests.exceptions.RequestException as e:
|
| 163 |
+
print(f"An error occurred during request for GA#{current_id}: {e}")
|
| 164 |
+
print("Stopping script. Run again to resume.")
|
| 165 |
+
break
|
| 166 |
+
|
| 167 |
+
# Parse the response content
|
| 168 |
+
parsed_data = parse_resolution_xml(response.text)
|
| 169 |
+
|
| 170 |
+
if parsed_data:
|
| 171 |
+
newly_fetched.append(parsed_data)
|
| 172 |
+
current_id += 1
|
| 173 |
+
time.sleep(0.7) # Be polite: 50 requests/30s = 0.6s per request. Add a small delay.
|
| 174 |
+
else:
|
| 175 |
+
# API returns empty <RESOLUTION> for non-existent IDs, signaling we are done.
|
| 176 |
+
print(f"GA#{current_id} does not exist. Assuming it's the last one.")
|
| 177 |
+
print("--- Fetching complete. ---")
|
| 178 |
+
break
|
| 179 |
+
|
| 180 |
+
# --- Post-Fetch Processing ---
|
| 181 |
+
if not newly_fetched:
|
| 182 |
+
print("No new resolutions found. Cache is up-to-date.")
|
| 183 |
+
return
|
| 184 |
+
|
| 185 |
+
print(f"Fetched {len(newly_fetched)} new resolutions.")
|
| 186 |
+
|
| 187 |
+
# Update cache with new data
|
| 188 |
+
updates_made = 0
|
| 189 |
+
for res in newly_fetched:
|
| 190 |
+
# Check if this new resolution repeals an older one
|
| 191 |
+
if res['status'] == 'Repealed' and res.get('repealed_by'):
|
| 192 |
+
repealed_id = res['id']
|
| 193 |
+
# Check if we have the repealed resolution in our cache
|
| 194 |
+
if repealed_id in cached_resolutions and cached_resolutions[repealed_id]['status'] == 'Active':
|
| 195 |
+
print(
|
| 196 |
+
f"Updating status for GA#{repealed_id}: was Active, now Repealed by GA#{res['repealed_by']['id']}.")
|
| 197 |
+
cached_resolutions[repealed_id]['status'] = 'Repealed'
|
| 198 |
+
cached_resolutions[repealed_id]['repealed_by'] = res['repealed_by']
|
| 199 |
+
updates_made += 1
|
| 200 |
+
|
| 201 |
+
# Add the new resolution to our collection
|
| 202 |
+
cached_resolutions[res['id']] = res
|
| 203 |
+
|
| 204 |
+
if updates_made:
|
| 205 |
+
print(f"Updated the status of {updates_made} existing resolutions.")
|
| 206 |
+
|
| 207 |
+
# Save the final, complete collection to the cache file
|
| 208 |
+
save_cache(CACHE_FILE, cached_resolutions)
|
| 209 |
+
|
| 210 |
+
|
| 211 |
+
if __name__ == "__main__":
|
| 212 |
+
main()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|