Jomaric commited on
Commit
a71f292
Β·
0 Parent(s):

feat: initial release of curated portfolio app

Browse files
Files changed (3) hide show
  1. README.md +20 -0
  2. app.py +185 -0
  3. requirements.txt +3 -0
README.md ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: Version Diffing & Provenance Auditor
3
+ emoji: πŸ“Š
4
+ colorFrom: green
5
+ colorTo: cyan
6
+ sdk: gradio
7
+ sdk_version: 4.19.2
8
+ app_file: app.py
9
+ pinned: false
10
+ ---
11
+
12
+ # Version Diffing & Provenance Auditor
13
+
14
+ A premium computational social science application designed to analyze text version differences, track document provenance, and measure lexical drift across legislative bills, treaties, oral history transcripts, or manuscript revisions.
15
+
16
+ ### Features
17
+ - **Side-by-Side Visual Diffing**: Color-coded HTML grid highlighting insertions (green), modifications (yellow), and deletions (red).
18
+ - **Lexical Drift Analytics**: Computes sequence alignment ratios, Jaccard vocabulary similarity indices, and precise word mutations.
19
+ - **File Upload Support**: Drag-and-drop plain text files or paste text blocks directly.
20
+ - **Headless CPU Optimized**: Pure mathematical comparison algorithms that start in milliseconds.
app.py ADDED
@@ -0,0 +1,185 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import difflib
3
+ import re
4
+
5
+ def clean_and_tokenize(text):
6
+ # Normalize whitespaces
7
+ return re.findall(r'\b\w+\b|[^\w\s]', text)
8
+
9
+ def calculate_drift_metrics(text_a, text_b):
10
+ words_a = text_a.split()
11
+ words_b = text_b.split()
12
+
13
+ set_a = set(words_a)
14
+ set_b = set(words_b)
15
+
16
+ # Jaccard similarity
17
+ union = len(set_a.union(set_b))
18
+ intersection = len(set_a.intersection(set_b))
19
+ jaccard_sim = (intersection / union) * 100 if union > 0 else 100.0
20
+
21
+ # SequenceMatcher
22
+ matcher = difflib.SequenceMatcher(None, words_a, words_b)
23
+ ratio = matcher.ratio() * 100
24
+
25
+ # Edits breakdown
26
+ opcodes = matcher.get_opcodes()
27
+ insertions = 0
28
+ deletions = 0
29
+ replacements = 0
30
+
31
+ for tag, i1, i2, j1, j2 in opcodes:
32
+ if tag == 'insert':
33
+ insertions += (j2 - j1)
34
+ elif tag == 'delete':
35
+ deletions += (i2 - i1)
36
+ elif tag == 'replace':
37
+ replacements += max(i2 - i1, j2 - j1)
38
+
39
+ drift_percent = 100 - ratio
40
+
41
+ return {
42
+ "jaccard": round(jaccard_sim, 2),
43
+ "similarity": round(ratio, 2),
44
+ "drift": round(drift_percent, 2),
45
+ "insertions": insertions,
46
+ "deletions": deletions,
47
+ "replacements": replacements,
48
+ "words_a": len(words_a),
49
+ "words_b": len(words_b)
50
+ }
51
+
52
+ def generate_word_diff(text_a, text_b):
53
+ # Clean texts
54
+ words_a = text_a.splitlines()
55
+ words_b = text_b.splitlines()
56
+
57
+ # We will generate a beautiful side-by-side or inline HTML diff
58
+ # difflib.HtmlDiff is nice, but we want a highly premium custom HTML view
59
+ diff = difflib.HtmlDiff(wrapcolumn=60)
60
+ html_table = diff.make_table(words_a, words_b, context=True, numlines=3)
61
+
62
+ # Style the table to fit our glowing dark theme
63
+ styled_html = f"""
64
+ <style>
65
+ .diff_table {{ font-family: 'Inter', monospace; font-size: 13px; border-collapse: collapse; width: 100%; color: #e2e8f0; background: #0f172a; border-radius: 8px; overflow: hidden; }}
66
+ .diff_header {{ background: #1e293b; color: #94a3b8; font-weight: 600; padding: 8px; border-bottom: 2px solid #334155; }}
67
+ td.diff_header {{ text-align: right; background: #1e293b; color: #64748b; padding: 4px 8px; user-select: none; width: 40px; border-right: 1px solid #334155; }}
68
+ .diff_next {{ display: none; }}
69
+ .diff_add {{ background-color: rgba(16, 185, 129, 0.25) !important; color: #34d399 !important; text-decoration: none; }}
70
+ .diff_chg {{ background-color: rgba(245, 158, 11, 0.25) !important; color: #fbbf24 !important; text-decoration: none; }}
71
+ .diff_sub {{ background-color: rgba(239, 68, 68, 0.25) !important; color: #f87171 !important; text-decoration: line-through; }}
72
+ td {{ padding: 4px 12px; word-break: break-all; white-space: pre-wrap; }}
73
+ tr:hover td {{ background: #1e293b; }}
74
+ </style>
75
+ <div style="overflow-x: auto; border-radius: 8px; border: 1px solid #334155;">
76
+ {html_table}
77
+ </div>
78
+ """
79
+ return styled_html
80
+
81
+ def handle_compare(text_a, file_a, text_b, file_b):
82
+ # Retrieve values
83
+ final_a = text_a
84
+ if file_a is not None:
85
+ try:
86
+ with open(file_a.name, 'r', encoding='utf-8', errors='ignore') as f:
87
+ final_a = f.read()
88
+ except Exception as e:
89
+ final_a = f"Error reading File A: {str(e)}"
90
+
91
+ final_b = text_b
92
+ if file_b is not None:
93
+ try:
94
+ with open(file_b.name, 'r', encoding='utf-8', errors='ignore') as f:
95
+ final_b = f.read()
96
+ except Exception as e:
97
+ final_b = f"Error reading File B: {str(e)}"
98
+
99
+ if not final_a.strip() and not final_b.strip():
100
+ return "<div style='color: #ef4444; padding: 1rem;'>Please enter or upload text for both versions to compare.</div>", ""
101
+
102
+ # Calculate stats
103
+ stats = calculate_drift_metrics(final_a, final_b)
104
+
105
+ # HTML stats card
106
+ stats_html = f"""
107
+ <div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 1rem; margin-bottom: 1.5rem;">
108
+ <div style="background: #1e293b; border: 1px solid #334155; border-radius: 8px; padding: 1rem; text-align: center;">
109
+ <div style="font-size: 0.8rem; color: #94a3b8; font-weight: 500; text-transform: uppercase; letter-spacing: 0.05em;">Document Similarity</div>
110
+ <div style="font-size: 2rem; font-weight: 700; color: #10b981; margin-top: 0.25rem;">{stats['similarity']}%</div>
111
+ </div>
112
+ <div style="background: #1e293b; border: 1px solid #334155; border-radius: 8px; padding: 1rem; text-align: center;">
113
+ <div style="font-size: 0.8rem; color: #94a3b8; font-weight: 500; text-transform: uppercase; letter-spacing: 0.05em;">Lexical Drift Spread</div>
114
+ <div style="font-size: 2rem; font-weight: 700; color: #ef4444; margin-top: 0.25rem;">{stats['drift']}%</div>
115
+ </div>
116
+ <div style="background: #1e293b; border: 1px solid #334155; border-radius: 8px; padding: 1rem; text-align: center;">
117
+ <div style="font-size: 0.8rem; color: #94a3b8; font-weight: 500; text-transform: uppercase; letter-spacing: 0.05em;">Word Additions</div>
118
+ <div style="font-size: 2rem; font-weight: 700; color: #3b82f6; margin-top: 0.25rem;">+{stats['insertions']}</div>
119
+ </div>
120
+ <div style="background: #1e293b; border: 1px solid #334155; border-radius: 8px; padding: 1rem; text-align: center;">
121
+ <div style="font-size: 0.8rem; color: #94a3b8; font-weight: 500; text-transform: uppercase; letter-spacing: 0.05em;">Word Deletions</div>
122
+ <div style="font-size: 2rem; font-weight: 700; color: #f43f5e; margin-top: 0.25rem;">-{stats['deletions']}</div>
123
+ </div>
124
+ </div>
125
+ <div style="background: #1e293b; border: 1px solid #334155; border-radius: 8px; padding: 1rem; margin-bottom: 1.5rem; color: #cbd5e1;">
126
+ <h4 style="margin: 0 0 0.5rem 0; color: #e2e8f0;">Provenance Analysis Summary</h4>
127
+ <p style="margin: 0; font-size: 0.95rem; line-height: 1.5;">
128
+ Version A contains <strong>{stats['words_a']}</strong> words, and Version B contains <strong>{stats['words_b']}</strong> words.
129
+ There are <strong>{stats['replacements']}</strong> words modified in place.
130
+ The vocabulary overlap index (Jaccard similarity) is <strong>{stats['jaccard']}%</strong>.
131
+ This indicates a <strong>{"high" if stats['drift'] > 30 else "moderate" if stats['drift'] > 10 else "minimal"} degree of document mutation</strong> over successive drafts.
132
+ </p>
133
+ </div>
134
+ """
135
+
136
+ # Generate interactive table
137
+ diff_table = generate_word_diff(final_a, final_b)
138
+
139
+ return stats_html, diff_table
140
+
141
+ # Setup CSS styling for premium look
142
+ custom_css = """
143
+ body, .gradio-container { background-color: #0b0f19 !important; font-family: 'Inter', sans-serif !important; }
144
+ h1, h2, h3 { color: #f8fafc !important; }
145
+ .gr-button-primary { background: linear-gradient(135deg, #059669 0%, #10b981 100%) !important; border: none !important; }
146
+ .gr-button-primary:hover { opacity: 0.9 !important; }
147
+ """
148
+
149
+ # App Interface
150
+ with gr.Blocks(theme=gr.themes.Soft(primary_hue="emerald", secondary_hue="slate"), css=custom_css) as demo:
151
+ gr.HTML("""
152
+ <div style="text-align: center; margin-bottom: 2rem; border-bottom: 1px solid #1e293b; padding-bottom: 1.5rem;">
153
+ <h1 style="font-size: 2.2rem; font-weight: 800; background: linear-gradient(135deg, #10b981 0%, #3b82f6 100%); -webkit-background-clip: text; -webkit-text-fill-color: transparent;">πŸ“Š Version Diffing & Provenance Auditor</h1>
154
+ <p style="color: #94a3b8; font-size: 1.1rem; margin-top: 0.5rem;">Audit the evolution of policies, legislative bills, manuscripts, or transcripts with statistical drift analytics.</p>
155
+ </div>
156
+ """)
157
+
158
+ with gr.Row():
159
+ with gr.Column(scale=1):
160
+ gr.Markdown("### πŸ“œ Original Version (Draft A)")
161
+ text_input_a = gr.Textbox(label="Paste Text A", placeholder="Enter original draft here...", lines=10)
162
+ file_input_a = gr.File(label="Or Upload Text File A", file_types=[".txt"])
163
+
164
+ with gr.Column(scale=1):
165
+ gr.Markdown("### πŸ“ New Version (Draft B)")
166
+ text_input_b = gr.Textbox(label="Paste Text B", placeholder="Enter revised draft here...", lines=10)
167
+ file_input_b = gr.File(label="Or Upload Text File B", file_types=[".txt"])
168
+
169
+ compare_btn = gr.Button("πŸ” Audit Document Drift & Generate Diff Table", variant="primary")
170
+
171
+ gr.Markdown("### πŸ“ˆ Analytical Provenance Dashboard & Drift Metrics")
172
+ stats_output = gr.HTML(value="<div style='color: #64748b;'>Submit documents above to calculate version changes.</div>")
173
+
174
+ gr.Markdown("### πŸ” Color-Coded Structural Difference View")
175
+ diff_output = gr.HTML(value="<div style='color: #64748b;'>Visual diff will render here. Green represents additions, red represents deletions, and yellow represents replacements.</div>")
176
+
177
+ # Wiring events
178
+ compare_btn.click(
179
+ fn=handle_compare,
180
+ inputs=[text_input_a, file_input_a, text_input_b, file_input_b],
181
+ outputs=[stats_output, diff_output]
182
+ )
183
+
184
+ if __name__ == "__main__":
185
+ demo.launch()
requirements.txt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ gradio>=4.0.0
2
+ pandas
3
+ jinja2