원클릭으로
officeqa-table-parser
Parse HTML and pipe-delimited tables from treasury bulletin files into clean labeled output
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
메뉴
Parse HTML and pipe-delimited tables from treasury bulletin files into clean labeled output
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
SOC 직업 분류 기준
Review board — verify the analyst's answer before final submission
Python computation formulas for Treasury data analysis. Use for percent change, CAGR, stdev, kurtosis, VaR, regression, Theil index, and other statistical calculations.
CPI-U annual averages (1929-2024) for inflation adjustment. Use when question mentions inflation, CPI, constant dollars, or real dollars.
How to read Treasury Bulletin data files in /app/resources/. Use when exploring resources or when JSON parsing fails.
U.S. fiscal year boundaries and calendar year conversions. Use when question mentions fiscal year, FY, or when you need to determine which months belong to a fiscal year.
How to accurately extract values from Treasury Bulletin tables. Use when reading tables with many columns, similar row labels, or when you need to verify you have the right cell.
| name | officeqa-table-parser |
| description | Parse HTML and pipe-delimited tables from treasury bulletin files into clean labeled output |
#!/usr/bin/env python3
"""Table parser: converts HTML and pipe tables in treasury .txt files to clean pipe-delimited output.
Usage: python3 /app/resources/t.py FILENAME [SEARCH_TERM]
- With no search term: prints all tables with headers
- With search term: prints only rows matching the term
"""
import sys, re, os
def parse_html_tables(html):
tables = []
for table_match in re.finditer(r'<table>(.*?)</table>', html, re.DOTALL):
table_html = table_match.group(1)
rows_raw = re.findall(r'<tr>(.*?)</tr>', table_html, re.DOTALL)
grid = []
pending = {}
for ri, row_html in enumerate(rows_raw):
cells = re.findall(r'<(th|td)(.*?)>(.*?)</(?:th|td)>', row_html, re.DOTALL)
row = []
ci = 0
while (ri, ci) in pending:
row.append(pending.pop((ri, ci)))
ci += 1
for tag, attrs, content in cells:
while (ri, ci) in pending:
row.append(pending.pop((ri, ci)))
ci += 1
val = re.sub(r'<[^>]+>', '', content).strip()
colspan = int(m.group(1)) if (m := re.search(r'colspan="(\d+)"', attrs)) else 1
rowspan = int(m.group(1)) if (m := re.search(r'rowspan="(\d+)"', attrs)) else 1
for c in range(colspan):
while (ri, ci) in pending:
row.append(pending.pop((ri, ci)))
ci += 1
row.append(val if c == 0 else "")
for r in range(1, rowspan):
pending[(ri + r, ci)] = val if c == 0 else ""
ci += 1
while (ri, ci) in pending:
row.append(pending.pop((ri, ci)))
ci += 1
grid.append(row)
if grid:
tables.append(grid)
return tables
def clean_num(val):
v = val.strip()
v = re.sub(r'\s*\d+/', '', v)
if v.startswith('(') and v.endswith(')'):
v = '-' + v[1:-1]
v = v.rstrip(' p')
return v
def format_table(grid, table_idx):
if not grid:
return ""
lines = []
headers = grid[0] if grid else []
merged_headers = list(headers)
data_start = 1
if len(grid) > 1:
row2 = grid[1]
non_empty = [c for c in row2 if c.strip()]
if non_empty and all(len(c) < 30 for c in non_empty):
numeric = sum(1 for c in non_empty if re.match(r'^[\d,.\-()$ ]+$', c.strip()))
if numeric < len(non_empty) / 2:
merged_headers = []
for i in range(max(len(headers), len(row2))):
h1 = headers[i].strip() if i < len(headers) else ""
h2 = row2[i].strip() if i < len(row2) else ""
if h1 and h2:
merged_headers.append(f"{h1} > {h2}")
else:
merged_headers.append(h1 or h2)
data_start = 2
lines.append(f"=== TABLE {table_idx + 1} ===")
lines.append(" | ".join(h.strip() for h in merged_headers))
lines.append("-" * 40)
for row in grid[data_start:]:
padded = row + [""] * (len(merged_headers) - len(row))
cleaned = [clean_num(c) for c in padded[:len(merged_headers)]]
if any(c.strip() for c in cleaned):
lines.append(" | ".join(c.strip() for c in cleaned))
return "\n".join(lines)
def parse_pipe_tables(text):
tables = []
lines = text.split("\n")
i = 0
while i < len(lines):
if re.match(r'^\|[\s-]+\|', lines[i]):
sep_idx = i
header_start = sep_idx - 1
while header_start > 0 and '|' in lines[header_start - 1] and lines[header_start - 1].strip().startswith('|'):
header_start -= 1
header_cells = []
for hi in range(header_start, sep_idx):
cells = [c.strip() for c in lines[hi].split('|')]
cells = [c for c in cells if c]
if not header_cells:
header_cells = cells
else:
for ci in range(min(len(header_cells), len(cells))):
if cells[ci] and cells[ci] != header_cells[ci]:
header_cells[ci] = f"{header_cells[ci]} > {cells[ci]}"
grid = [header_cells]
j = sep_idx + 1
while j < len(lines) and '|' in lines[j]:
cells = [c.strip() for c in lines[j].split('|')]
cells = [c for c in cells if c or len([x for x in cells if x]) > 1]
if cells and cells[0] == '':
cells = cells[1:]
if cells and cells[-1] == '':
cells = cells[:-1]
if cells:
grid.append(cells)
j += 1
if len(grid) > 1:
tables.append(grid)
i = j
else:
i += 1
return tables
def main():
if len(sys.argv) < 2:
print("Usage: python3 t.py FILENAME [SEARCH_TERM]")
sys.exit(1)
filepath = sys.argv[1]
search = sys.argv[2].lower() if len(sys.argv) > 2 else None
if not os.path.exists(filepath):
alt = os.path.join("/app/resources", filepath)
if os.path.exists(alt):
filepath = alt
else:
print(f"File not found: {filepath}")
sys.exit(1)
content = open(filepath).read()
tables = parse_html_tables(content)
if not tables:
tables = parse_pipe_tables(content)
if not tables:
print("No tables found. Raw content (first 3000 chars):")
print(content[:3000])
sys.exit(0)
for i, grid in enumerate(tables):
formatted = format_table(grid, i)
if search:
lines = formatted.split("\n")
header_lines = lines[:3]
matching = [l for l in lines[3:] if search in l.lower()]
if matching:
print("\n".join(header_lines))
for m in matching:
print(m)
print()
else:
print(formatted)
print()
if __name__ == "__main__":
main()
python3 /app/resources/t.py FILENAME # show all tables
python3 /app/resources/t.py FILENAME "1955" # show rows matching "1955"
python3 /app/resources/t.py FILENAME "defense" # show rows matching "defense"
python3 /app/resources/t.py FILENAME "army" # find army expenditures
IMPORTANT: The .txt files contain messy HTML/pipe tables. DO NOT grep or cat them for numbers — you WILL misread columns. Always use t.py first to get clean column-aligned output.
When a column header shows "Department > Sub-category" (e.g. "Defense Department > Military functions"), the sub-categories belong to the same department. Sum all sub-columns to get the department total.