Skip to content

Commit 04e789d

Browse files
committed
Merge branch 'dev'
2 parents 9a55194 + b50e05a commit 04e789d

File tree

6 files changed

+365
-109
lines changed

6 files changed

+365
-109
lines changed

CLAUDE.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# SimScore API Dev Guide
2+
3+
## Commands
4+
- **Setup:** `poetry install --no-root && poetry shell`
5+
- **Local Dev:** `supabase start && fastapi dev`
6+
- **Tests:**
7+
- All: `pytest tests/`
8+
- Single file: `pytest tests/api/v1/routes/test_ideas.py`
9+
- Single test: `pytest tests/api/v1/routes/test_ideas.py::test_function_name`
10+
- **Lint/Format:** `black app/ tests/`
11+
- **Type Check:** `mypy app/ tests/`
12+
13+
## Code Style
14+
- **Imports:** stdlib → third-party → project (alphabetically within groups)
15+
- **Naming:** `snake_case` for variables/functions, `PascalCase` for classes, `UPPER_CASE` for constants
16+
- **Types:** Use type annotations for all function parameters and return values
17+
- **Error Handling:** Use FastAPI `HTTPException` with appropriate status codes
18+
- **Documentation:** Google-style docstrings with triple double-quotes
19+
- **Architecture:** Follow FastAPI patterns with routes, models, services separation
20+
- **Formatting:** Project uses Black with default settings
21+
22+
## Environment
23+
This project uses Poetry for dependency management and FastAPI for the API framework.

app/api/v1/routes/ideas.py

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -93,8 +93,19 @@ async def rank_ideas(
9393
ideaRequest, response, user_id, ideas, plot_data, num_ideas, total_bytes
9494
)
9595

96-
print('Results calculated successfully!\n', response)
97-
return AnalysisResponse(**response)
96+
results = AnalysisResponse(**response)
97+
98+
print('Results calculated successfully!')
99+
if results.ranked_ideas:
100+
print('First 5 ranked ideas:', results.ranked_ideas[:5])
101+
if results.pairwise_similarity_matrix:
102+
print('First 5 similarity scores:', results.pairwise_similarity_matrix[:5])
103+
if results.cluster_names:
104+
print('First 5 cluster names:', results.cluster_names[:5])
105+
if results.relationship_graph:
106+
print('First 5 graph nodes & edges:', results.relationship_graph.nodes[:5], results.relationship_graph.edges[:5])
107+
108+
return results
98109

99110
def _generate_edges(ranked_ideas: List[RankedIdea], similarity_matrix: List[List[float]]) -> List[dict]:
100111
"""
@@ -134,12 +145,13 @@ async def build_base_response(ideas: List[str], results: Results, plot_data: Plo
134145

135146
ranked_ideas = [
136147
RankedIdea(
137-
id=str(idea_to_input[idea].id) if idea_to_input[idea].id is not None else str(idx),
148+
id=str(idea_to_input[idea].id) if idea_to_input[idea].id is not None else str(index),
138149
idea=idea,
139-
similarity_score=results["similarity"][idx],
140-
cluster_id=plot_data["kmeans_data"]["cluster"][idx],
150+
author_id=str(idea_to_input[idea].author_id) if idea_to_input[idea].author_id is not None else '',
151+
similarity_score=results["similarity"][index],
152+
cluster_id=plot_data["kmeans_data"]["cluster"][index],
141153
)
142-
for idx, idea in enumerate(results["ideas"])
154+
for index, idea in enumerate(results["ideas"])
143155
]
144156

145157
ranked_ideas.sort(key=lambda x: x.similarity_score, reverse=True)

aux_tools/convert_file.py

100644100755
Lines changed: 37 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -48,8 +48,9 @@ def convert_harmonica_to_request(
4848

4949
def convert_spreadsheet_to_request(
5050
file_path: str,
51-
id_column: str,
5251
data_column: str,
52+
id_column: str = None,
53+
author_column: str = None,
5354
advanced_features: AdvancedFeatures = {}
5455
) -> IdeaRequest:
5556
"""
@@ -60,8 +61,9 @@ def convert_spreadsheet_to_request(
6061
6162
Args:
6263
file_path: Path to the spreadsheet file (xlsx, csv, etc)
63-
id_column: Name of the column containing IDs
6464
data_column: Name of the column containing idea text
65+
id_column: Name of the column containing IDs
66+
author_column: Name of the column containing Author names
6567
advanced_features: Whether to include advanced analysis features
6668
6769
Returns:
@@ -74,18 +76,31 @@ def convert_spreadsheet_to_request(
7476
df = pd.read_excel(file_path)
7577

7678
# Validate columns exist
77-
if id_column not in df.columns or data_column not in df.columns:
78-
raise ValueError(f"Columns {id_column} and/or {data_column} not found in spreadsheet")
79+
if data_column not in df.columns:
80+
raise ValueError(f"Data Column {data_column} not found in spreadsheet")
81+
82+
7983

8084
# Convert rows to IdeaInput objects
81-
ideas = [
82-
IdeaInput(
83-
id=str(row[id_column]), # Convert to string to handle various ID formats
84-
idea=str(row[data_column]).strip(),
85-
)
86-
for _, row in df.iterrows()
87-
if pd.notna(row[data_column]) # Skip rows with empty ideas
88-
]
85+
ideas = []
86+
for idx, row in df.iterrows():
87+
if pd.notna(row[data_column]):
88+
idea_input = {
89+
'idea': str(row[data_column]).strip(),
90+
}
91+
92+
# Add ID if column specified and value exists
93+
if id_column and id_column in df.columns:
94+
idea_input['id'] = str(row[id_column]) if pd.notna(row[id_column]) else str(idx)
95+
else:
96+
idea_input['id'] = str(idx)
97+
98+
# Add author if column specified and value exists
99+
if author_column and author_column in df.columns and pd.notna(row[author_column]):
100+
idea_input['author_id'] = str(row[author_column])
101+
102+
ideas.append(IdeaInput(**idea_input))
103+
89104

90105
# Create request object
91106
request = IdeaRequest(
@@ -131,29 +146,35 @@ def convert_request_to_spreadsheet(request_data: dict, output_path: str = "outpu
131146
if __name__ == "__main__":
132147
import argparse
133148

149+
print("Starting conversion")
150+
134151
parser = argparse.ArgumentParser(description='Convert file to IdeaRequest format')
135152
parser.add_argument('file_path', help='Path to the input file (xlsx, csv, or json)')
136-
parser.add_argument('--id_column', help='Name of the column containing IDs (for spreadsheets)')
137153
parser.add_argument('--data_column', help='Name of the column containing idea text (for spreadsheets)')
154+
parser.add_argument('--id_column', help='Name of the column containing IDs (for spreadsheets)')
155+
parser.add_argument('--author_column', help='Name of the column containing Author names (for spreadsheets)')
138156
parser.add_argument('--save_as_spreadsheet', help='Save the output as spreadsheet (for json input)')
139157

140158
args = parser.parse_args()
141159

142160
try:
143161
if args.file_path.endswith('.json'):
162+
print("Convertin Harmonica JSON to SimScore JSON...")
144163
request = convert_harmonica_to_request(args.file_path,
145164
AdvancedFeatures(relationship_graph=True,
146165
pairwise_similarity_matrix=True,
147166
cluster_names=True)
148167
)
149168
print(f"Successfully converted {len(request.ideas)} paragraphs from chat data.")
150169
else:
151-
if not args.id_column or not args.data_column:
152-
raise ValueError("id_column and data_column are required for spreadsheet conversion")
170+
print("Converting Spreadsheet to SimScore")
171+
if not args.data_column:
172+
raise ValueError("data_column is required for spreadsheet conversion")
153173
request = convert_spreadsheet_to_request(
154174
file_path=args.file_path,
175+
data_column=args.data_column,
155176
id_column=args.id_column,
156-
data_column=args.data_column
177+
author_column=args.author_column
157178
)
158179
print(f"Successfully converted {len(request.ideas)} ideas from spreadsheet.")
159180

Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
<!DOCTYPE html>
2+
<html>
3+
<head>
4+
<base target="_top">
5+
<link href="https://fonts.googleapis.com/css2?family=Google+Sans:wght@400;500;700&display=swap" rel="stylesheet">
6+
<style>
7+
body {
8+
font-family: 'Google Sans', sans-serif;
9+
}
10+
</style>
11+
12+
<style>
13+
.form-group {
14+
margin: 10px 0;
15+
}
16+
select {
17+
width: 100%;
18+
padding: 5px;
19+
}
20+
.required {
21+
color: red;
22+
}
23+
</style>
24+
25+
<style>
26+
.spinner {
27+
width: 40px;
28+
height: 40px;
29+
border: 4px solid #f3f3f3;
30+
border-top: 4px solid #3498db;
31+
border-radius: 50%;
32+
animation: spin 1s linear infinite;
33+
margin: 20px auto;
34+
}
35+
36+
@keyframes spin {
37+
0% { transform: rotate(0deg); }
38+
100% { transform: rotate(360deg); }
39+
}
40+
41+
.loading-message {
42+
margin: 10px;
43+
font-style: italic;
44+
color: #666;
45+
}
46+
</style>
47+
48+
49+
</head>
50+
<body>
51+
<form onsubmit="handleSubmit(this); return false;">
52+
53+
<div class="form-group">
54+
<label>Ideas Column <span class="required">*</span></label>
55+
<select name="ideaColumn" required>
56+
<option value="">Select column...</option>
57+
<? headers.forEach(function(header) { ?>
58+
<option value="<?= header ?>"><?= header ?></option>
59+
<? }); ?>
60+
</select>
61+
</div>
62+
63+
<div class="form-group">
64+
<label>ID Column (optional)</label>
65+
<select name="idColumn">
66+
<option value="">None</option>
67+
<? headers.forEach(function(header) { ?>
68+
<option value="<?= header ?>"><?= header ?></option>
69+
<? }); ?>
70+
</select>
71+
</div>
72+
73+
<div class="form-group">
74+
<label>Author Column (optional)</label>
75+
<select name="authorColumn">
76+
<option value="">None</option>
77+
<? headers.forEach(function(header) { ?>
78+
<option value="<?= header ?>"><?= header ?></option>
79+
<? }); ?>
80+
</select>
81+
</div>
82+
83+
<div class="form-group">
84+
<label>Number of Ranked Results </label>
85+
<input type="number" name="resultCount" min="1" max="500" value="10" required>
86+
</div>
87+
88+
<button type="submit">Analyze</button>
89+
</form>
90+
91+
<div id="loadingIndicator" style="display: none; text-align: center; margin-top: 20px;">
92+
<div class="spinner"></div>
93+
<div id="loadingMessage" class="loading-message">Initializing SimScore analysis...</div>
94+
</div>
95+
96+
<script>
97+
const loadingMessages = [
98+
"Optimizing neural pathways...",
99+
"Calibrating relativity biases...",
100+
"Measuring cosine distances in vector space...",
101+
"Aligning semantic tensors...",
102+
"Computing similarity matrices...",
103+
"Discovering idea clusters...",
104+
"Calculating innovation potential...",
105+
"Synchronizing thought vectors..."
106+
];
107+
108+
function updateLoadingMessage() {
109+
const messageElement = document.getElementById('loadingMessage');
110+
let currentIndex = 0;
111+
112+
return setInterval(() => {
113+
messageElement.textContent = loadingMessages[currentIndex];
114+
currentIndex = (currentIndex + 1) % loadingMessages.length;
115+
}, 5000);
116+
}
117+
118+
function handleSubmit(form) {
119+
document.querySelector('form').style.display = 'none';
120+
document.getElementById('loadingIndicator').style.display = 'block';
121+
122+
// Start cycling through messages
123+
const messageInterval = updateLoadingMessage();
124+
125+
const data = {
126+
idColumn: form.idColumn.value,
127+
ideaColumn: form.ideaColumn.value,
128+
authorColumn: form.authorColumn.value,
129+
resultCount: parseInt(form.resultCount.value) || 10
130+
};
131+
132+
google.script.run
133+
.withSuccessHandler(() => {
134+
clearInterval(messageInterval);
135+
closeDialog();
136+
})
137+
.withFailureHandler((error) => {
138+
clearInterval(messageInterval);
139+
handleError(error);
140+
})
141+
.processSelectedColumns(data);
142+
}
143+
144+
function closeDialog() {
145+
google.script.host.close();
146+
}
147+
148+
function handleError(error) {
149+
// Show form again if there's an error
150+
document.querySelector('form').style.display = 'block';
151+
document.getElementById('loadingIndicator').style.display = 'none';
152+
showError(error);
153+
}
154+
155+
function showError(error) {
156+
alert('Error: ' + error);
157+
}
158+
</script>
159+
</body>
160+
</html>

0 commit comments

Comments
 (0)