-
Notifications
You must be signed in to change notification settings - Fork 4
Drop 10 work item across 1851 and 1873 story #19
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
kiranharidas187
merged 7 commits into
ELEVATE-Project:release-1.3.0
from
darshilbabel:drop_10_work
Aug 7, 2026
Merged
Changes from all commits
Commits
Show all changes
7 commits
Select commit
Hold shift + click to select a range
e6600e8
Drop 10 work item across 1851 and 1873 story
KUNALTEMPEST 953edb2
coderabbit fix
KUNALTEMPEST 5cec20e
passing metadata for websearch and kb search
KUNALTEMPEST 942b47e
adding export script and gotenberg setup update
KUNALTEMPEST a5f66b1
adding company_name and profile read api changes
KUNALTEMPEST 33ad789
Add strict request validation for companychat-feedback API (reject un…
KUNALTEMPEST fc082e6
coderaabit suggested change
KUNALTEMPEST File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
44 changes: 44 additions & 0 deletions
44
chatbot/migrations/0089_alter_chatsession_language_alter_story_language_and_more.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,44 @@ | ||
| # Generated by Django 5.2 on 2026-07-30 09:49 | ||
|
|
||
| import django.db.models.deletion | ||
| from django.db import migrations, models | ||
|
|
||
|
|
||
| class Migration(migrations.Migration): | ||
|
|
||
| dependencies = [ | ||
| ('chatbot', '0088_remove_botvernacular_bot_vernacu_company_483975_idx_and_more'), | ||
| ] | ||
|
|
||
| operations = [ | ||
| migrations.AlterField( | ||
| model_name='chatsession', | ||
| name='language', | ||
| field=models.CharField(choices=[('en', 'English'), ('hi', 'Hindi'), ('kn', 'Kannada'), ('te', 'Telugu'), ('or', 'Odia'), ('ta', 'Tamil')], default='en', max_length=1000), | ||
| ), | ||
| migrations.AlterField( | ||
| model_name='story', | ||
| name='language', | ||
| field=models.CharField(choices=[('en', 'English'), ('hi', 'Hindi'), ('kn', 'Kannada'), ('te', 'Telugu'), ('or', 'Odia'), ('ta', 'Tamil')], default='en', max_length=1000), | ||
| ), | ||
| migrations.AlterField( | ||
| model_name='storytranslation', | ||
| name='language', | ||
| field=models.CharField(choices=[('en', 'English'), ('hi', 'Hindi'), ('kn', 'Kannada'), ('te', 'Telugu'), ('or', 'Odia'), ('ta', 'Tamil')], max_length=10), | ||
| ), | ||
| migrations.CreateModel( | ||
| name='CompanyChatFeedback', | ||
| fields=[ | ||
| ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), | ||
| ('thumbs_up', models.BooleanField(default=False, help_text='True if the user gave a positive rating in this submission.')), | ||
| ('thumbs_down', models.BooleanField(default=False, help_text='True if the user gave a negative rating in this submission. Cannot be True at the same time as thumbs_up (enforced in the serializer).')), | ||
| ('comment', models.TextField(blank=True, help_text='Optional free-text feedback typed by the user.', null=True)), | ||
| ('created_at', models.DateTimeField(auto_now_add=True, help_text='When this feedback was submitted. Immutable — also used to determine the current state (latest row wins) and submission order.')), | ||
| ('company_chat', models.ForeignKey(help_text='The bot response (CompanyChat row) this feedback is about.', on_delete=django.db.models.deletion.CASCADE, related_name='feedbacks', to='chatbot.companychat')), | ||
| ], | ||
| options={ | ||
| 'ordering': ['-created_at'], | ||
| 'indexes': [models.Index(fields=['company_chat', '-created_at'], name='chatbot_com_company_f623f2_idx')], | ||
| }, | ||
| ), | ||
| ] |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,36 @@ | ||
| import codecs | ||
|
|
||
| from django.conf import settings | ||
| from rest_framework.exceptions import ParseError | ||
| from rest_framework.parsers import JSONParser | ||
| from rest_framework.utils import json | ||
|
|
||
|
|
||
| def _reject_duplicate_keys(pairs): | ||
| seen = set() | ||
| for key, _ in pairs: | ||
| if key in seen: | ||
| raise ParseError(f'Duplicate key "{key}" in request body.') | ||
| seen.add(key) | ||
| return dict(pairs) | ||
|
|
||
|
|
||
| class StrictJSONParser(JSONParser): | ||
| """Like DRF's JSONParser, but rejects a JSON object with duplicate keys instead of | ||
| silently keeping only the last occurrence.""" | ||
|
|
||
| def parse(self, stream, media_type=None, parser_context=None): | ||
| parser_context = parser_context or {} | ||
| encoding = parser_context.get('encoding', settings.DEFAULT_CHARSET) | ||
| try: | ||
| decoded_stream = codecs.getreader(encoding)(stream) | ||
| parse_constant = json.strict_constant if self.strict else None | ||
| return json.load( | ||
| decoded_stream, | ||
| object_pairs_hook=_reject_duplicate_keys, | ||
| parse_constant=parse_constant, | ||
| ) | ||
| except ParseError: | ||
| raise | ||
| except ValueError as exc: | ||
| raise ParseError('JSON parse error - %s' % str(exc)) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,170 @@ | ||
| # Standalone shell_plus script. | ||
| # | ||
| # Usage: open `python manage.py shell_plus`, paste this ENTIRE file, then run: | ||
| # | ||
| # export_chats_by_userid( | ||
| # input_csv='/path/to/phnNumberData.csv', | ||
| # output_xlsx='/path/to/output.xlsx', | ||
| # ) | ||
| # | ||
| # It's wrapped in exec("""...""") on purpose — some shell_plus setups (plain | ||
| # Python REPL inside tmux/screen, no bracketed paste) mis-parse a pasted | ||
| # multi-line script because blank lines inside a function body look like | ||
| # "end of block" to the incremental parser. Wrapping the whole body in a | ||
| # single string literal sidesteps that: the REPL just buffers lines until | ||
| # the closing triple-quote, then exec() runs it as one unit. | ||
| # | ||
| # Input CSV must have columns: Name, Phone Number, User Id | ||
| # (header names are matched case-insensitively, spaces/underscores ignored). | ||
| # | ||
| # For every input row, this fetches chatbot_profile by userid, then every | ||
| # CompanyChat where that profile is sender or receiver, and writes one xlsx | ||
| # row per chat message (all CompanyChat fields except other_params), with | ||
| # the original Name/Phone Number/User Id repeated on every row so it can be | ||
| # filtered/pivoted in Google Sheets. Rows whose userid doesn't map to a | ||
| # profile, or has no chats, still get exactly one output row with the chat | ||
| # columns left blank — a mapping_status column says why. | ||
|
|
||
| exec(""" | ||
| import csv | ||
| import pandas as pd | ||
| from django.db.models import Q | ||
| from chatbot.models import Profile | ||
| from chatbot.models.company_models import CompanyChat | ||
|
|
||
| COMPANY_CHAT_FIELDS = [ | ||
| 'id', 'session', 'message', 'translated_message', 'chunks', | ||
| 'sender_id', 'receiver_id', 'created_at', 'updated_at', 'status', | ||
| 'feedback', 'source', 'source_msg_id', 'whatsapp_message_id', | ||
| 'message_type', 'stage', 'file_url', 'audio_file', | ||
| ] # deliberately excludes other_params | ||
|
|
||
| EMPTY_CHAT_ROW = {f'chat_{f}': '' for f in COMPANY_CHAT_FIELDS} | ||
| EMPTY_CHAT_ROW['chat_file_url_https'] = '' | ||
|
|
||
|
|
||
| def _s3_to_https(value): | ||
| if value and value.startswith('s3://'): | ||
| return 'https://' + value[len('s3://'):] | ||
| return '' | ||
|
|
||
|
|
||
| def _read_input_rows(input_csv): | ||
| with open(input_csv, newline='', encoding='utf-8-sig') as f: | ||
| reader = csv.DictReader(f) | ||
| header_map = {} | ||
| for raw_name in reader.fieldnames or []: | ||
| key = raw_name.strip().lower().replace(' ', '').replace('_', '') | ||
| header_map[key] = raw_name | ||
|
|
||
| name_col = header_map.get('name') | ||
| phone_col = header_map.get('phonenumber') or header_map.get('phone') | ||
| userid_col = header_map.get('userid') | ||
|
|
||
| if not (name_col and phone_col and userid_col): | ||
| raise ValueError( | ||
| f"Could not find Name/Phone Number/User Id columns. " | ||
| f"Found headers: {reader.fieldnames}" | ||
| ) | ||
|
|
||
| rows = [] | ||
| for row in reader: | ||
| rows.append({ | ||
| 'csv_name': (row.get(name_col) or '').strip(), | ||
| 'csv_phone_number': (row.get(phone_col) or '').strip(), | ||
| 'csv_user_id': (row.get(userid_col) or '').strip(), | ||
| }) | ||
| return rows | ||
|
|
||
|
|
||
| def _chat_row(chat): | ||
| out = {} | ||
| for field in COMPANY_CHAT_FIELDS: | ||
| value = getattr(chat, field) | ||
| out[f'chat_{field}'] = str(value) if value not in (None, '') else '' | ||
| out['chat_file_url_https'] = _s3_to_https(out['chat_file_url']) | ||
| return out | ||
|
|
||
|
|
||
| def export_chats_by_userid(input_csv, output_xlsx): | ||
| input_rows = _read_input_rows(input_csv) | ||
| print(f"[export_chats_by_userid] read {len(input_rows)} input rows from {input_csv}") | ||
|
|
||
| output_rows = [] | ||
| stats = {'no_userid_in_csv': 0, 'profile_not_found': 0, 'profile_found_no_chats': 0, 'profile_found': 0} | ||
|
|
||
| for i, row in enumerate(input_rows, start=1): | ||
| userid = row['csv_user_id'] | ||
| base = { | ||
| 'name': row['csv_name'], | ||
| 'phone_number': row['csv_phone_number'], | ||
| 'user_id': userid, | ||
| } | ||
|
|
||
| if not userid: | ||
| stats['no_userid_in_csv'] += 1 | ||
| output_rows.append({**base, 'mapping_status': 'no_userid_in_csv', | ||
| 'profile_id': '', 'profile_first_name': '', 'profile_last_name': '', | ||
| 'profile_phone': '', 'profile_email': '', 'profile_status': '', | ||
| **EMPTY_CHAT_ROW}) | ||
| continue | ||
|
|
||
| profile = Profile.objects.filter(userid=userid).first() | ||
|
|
||
| if not profile: | ||
| stats['profile_not_found'] += 1 | ||
| output_rows.append({**base, 'mapping_status': 'profile_not_found', | ||
| 'profile_id': '', 'profile_first_name': '', 'profile_last_name': '', | ||
| 'profile_phone': '', 'profile_email': '', 'profile_status': '', | ||
| **EMPTY_CHAT_ROW}) | ||
| continue | ||
|
|
||
| profile_cols = { | ||
| 'profile_id': profile.id, | ||
| 'profile_first_name': profile.first_name or '', | ||
| 'profile_last_name': profile.last_name or '', | ||
| 'profile_phone': profile.phone or '', | ||
| 'profile_email': profile.email or '', | ||
| 'profile_status': profile.status or '', | ||
| } | ||
|
|
||
| chats = CompanyChat.objects.filter( | ||
| Q(sender_id=profile.id) | Q(receiver_id=profile.id) | ||
| ).order_by('session', 'created_at') | ||
|
|
||
| if not chats.exists(): | ||
| stats['profile_found_no_chats'] += 1 | ||
| output_rows.append({**base, 'mapping_status': 'profile_found_no_chats', | ||
| **profile_cols, **EMPTY_CHAT_ROW}) | ||
| continue | ||
|
|
||
| stats['profile_found'] += 1 | ||
| for chat in chats: | ||
| output_rows.append({**base, 'mapping_status': 'profile_found', | ||
| **profile_cols, **_chat_row(chat)}) | ||
|
|
||
| if i % 50 == 0: | ||
| print(f"[export_chats_by_userid] processed {i}/{len(input_rows)} input rows") | ||
|
|
||
| df = pd.DataFrame(output_rows) | ||
| df.to_excel(output_xlsx, index=False, engine='openpyxl') | ||
|
|
||
| print("[export_chats_by_userid] done") | ||
| print(f" input rows : {len(input_rows)}") | ||
| print(f" no_userid_in_csv : {stats['no_userid_in_csv']}") | ||
| print(f" profile_not_found : {stats['profile_not_found']}") | ||
| print(f" profile_found_no_chats: {stats['profile_found_no_chats']}") | ||
| print(f" profile_found : {stats['profile_found']}") | ||
| print(f" output rows (xlsx) : {len(output_rows)}") | ||
| print(f" written to : {output_xlsx}") | ||
|
|
||
| return df | ||
| """) | ||
|
|
||
| # ============================================================================ | ||
| # Run — edit the paths below before pasting into shell_plus | ||
| # ============================================================================ | ||
| export_chats_by_userid( | ||
| input_csv='/home/ubuntu/user_sample_data.csv', | ||
| output_xlsx='/home/ubuntu/sample_output.xlsx', | ||
| ) | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.