updated openai, added pdt to pptx convert

This commit is contained in:
Alexander Karpov 2023-08-27 00:23:57 +03:00
parent 983f122a81
commit 1c58a20508
9 changed files with 201 additions and 60 deletions

View File

@ -1,4 +1,4 @@
exclude: "^docs/|/migrations/" exclude: "^docs/|/migrations/|^ml/"
default_stages: [commit] default_stages: [commit]
repos: repos:

View File

@ -9,27 +9,29 @@
regex = r"({(\n.+)+\n})" regex = r"({(\n.+)+\n})"
description = """🍀 Что такое Pitch-Deck? Pitch-Deck представляет собой презентацию-тизер проекта/компании для description = """
инвесторов, партнеров, журналистов и других заинтересованных лиц. Цель презентации - привлечение дополнительного 🍀 Кейсодержатель:
финансирования (инвестиций). Почему это проблема? ООО «Акселератор Возможностей» (https://ac-vo.ru/) при ИНТЦ МГУ «Воробьевы горы».
Организация технологических и инвестиционных мероприятий, курирование инновационной деятельности внутри ИНТЦ МГУ «Воробьевы горы»
🍀 Проблема #1. Недостаток средств: Для многих стартапов ограниченные финансы создают преграду при разработке Раскроем небольшую тайну венчура для привлечения денежных средств и защиты своего проекта, стартапу нужен Pitch-Deck.
качественного Pitch Deck. Отсутствие достаточных средств для найма профессиональных консультантов, дизайнеров и
копирайтеров, а также для проведения исследований рынка, может привести к созданию менее привлекательной и
малоинформативной презентации, что затрудняет привлечение инвестиций.
🍀 Проблема #2. Недостаток экспертизы: Проблемой для стартапов является недостаток экспертизы для проведения 🍀 Что такое Pitch-Deck?
необходимых исследований и корректного отражения их результатов в Pitch Deck. Не всегда у стартапов есть нужные Pitch-Deck представляет собой презентацию-тизер проекта/компании для инвесторов, партнеров, журналистов и других заинтересованных лиц. Цель презентации - привлечение дополнительного финансирования (инвестиций).
знания в области маркетинга, финансов и анализа рынка, что затрудняет создание убедительной и информативной Почему это проблема?
презентации для привлечения инвестиций.
🍀 Проблема #3. Недостаток времени Молодым компаниям для привлечения инвестиций требуется подготовить целый пакет 🍀 Проблема #1. Недостаток средств:
документов, одним из которых является Pitch Deck. Особенностью стартапов является сравнительного молодая и небольшая Для многих стартапов ограниченные финансы создают преграду при разработке качественного Pitch Deck. Отсутствие достаточных средств для найма профессиональных консультантов, дизайнеров и копирайтеров, а также для проведения исследований рынка, может привести к созданию менее привлекательной и малоинформативной презентации, что затрудняет привлечение инвестиций.
команда, у которой чисто физически не хватает времени на разработку инвестиционных материалов, ведь они полностью
погружены в процесс разработки и улучшения продукта или сервиса.
🍀 ИДЕЯ: Основная идея кейса заключается в создании вспомогательного инструмента на основе ИИ, заточенного под 🍀 Проблема #2. Недостаток экспертизы:
создание Pitch-Deck.""" Проблемой для стартапов является недостаток экспертизы для проведения необходимых исследований и корректного отражения их результатов в Pitch Deck. Не всегда у стартапов есть нужные знания в области маркетинга, финансов и анализа рынка, что затрудняет создание убедительной и информативной презентации для привлечения инвестиций.
🍀 Проблема #3. Недостаток времени
Молодым компаниям для привлечения инвестиций требуется подготовить целый пакет документов, одним из которых является Pitch Deck. Особенностью стартапов является сравнительного молодая и небольшая команда, у которой чисто физически не хватает времени на разработку инвестиционных материалов, ведь они полностью погружены в процесс разработки и улучшения продукта или сервиса.
🍀 ИДЕЯ:
Основная идея кейса заключается в создании вспомогательного инструмента на основе ИИ, заточенного под создание Pitch-Deck.
"""
names_prompt = """ names_prompt = """
По тексту ответь или предположи ответ на вопросы в следующем формате: По тексту ответь или предположи ответ на вопросы в следующем формате:
@ -42,11 +44,11 @@
""" """
По тексту ответь или предположи ответ на вопросы в следующем формате: По тексту ответь или предположи ответ на вопросы в следующем формате:
{ {
'users': 'Кто будет пользоваться продуктом', 'users': 'Кто будет пользоваться продуктом?',
'problems': 'Какие проблемы решает продукт', 'problems': 'Какие проблемы решает продукт?',
'actuality': 'Продолжите предложение: Актуальность проблемы подтверждается тем фактом, что...', 'actuality': 'Каким фактом обуславливается актуальность проблемы?',
'solve': 'Как решаем эти проблемы', 'solve': 'Как решаем эти проблемы?',
'works': 'Как работает решение', 'works': 'Как работает решение?',
} }
""", """,
""" """
@ -59,14 +61,15 @@
'financial_indicators': 'Напиши финансовые показатели проекта' 'financial_indicators': 'Напиши финансовые показатели проекта'
} }
""", """,
"""По тексту ответь или предположи ответ на вопросы в следующем формате: { 'achieve': 'Чего добьется команда после """
освоения инвестиций', 'competitors_strength': 'Сильные стороны конкурентов', 'competitors_low': 'Слабые стороны По тексту ответь или предположи ответ на вопросы в следующем формате:
конкурентов', 'advantages': 'Какие могут быть преимущества над конкурентами', 'category': "На каком рынке {
находится этот проект? Выбери из вариантов: 'Business Software', 'IndustrialTech', 'E-commerce', 'Advertising & 'achieve': 'Чего добьется команда после освоения инвестиций',
Marketing', 'Hardware', 'RetailTech', 'ConstructionTech', 'Web3', 'EdTech', 'Business Intelligence', 'competitors_strength': 'Сильные стороны конкурентов',
'Cybersecurity', 'HrTech', 'Telecom & Communication', 'Media & Entertainment', 'FinTech', 'MedTech', 'Transport & 'competitors_low': 'Слабые стороны конкурентов',
Logistics', 'Gaming', 'FoodTech', 'AI', 'WorkTech', 'Consumer Goods & Services', 'Aero & SpaceTech', 'advantages': 'Какие могут быть преимущества над конкурентами',
'Legal & RegTech', 'Travel', 'PropTech', 'Energy', 'GreenTech'" } , 'category': "На каком рынке находится этот проект? Выбери из вариантов: 'Business Software', 'IndustrialTech', 'E-commerce', 'Advertising & Marketing', 'Hardware', 'RetailTech', 'ConstructionTech', 'Web3', 'EdTech', 'Business Intelligence', 'Cybersecurity', 'HrTech', 'Telecom & Communication', 'Media & Entertainment', 'FinTech', 'MedTech', 'Transport & Logistics', 'Gaming', 'FoodTech', 'AI', 'WorkTech', 'Consumer Goods & Services', 'Aero & SpaceTech', 'Legal & RegTech', 'Travel', 'PropTech', 'Energy', 'GreenTech'"
}
""", """,
] ]
@ -103,10 +106,14 @@ def create_hints(description: str, stage: int):
messages=[{"role": "user", "content": description + "\n" + prompts[stage]}], messages=[{"role": "user", "content": description + "\n" + prompts[stage]}],
) )
str_content = chat_completion.choices[0].message.content str_content = chat_completion.choices[0].message.content
print(str_content) try:
filtered_content = list(re.finditer(regex, str_content, re.MULTILINE))[-1].group() filtered_content = list(re.finditer(regex, str_content, re.MULTILINE))[
-1
].group()
if not len(filtered_content): if not len(filtered_content):
raise ValueError(f"answer doesnt pass validation, {filtered_content}") raise ValueError(f"answer doesnt pass validation, {filtered_content}")
except:
raise ValueError(f"answer doesnt pass validation, {filtered_content}")
content = literal_eval(filtered_content) content = literal_eval(filtered_content)
for assertion_statement in assertions[stage]: for assertion_statement in assertions[stage]:
assert assertion_statement(content) assert assertion_statement(content)
@ -147,7 +154,7 @@ def create_name_hint(description: str):
answer = literal_eval(chat_completion.choices[0].message.content)["names"].split( answer = literal_eval(chat_completion.choices[0].message.content)["names"].split(
", " ", "
) )
assert len(answer) == 5 print(answer)
return {"type": "names", "value": answer} return {"type": "names", "value": answer}

View File

@ -5,6 +5,7 @@
from rest_framework.generics import get_object_or_404 from rest_framework.generics import get_object_or_404
from pitch_deck_generator.decks.models import ( from pitch_deck_generator.decks.models import (
PdfToPPTXStorage,
PitchDeck, PitchDeck,
Question, Question,
QuestionAnswer, QuestionAnswer,
@ -40,11 +41,17 @@ class HintSerializer(serializers.Serializer):
class PresentationAnswerSerializer(serializers.Serializer): class PresentationAnswerSerializer(serializers.Serializer):
slug = serializers.CharField() slug = serializers.CharField()
answer = serializers.JSONField() answer = serializers.JSONField()
photos = serializers.ListSerializer(child=serializers.ImageField())
class PitchDeckSlidePresentationSerializer(serializers.Serializer):
slide = serializers.IntegerField()
data = PresentationAnswerSerializer(many=True)
class PitchDeckPresentationSerializer(serializers.Serializer): class PitchDeckPresentationSerializer(serializers.Serializer):
slide = serializers.IntegerField() deck = BasePitchDeckSerializer()
data = PresentationAnswerSerializer(many=True) slide = PitchDeckSlidePresentationSerializer(many=True)
class QuestionSerializer(serializers.ModelSerializer): class QuestionSerializer(serializers.ModelSerializer):
@ -100,7 +107,7 @@ class Meta:
} }
def validate(self, data): def validate(self, data):
answer = data["answer"] answer = data["answer"] if "answer" in data else None
question = get_object_or_404( question = get_object_or_404(
Question, id=self.context["view"].kwargs["question_id"] Question, id=self.context["view"].kwargs["question_id"]
) )
@ -168,7 +175,7 @@ def validate(self, data):
if answer: if answer:
raise serializers.ValidationError("Answer should be blank") raise serializers.ValidationError("Answer should be blank")
for key, value in data.items(): for key, value in data.items():
if isinstance(value, InMemoryUploadedFile): if isinstance(value, (TemporaryUploadedFile, InMemoryUploadedFile)):
if "_" not in key: if "_" not in key:
raise serializers.ValidationError( raise serializers.ValidationError(
"You should use file_num for file keys" "You should use file_num for file keys"
@ -198,7 +205,7 @@ def validate(self, data):
len_f = 0 len_f = 0
for key, value in data.items(): for key, value in data.items():
if isinstance(value, TemporaryUploadedFile): if isinstance(value, (TemporaryUploadedFile, InMemoryUploadedFile)):
if "_" not in key: if "_" not in key:
raise serializers.ValidationError( raise serializers.ValidationError(
"You should use file_num for file keys" "You should use file_num for file keys"
@ -247,19 +254,27 @@ def create(self, validated_data):
q = QuestionAnswer.objects.get_or_create( q = QuestionAnswer.objects.get_or_create(
deck_id=validated_data["deck_id"], question_id=validated_data["question_id"] deck_id=validated_data["deck_id"], question_id=validated_data["question_id"]
)[0] )[0]
q.answer = validated_data["answer"] q.answer = validated_data["answer"] if "answer" in validated_data else {}
q.save() q.save()
s = [ s = [
key key
for key, val in validated_data.items() for key, val in validated_data.items()
if isinstance(val, TemporaryUploadedFile) and key != "file" if isinstance(val, (TemporaryUploadedFile, InMemoryUploadedFile))
and key != "file"
] ]
if "file" in validated_data: if "file" in validated_data:
QuestionAnswerPhoto.objects.create(answer=q, file=validated_data["file"]) QuestionAnswerPhoto.objects.create(answer=q, file=validated_data["file"])
elif s: if s:
s.sort(key=lambda x: int(x.split("_")[1])) s.sort(key=lambda x: int(x.split("_")[1]))
for key in s: for key in s:
QuestionAnswerPhoto.objects.create(answer=q, file=validated_data[key]) QuestionAnswerPhoto.objects.create(answer=q, file=validated_data[key])
return q return q
class PdfToPPTXSerializer(serializers.ModelSerializer):
class Meta:
model = PdfToPPTXStorage
fields = ["pdf", "pptx"]
extra_kwargs = {"pptx": {"read_only": True}}

View File

@ -1,6 +1,7 @@
from django.urls import path from django.urls import path
from pitch_deck_generator.decks.api.views import ( from pitch_deck_generator.decks.api.views import (
ConvertPdfToPPTXApiView,
CreateQuestionAnswerApiView, CreateQuestionAnswerApiView,
GetDeckPresentationDataApiView, GetDeckPresentationDataApiView,
GetDeckQuestionApiView, GetDeckQuestionApiView,
@ -15,6 +16,7 @@
urlpatterns = [ urlpatterns = [
path("", ListDecksApiView.as_view()), path("", ListDecksApiView.as_view()),
path("<int:id>", RetrievePitchApiView.as_view()), path("<int:id>", RetrievePitchApiView.as_view()),
path("pdf-to-pptx", ConvertPdfToPPTXApiView.as_view()),
path( path(
"question/<int:deck_id>/presentation", GetDeckPresentationDataApiView.as_view() "question/<int:deck_id>/presentation", GetDeckPresentationDataApiView.as_view()
), ),

View File

@ -7,6 +7,7 @@
AnswerSerializer, AnswerSerializer,
BasePitchDeckSerializer, BasePitchDeckSerializer,
HintSerializer, HintSerializer,
PdfToPPTXSerializer,
PitchDeckPresentationSerializer, PitchDeckPresentationSerializer,
PitchDeckSerializer, PitchDeckSerializer,
QuestionSerializer, QuestionSerializer,
@ -74,6 +75,7 @@ def get(self, request, *args, **kwargs):
class GetDeckPresentationDataApiView(generics.GenericAPIView): class GetDeckPresentationDataApiView(generics.GenericAPIView):
queryset = PitchDeck.objects.none()
serializer_class = PitchDeckPresentationSerializer serializer_class = PitchDeckPresentationSerializer
structure = { structure = {
@ -84,7 +86,7 @@ class GetDeckPresentationDataApiView(generics.GenericAPIView):
5: ["market_values", "users"], 5: ["market_values", "users"],
6: ["competitors", "competitors_strength", "competitors_low", "advantages"], 6: ["competitors", "competitors_strength", "competitors_low", "advantages"],
7: ["money", "finance_model"], 7: ["money", "finance_model"],
8: ["how_much_investments", "financial_indicators"], 8: ["how_much_investments", "financial_indicators", "users_metrics"],
9: ["your_role", "your_teammates", "past_investors"], 9: ["your_role", "your_teammates", "past_investors"],
10: ["how_much_investments", "time_to_spend", "investments_sold"], 10: ["how_much_investments", "time_to_spend", "investments_sold"],
11: ["company_value", "future_value", "time_to_spend"], 11: ["company_value", "future_value", "time_to_spend"],
@ -97,14 +99,27 @@ def get(self, request, *args, **kwargs):
PitchDeck, PitchDeck,
id=self.kwargs["deck_id"], id=self.kwargs["deck_id"],
) )
re_data = {
"deck": BasePitchDeckSerializer().to_representation(deck),
}
resp = [] resp = []
data = deck.questions data = deck.questions
for slide, tags in self.structure.items(): for slide, tags in self.structure.items():
slide_data = {"slide": slide, "data": []} slide_data = {"slide": slide, "data": []}
for tag in tags: for tag in tags:
slide_data["data"].append( b_data = {}
{"slug": tag, "answer": data[tag] if tag in data else {}} if tag in data:
) if "answer" in data[tag]:
resp.append(slide_data) b_data["answer"] = data[tag]["answer"]
if "photos" in data[tag]:
b_data["photos"] = data[tag]["photos"]
return Response(resp) slide_data["data"].append({"slug": tag, **b_data})
resp.append(slide_data)
re_data["slides"] = resp
return Response(re_data)
class ConvertPdfToPPTXApiView(generics.CreateAPIView):
serializer_class = PdfToPPTXSerializer
parser_classes = [FormParser, MultiPartParser]

View File

@ -0,0 +1,53 @@
# Generated by Django 4.2.4 on 2023-08-26 21:07
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("decks", "0007_alter_question_type_alter_questionanswer_deck"),
]
operations = [
migrations.CreateModel(
name="PdfToPPTXStorage",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("pdf", models.FileField(upload_to="pdf/")),
("pptx", models.FileField(blank=True, null=True, upload_to="pptx/")),
],
),
migrations.AlterField(
model_name="question",
name="type",
field=models.CharField(
choices=[
("text", "Text"),
("number", "Number"),
("text_array", "text array"),
("range", "Range"),
("multiple_range", "multiple range"),
("select", "Select"),
("link", "Link"),
("date", "Date"),
("multiple_date_description", "multiple date description"),
("photo", "Photo"),
("multiple_photo", "multiple photo"),
("photo_description", "photo description"),
("multiple_link_description", "multiple link description"),
("multiple_photo_description", "multiple photo description"),
("multiple_links", "multiple links"),
],
max_length=26,
),
),
]

View File

@ -85,3 +85,8 @@ class QuestionAnswerPhoto(models.Model):
"QuestionAnswer", related_name="photos", on_delete=models.CASCADE "QuestionAnswer", related_name="photos", on_delete=models.CASCADE
) )
file = models.ImageField(upload_to="uploads/") file = models.ImageField(upload_to="uploads/")
class PdfToPPTXStorage(models.Model):
pdf = models.FileField(upload_to="pdf/")
pptx = models.FileField(upload_to="pptx/", blank=True, null=True)

View File

@ -1,8 +1,18 @@
from io import BytesIO
import requests
from django.core.files import File
from django.db.models.signals import post_save, pre_save from django.db.models.signals import post_save, pre_save
from django.dispatch import receiver from django.dispatch import receiver
from pitch_deck_generator.decks.models import PitchDeck, QuestionAnswer from pitch_deck_generator.decks.models import (
PdfToPPTXStorage,
PitchDeck,
QuestionAnswer,
)
from pitch_deck_generator.decks.tasks import ( from pitch_deck_generator.decks.tasks import (
ML_HOST,
create_images_mokups,
generate_numeric_values, generate_numeric_values,
qenerate_answer_qr, qenerate_answer_qr,
run_pitch_deck_calculation, run_pitch_deck_calculation,
@ -23,9 +33,14 @@ def question_answer_create(sender, instance: QuestionAnswer, created, **kwargs):
generate_numeric_values.apply_async( generate_numeric_values.apply_async(
kwargs={"pk": instance.deck.pk}, countdown=1 kwargs={"pk": instance.deck.pk}, countdown=1
) )
elif instance.question.inner_tag == "names":
instance.deck.name = instance.answer
instance.deck.save()
elif instance.question.inner_tag in ["finance_model"]: elif instance.question.inner_tag in ["finance_model"]:
qenerate_answer_qr.apply_async(kwargs={"pk": instance.pk}, countdown=1) qenerate_answer_qr.apply_async(kwargs={"pk": instance.pk}, countdown=5)
save_answer_to_deck.apply_async(kwargs={"pk": instance.pk}, countdown=5) elif instance.question.inner_tag == "images":
create_images_mokups.apply_async(kwargs={"pk": instance.pk}, countdown=5)
save_answer_to_deck.apply_async(kwargs={"pk": instance.pk}, countdown=10)
@receiver(pre_save, sender=QuestionAnswer) @receiver(pre_save, sender=QuestionAnswer)
@ -36,5 +51,23 @@ def question_answer_update(sender, instance: QuestionAnswer, **kwargs):
kwargs={"pk": instance.deck.pk}, countdown=1 kwargs={"pk": instance.deck.pk}, countdown=1
) )
elif instance.question.inner_tag in ["finance_model"]: elif instance.question.inner_tag in ["finance_model"]:
qenerate_answer_qr.apply_async(kwargs={"pk": instance.pk}, countdown=1) qenerate_answer_qr.apply_async(kwargs={"pk": instance.pk}, countdown=5)
save_answer_to_deck.apply_async(kwargs={"pk": instance.pk}, countdown=5) elif instance.question.inner_tag == "images":
create_images_mokups.apply_async(kwargs={"pk": instance.pk}, countdown=5)
save_answer_to_deck.apply_async(kwargs={"pk": instance.pk}, countdown=10)
@receiver(post_save, sender=PdfToPPTXStorage)
def pdt_to_pptx_convert(sender, instance: PdfToPPTXStorage, created, **kwargs):
if created:
with open(instance.pdf.path, "rb") as f:
r = requests.post(ML_HOST + "convert-to-pptx", files={"in_file": f}).json()
data = requests.get(ML_HOST + r["file"][1:]).content
instance.pptx.save(
instance.pdf.path.split("/")[-1].replace("pdf", "pptx"),
File(
BytesIO(data),
instance.pdf.path.split("/")[-1].replace("pdf", "pptx"),
),
)
instance.save()

View File

@ -13,8 +13,9 @@
QuestionAnswerPhoto, QuestionAnswerPhoto,
QuestionDeckHint, QuestionDeckHint,
) )
from pitch_deck_generator.decks.services import get_image_mokeup
ML_HOST = "https://purple-kids-drive.loca.lt/" ML_HOST = "https://forty-eggs-slide.loca.lt/"
data_types = { data_types = {
"names": ("text", 1), "names": ("text", 1),
@ -166,3 +167,13 @@ def qenerate_answer_qr(pk: int):
answer=qa, answer=qa,
file=File(tmp, name="qr.png"), file=File(tmp, name="qr.png"),
) )
@shared_task
def create_images_mokups(pk: int):
qa = QuestionAnswer.objects.get(pk=pk)
for image in qa.photos.all():
mokup_path = get_image_mokeup(image.file.path)
with open(mokup_path, "rb") as f:
image.file = File(f, name="mokup.png")
image.save()