Initial commit
This commit is contained in:
@@ -0,0 +1,2 @@
|
||||
from edgar.offerings.formc import FormC, FundingPortal, Signer
|
||||
from edgar.offerings.formd import FormD
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
585
venv/lib/python3.10/site-packages/edgar/offerings/formc.py
Normal file
585
venv/lib/python3.10/site-packages/edgar/offerings/formc.py
Normal file
@@ -0,0 +1,585 @@
|
||||
from collections import defaultdict
|
||||
from datetime import date, datetime
|
||||
from functools import lru_cache
|
||||
from typing import List, Optional
|
||||
|
||||
from bs4 import BeautifulSoup
|
||||
from pydantic import BaseModel, ConfigDict
|
||||
from rich import box
|
||||
from rich.columns import Columns
|
||||
from rich.console import Group, Text
|
||||
from rich.panel import Panel
|
||||
from rich.table import Column, Table
|
||||
|
||||
from edgar._party import Address
|
||||
from edgar.core import get_bool
|
||||
from edgar.entity import Company
|
||||
from edgar.formatting import yes_no
|
||||
from edgar.reference import states
|
||||
from edgar.richtools import repr_rich
|
||||
from edgar.xmltools import child_text
|
||||
|
||||
__all__ = ['FormC', 'Signer', 'FundingPortal']
|
||||
|
||||
|
||||
class FilerInformation(BaseModel):
|
||||
model_config = ConfigDict(frozen=True)
|
||||
|
||||
cik: str
|
||||
ccc: str
|
||||
confirming_copy_flag: bool
|
||||
return_copy_flag: bool
|
||||
override_internet_flag: bool
|
||||
live_or_test: bool
|
||||
period: Optional[date] = None
|
||||
|
||||
@property
|
||||
@lru_cache(maxsize=1)
|
||||
def company(self):
|
||||
return Company(self.cik)
|
||||
|
||||
|
||||
class FundingPortal(BaseModel):
|
||||
"""The intermediary the company is using to raise funds"""
|
||||
|
||||
name: str
|
||||
cik: str
|
||||
crd: Optional[str]
|
||||
file_number: str
|
||||
|
||||
|
||||
class IssuerInformation(BaseModel):
|
||||
name: str
|
||||
address: Address
|
||||
website: str
|
||||
co_issuer: bool
|
||||
funding_portal: Optional[FundingPortal]
|
||||
legal_status: str
|
||||
jurisdiction: str
|
||||
date_of_incorporation: date
|
||||
|
||||
@property
|
||||
def incorporated(self):
|
||||
return f"{self.date_of_incorporation or ''} {self.jurisdiction or ''}"
|
||||
|
||||
|
||||
class OfferingInformation(BaseModel):
|
||||
"""
|
||||
<offeringInformation>
|
||||
<compensationAmount>A fee equal of 3% in cash of the aggregate amount raised by the Company, payable at each closing of the Offering.</compensationAmount>
|
||||
<financialInterest>No</financialInterest>
|
||||
<securityOfferedType>Other</securityOfferedType>
|
||||
<securityOfferedOtherDesc>Membership Interests</securityOfferedOtherDesc>
|
||||
<noOfSecurityOffered>41666</noOfSecurityOffered>
|
||||
<price>1.20000</price>
|
||||
<priceDeterminationMethod>Determined arbitrarily by the issuer</priceDeterminationMethod>
|
||||
<offeringAmount>50000.00</offeringAmount>
|
||||
<overSubscriptionAccepted>Y</overSubscriptionAccepted>
|
||||
<overSubscriptionAllocationType>First-come, first-served basis</overSubscriptionAllocationType>
|
||||
<descOverSubscription>At issuer's discretion, with priority given to StartEngine Owners</descOverSubscription>
|
||||
<maximumOfferingAmount>950000.00</maximumOfferingAmount>
|
||||
<deadlineDate>12-31-2024</deadlineDate>
|
||||
</offeringInformation>
|
||||
"""
|
||||
compensation_amount: str
|
||||
financial_interest: Optional[str]
|
||||
security_offered_type: Optional[str]
|
||||
security_offered_other_desc: Optional[str]
|
||||
no_of_security_offered: Optional[str]
|
||||
price: Optional[str]
|
||||
price_determination_method: Optional[str]
|
||||
offering_amount: Optional[float]
|
||||
over_subscription_accepted: Optional[str]
|
||||
over_subscription_allocation_type: Optional[str]
|
||||
desc_over_subscription: Optional[str]
|
||||
maximum_offering_amount: Optional[float]
|
||||
deadline_date: Optional[date]
|
||||
|
||||
|
||||
class AnnualReportDisclosure(BaseModel):
|
||||
"""
|
||||
<annualReportDisclosureRequirements>
|
||||
<currentEmployees>0.00</currentEmployees>
|
||||
<totalAssetMostRecentFiscalYear>0.00</totalAssetMostRecentFiscalYear>
|
||||
<totalAssetPriorFiscalYear>0.00</totalAssetPriorFiscalYear>
|
||||
<cashEquiMostRecentFiscalYear>0.00</cashEquiMostRecentFiscalYear>
|
||||
<cashEquiPriorFiscalYear>0.00</cashEquiPriorFiscalYear>
|
||||
<actReceivedMostRecentFiscalYear>0.00</actReceivedMostRecentFiscalYear>
|
||||
<actReceivedPriorFiscalYear>0.00</actReceivedPriorFiscalYear>
|
||||
<shortTermDebtMostRecentFiscalYear>0.00</shortTermDebtMostRecentFiscalYear>
|
||||
<shortTermDebtPriorFiscalYear>0.00</shortTermDebtPriorFiscalYear>
|
||||
<longTermDebtMostRecentFiscalYear>0.00</longTermDebtMostRecentFiscalYear>
|
||||
<longTermDebtPriorFiscalYear>0.00</longTermDebtPriorFiscalYear>
|
||||
<revenueMostRecentFiscalYear>0.00</revenueMostRecentFiscalYear>
|
||||
<revenuePriorFiscalYear>0.00</revenuePriorFiscalYear>
|
||||
<costGoodsSoldMostRecentFiscalYear>0.00</costGoodsSoldMostRecentFiscalYear>
|
||||
<costGoodsSoldPriorFiscalYear>0.00</costGoodsSoldPriorFiscalYear>
|
||||
<taxPaidMostRecentFiscalYear>0.00</taxPaidMostRecentFiscalYear>
|
||||
<taxPaidPriorFiscalYear>0.00</taxPaidPriorFiscalYear>
|
||||
<netIncomeMostRecentFiscalYear>0.00</netIncomeMostRecentFiscalYear>
|
||||
<netIncomePriorFiscalYear>0.00</netIncomePriorFiscalYear>
|
||||
</annualReportDisclosureRequirements>
|
||||
"""
|
||||
current_employees: int
|
||||
total_asset_most_recent_fiscal_year: float
|
||||
total_asset_prior_fiscal_year: float
|
||||
cash_equi_most_recent_fiscal_year: float
|
||||
cash_equi_prior_fiscal_year: float
|
||||
act_received_most_recent_fiscal_year: float
|
||||
act_received_prior_fiscal_year: float
|
||||
short_term_debt_most_recent_fiscal_year: float
|
||||
short_term_debt_prior_fiscal_year: float
|
||||
long_term_debt_most_recent_fiscal_year: float
|
||||
long_term_debt_prior_fiscal_year: float
|
||||
revenue_most_recent_fiscal_year: float
|
||||
revenue_prior_fiscal_year: float
|
||||
cost_goods_sold_most_recent_fiscal_year: float
|
||||
cost_goods_sold_prior_fiscal_year: float
|
||||
tax_paid_most_recent_fiscal_year: float
|
||||
tax_paid_prior_fiscal_year: float
|
||||
net_income_most_recent_fiscal_year: float
|
||||
net_income_prior_fiscal_year: float
|
||||
|
||||
offering_jurisdictions: List[str]
|
||||
|
||||
@property
|
||||
def is_offered_in_all_states(self):
|
||||
return set(self.offering_jurisdictions).issuperset(states.keys())
|
||||
|
||||
def __rich__(self):
|
||||
annual_report_table = Table(Column("", style='bold'), Column("Current Fiscal Year", style="bold"),
|
||||
Column("Previous Fiscal Year"),
|
||||
box=box.SIMPLE, row_styles=["", "bold"])
|
||||
annual_report_table.add_row("Current Employees", f"{self.current_employees:,.0f}", "")
|
||||
annual_report_table.add_row("Total Asset", f"${self.total_asset_most_recent_fiscal_year:,.2f}",
|
||||
f"${self.total_asset_prior_fiscal_year:,.2f}")
|
||||
annual_report_table.add_row("Cash Equivalent", f"${self.cash_equi_most_recent_fiscal_year:,.2f}",
|
||||
f"${self.cash_equi_prior_fiscal_year:,.2f}")
|
||||
annual_report_table.add_row("Accounts Receivable", f"${self.act_received_most_recent_fiscal_year:,.2f}",
|
||||
f"${self.act_received_prior_fiscal_year:,.2f}")
|
||||
annual_report_table.add_row("Short Term Debt", f"${self.short_term_debt_most_recent_fiscal_year:,.2f}",
|
||||
f"${self.short_term_debt_prior_fiscal_year:,.2f}")
|
||||
annual_report_table.add_row("Long Term Debt", f"${self.long_term_debt_most_recent_fiscal_year:,.2f}",
|
||||
f"${self.long_term_debt_prior_fiscal_year:,.2f}")
|
||||
annual_report_table.add_row("Revenue", f"${self.revenue_most_recent_fiscal_year:,.2f}",
|
||||
f"${self.revenue_prior_fiscal_year:,.2f}")
|
||||
annual_report_table.add_row("Cost of Goods Sold", f"${self.cost_goods_sold_most_recent_fiscal_year:,.2f}",
|
||||
f"${self.cost_goods_sold_prior_fiscal_year:,.2f}")
|
||||
annual_report_table.add_row("Tax Paid", f"${self.tax_paid_most_recent_fiscal_year:,.2f}",
|
||||
f"${self.tax_paid_prior_fiscal_year:,.2f}")
|
||||
annual_report_table.add_row("Net Income", f"${self.net_income_most_recent_fiscal_year:,.2f}",
|
||||
f"${self.net_income_prior_fiscal_year:,.2f}")
|
||||
|
||||
# Jurisdictions
|
||||
jurisdiction_table = Table(Column("Offered In", style="bold"), box=box.SIMPLE, row_styles=["", "bold"])
|
||||
if self.is_offered_in_all_states:
|
||||
juris_description = "All 50 States"
|
||||
jurisdiction_table.add_row(juris_description)
|
||||
else:
|
||||
jurisdiction_lists = split_list(self.offering_jurisdictions, chunk_size=25)
|
||||
for _index, jurisdictions in enumerate(jurisdiction_lists):
|
||||
jurisdiction_table.add_row(", ".join(jurisdictions))
|
||||
return Group(annual_report_table, jurisdiction_table)
|
||||
|
||||
def __repr__(self):
|
||||
return repr_rich(self.__rich__())
|
||||
|
||||
|
||||
class PersonSignature(BaseModel):
|
||||
signature: str
|
||||
title: str
|
||||
date: date
|
||||
|
||||
|
||||
class IssuerSignature(BaseModel):
|
||||
issuer: str
|
||||
title: str
|
||||
signature: str
|
||||
|
||||
|
||||
class Signer(BaseModel):
|
||||
name: str
|
||||
titles: List[str]
|
||||
|
||||
|
||||
class SignatureInfo(BaseModel):
|
||||
issuer_signature: IssuerSignature
|
||||
signatures: List[PersonSignature]
|
||||
|
||||
@property
|
||||
def signers(self) -> List[Signer]:
|
||||
signer_dict = defaultdict(list)
|
||||
for signature in self.signatures:
|
||||
signer_dict[signature.signature].append(signature.title)
|
||||
signer_dict[self.issuer_signature.signature].append(self.issuer_signature.title)
|
||||
return [Signer(name=name, titles=list(set(titles))) for name, titles in signer_dict.items()]
|
||||
|
||||
|
||||
def split_list(states, chunk_size=10):
|
||||
# Split a list into sublist of size chunk_size
|
||||
return [states[i:i + chunk_size] for i in range(0, len(states), chunk_size)]
|
||||
|
||||
|
||||
def maybe_float(value):
|
||||
if not value:
|
||||
return 0.00
|
||||
try:
|
||||
return float(value)
|
||||
except ValueError:
|
||||
return 0.00
|
||||
|
||||
|
||||
def maybe_date(value):
|
||||
if not value:
|
||||
return None
|
||||
try:
|
||||
return FormC.parse_date(value)
|
||||
except ValueError:
|
||||
return None
|
||||
|
||||
|
||||
class FormC:
|
||||
|
||||
def __init__(self,
|
||||
filer_information: FilerInformation,
|
||||
issuer_information: IssuerInformation,
|
||||
offering_information: Optional[OfferingInformation],
|
||||
annual_report_disclosure: Optional[AnnualReportDisclosure],
|
||||
signature_info: SignatureInfo,
|
||||
form: str):
|
||||
self.filer_information: FilerInformation = filer_information
|
||||
self.issuer_information: IssuerInformation = issuer_information
|
||||
self.offering_information: OfferingInformation = offering_information
|
||||
self.annual_report_disclosure: Optional[AnnualReportDisclosure] = annual_report_disclosure
|
||||
self.signature_info: SignatureInfo = signature_info
|
||||
self.form = form
|
||||
|
||||
@property
|
||||
def description(self):
|
||||
desc = ""
|
||||
if self.form == "C":
|
||||
desc = "Form C - Offering"
|
||||
elif self.form == "C/A":
|
||||
desc = "Form C/A - Offering Amendment"
|
||||
elif self.form == "C-U":
|
||||
desc = "Form C-U - Offering Progress Update"
|
||||
elif self.form == "C-U/A":
|
||||
desc = "Form C-U/A - Offering Progress Update Amendment"
|
||||
elif self.form == "C-AR":
|
||||
desc = "Form C-AR - Offering Annual Report"
|
||||
elif self.form == "C-AR/A":
|
||||
desc = "Form C-AR/A - Offering Annual Report Amendment"
|
||||
elif self.form == "C-TR":
|
||||
desc = "Form C-TR - Offering Termination Report"
|
||||
return desc
|
||||
|
||||
@staticmethod
|
||||
def parse_date(date_str) -> date:
|
||||
"""
|
||||
The date is in the format MM-DD-YYYY
|
||||
"""
|
||||
return datetime.strptime(date_str, "%m-%d-%Y").date()
|
||||
|
||||
@staticmethod
|
||||
def format_date(date_value: date):
|
||||
"""
|
||||
Format as April 1, 2021
|
||||
"""
|
||||
return date_value.strftime("%B %d, %Y")
|
||||
|
||||
@classmethod
|
||||
def from_xml(cls, offering_xml: str, form: str):
|
||||
soup = BeautifulSoup(offering_xml, "xml")
|
||||
root = soup.find('edgarSubmission')
|
||||
|
||||
# Header Data
|
||||
header_data = root.find('headerData')
|
||||
filer_info_el = header_data.find('filerInfo')
|
||||
|
||||
filer_el = filer_info_el.find('filer')
|
||||
|
||||
# Flags
|
||||
flags_tag = header_data.find('flags')
|
||||
confirming_copy_flag = child_text(flags_tag, 'confirmingCopyFlag') == 'true'
|
||||
return_copy_flag = child_text(flags_tag, 'returnCopyFlag') == 'true'
|
||||
override_internet_flag = child_text(flags_tag, 'overrideInternetFlag') == 'true'
|
||||
|
||||
period = child_text(header_data, 'period')
|
||||
filer_information = FilerInformation(
|
||||
cik=filer_el.find('filerCik').text,
|
||||
ccc=filer_el.find('filerCik').text,
|
||||
confirming_copy_flag=confirming_copy_flag,
|
||||
return_copy_flag=return_copy_flag,
|
||||
override_internet_flag=override_internet_flag,
|
||||
live_or_test=child_text(filer_el, 'testOrLive') == 'LIVE',
|
||||
period=FormC.parse_date(period) if period else None
|
||||
)
|
||||
|
||||
# Form
|
||||
form_data_tag = root.find('formData')
|
||||
|
||||
# Issuer Information
|
||||
issuer_information_tag = form_data_tag.find('issuerInformation')
|
||||
issuer_info_tag = issuer_information_tag.find('issuerInfo')
|
||||
issuer_address_tag = issuer_info_tag.find('issuerAddress')
|
||||
address = Address(
|
||||
street1=child_text(issuer_address_tag, 'street1'),
|
||||
street2=child_text(issuer_address_tag, 'street2'),
|
||||
city=child_text(issuer_address_tag, 'city'),
|
||||
state_or_country=child_text(issuer_address_tag, 'stateOrCountry'),
|
||||
zipcode=child_text(issuer_address_tag, 'zipCode')
|
||||
)
|
||||
|
||||
legal_status = child_text(issuer_info_tag, 'legalStatusForm')
|
||||
jurisdiction = child_text(issuer_info_tag, 'jurisdictionOrganization')
|
||||
date_of_incorporation = child_text(issuer_info_tag, 'dateIncorporation')
|
||||
|
||||
# Funding Portal data
|
||||
funding_portal_cik = child_text(issuer_information_tag, 'commissionCik')
|
||||
funding_portal = FundingPortal(
|
||||
name=child_text(issuer_information_tag, 'companyName'),
|
||||
cik=funding_portal_cik,
|
||||
file_number=child_text(issuer_information_tag, 'commissionFileNumber'),
|
||||
crd=child_text(issuer_information_tag, 'crdNumber')
|
||||
) if funding_portal_cik else None
|
||||
|
||||
issuer_information = IssuerInformation(
|
||||
name=child_text(issuer_info_tag, 'nameOfIssuer'),
|
||||
address=address,
|
||||
website=child_text(issuer_info_tag, 'issuerWebsite'),
|
||||
co_issuer=get_bool(child_text(issuer_information_tag, 'isCoIssuer')),
|
||||
funding_portal=funding_portal,
|
||||
legal_status=legal_status,
|
||||
jurisdiction=jurisdiction,
|
||||
date_of_incorporation=FormC.parse_date(date_of_incorporation)
|
||||
)
|
||||
|
||||
# Offering Information
|
||||
offering_info_tag = form_data_tag.find('offeringInformation')
|
||||
if offering_info_tag is not None and offering_info_tag.contents and offering_info_tag.get_text(strip=True):
|
||||
|
||||
offering_information = OfferingInformation(
|
||||
compensation_amount=child_text(offering_info_tag, 'compensationAmount'),
|
||||
financial_interest=child_text(offering_info_tag, 'financialInterest'),
|
||||
security_offered_type=child_text(offering_info_tag, 'securityOfferedType'),
|
||||
security_offered_other_desc=child_text(offering_info_tag, 'securityOfferedOtherDesc'),
|
||||
no_of_security_offered=child_text(offering_info_tag, 'noOfSecurityOffered'),
|
||||
price=child_text(offering_info_tag, 'price'),
|
||||
price_determination_method=child_text(offering_info_tag, 'priceDeterminationMethod'),
|
||||
offering_amount=maybe_float(child_text(offering_info_tag, 'offeringAmount')),
|
||||
over_subscription_accepted=child_text(offering_info_tag, 'overSubscriptionAccepted'),
|
||||
over_subscription_allocation_type=child_text(offering_info_tag, 'overSubscriptionAllocationType'),
|
||||
desc_over_subscription=child_text(offering_info_tag, 'descOverSubscription'),
|
||||
maximum_offering_amount=maybe_float(child_text(offering_info_tag, 'maximumOfferingAmount')),
|
||||
deadline_date=maybe_date(child_text(offering_info_tag, 'deadlineDate'))
|
||||
)
|
||||
else:
|
||||
offering_information = None
|
||||
|
||||
# Annual Report Disclosure
|
||||
annual_report_disclosure_tag = form_data_tag.find('annualReportDisclosureRequirements')
|
||||
# If the tag is not None and not Empty e.g. <annualReportDisclosureRequirements/>
|
||||
if annual_report_disclosure_tag and annual_report_disclosure_tag.contents:
|
||||
annual_report_disclosure = AnnualReportDisclosure(
|
||||
current_employees=int(float(child_text(annual_report_disclosure_tag, 'currentEmployees') or "0.00")),
|
||||
total_asset_most_recent_fiscal_year=maybe_float(child_text(annual_report_disclosure_tag,
|
||||
'totalAssetMostRecentFiscalYear')),
|
||||
total_asset_prior_fiscal_year=maybe_float(
|
||||
child_text(annual_report_disclosure_tag, 'totalAssetPriorFiscalYear')),
|
||||
cash_equi_most_recent_fiscal_year=maybe_float(
|
||||
child_text(annual_report_disclosure_tag, 'cashEquiMostRecentFiscalYear')),
|
||||
cash_equi_prior_fiscal_year=maybe_float(
|
||||
child_text(annual_report_disclosure_tag, 'cashEquiPriorFiscalYear')),
|
||||
act_received_most_recent_fiscal_year=maybe_float(child_text(annual_report_disclosure_tag,
|
||||
'actReceivedMostRecentFiscalYear')),
|
||||
act_received_prior_fiscal_year=maybe_float(
|
||||
child_text(annual_report_disclosure_tag, 'actReceivedPriorFiscalYear')),
|
||||
short_term_debt_most_recent_fiscal_year=maybe_float(child_text(annual_report_disclosure_tag,
|
||||
'shortTermDebtMostRecentFiscalYear')),
|
||||
short_term_debt_prior_fiscal_year=maybe_float(
|
||||
child_text(annual_report_disclosure_tag, 'shortTermDebtPriorFiscalYear')),
|
||||
long_term_debt_most_recent_fiscal_year=maybe_float(child_text(annual_report_disclosure_tag,
|
||||
'longTermDebtMostRecentFiscalYear')),
|
||||
long_term_debt_prior_fiscal_year=maybe_float(
|
||||
child_text(annual_report_disclosure_tag, 'longTermDebtPriorFiscalYear')),
|
||||
revenue_most_recent_fiscal_year=maybe_float(
|
||||
child_text(annual_report_disclosure_tag, 'revenueMostRecentFiscalYear')),
|
||||
revenue_prior_fiscal_year=maybe_float(
|
||||
child_text(annual_report_disclosure_tag, 'revenuePriorFiscalYear')),
|
||||
cost_goods_sold_most_recent_fiscal_year=maybe_float(child_text(annual_report_disclosure_tag,
|
||||
'costGoodsSoldMostRecentFiscalYear')),
|
||||
cost_goods_sold_prior_fiscal_year=maybe_float(
|
||||
child_text(annual_report_disclosure_tag, 'costGoodsSoldPriorFiscalYear')),
|
||||
tax_paid_most_recent_fiscal_year=maybe_float(
|
||||
child_text(annual_report_disclosure_tag, 'taxPaidMostRecentFiscalYear')),
|
||||
tax_paid_prior_fiscal_year=maybe_float(
|
||||
child_text(annual_report_disclosure_tag, 'taxPaidPriorFiscalYear')),
|
||||
net_income_most_recent_fiscal_year=maybe_float(child_text(annual_report_disclosure_tag,
|
||||
'netIncomeMostRecentFiscalYear')),
|
||||
net_income_prior_fiscal_year=maybe_float(
|
||||
child_text(annual_report_disclosure_tag, 'netIncomePriorFiscalYear')),
|
||||
offering_jurisdictions=[el.text for el in
|
||||
annual_report_disclosure_tag.find_all('issueJurisdictionSecuritiesOffering')]
|
||||
)
|
||||
else:
|
||||
annual_report_disclosure = None
|
||||
|
||||
# Signature Block
|
||||
signature_block_tag = root.find("signatureInfo")
|
||||
|
||||
issuer_signature_tag = signature_block_tag.find("issuerSignature")
|
||||
|
||||
signature_info = SignatureInfo(
|
||||
issuer_signature=IssuerSignature(
|
||||
issuer=child_text(issuer_signature_tag, "issuer"),
|
||||
signature=child_text(issuer_signature_tag, "issuerSignature"),
|
||||
title=child_text(issuer_signature_tag, "issuerTitle")
|
||||
),
|
||||
signatures=[
|
||||
PersonSignature(
|
||||
signature=child_text(person_signature_tag, "personSignature"),
|
||||
title=child_text(person_signature_tag, "personTitle"),
|
||||
date=FormC.parse_date(child_text(person_signature_tag, "signatureDate"))
|
||||
) for person_signature_tag in signature_block_tag.find_all('signaturePerson')
|
||||
]
|
||||
)
|
||||
|
||||
return cls(filer_information=filer_information,
|
||||
issuer_information=issuer_information,
|
||||
offering_information=offering_information,
|
||||
annual_report_disclosure=annual_report_disclosure,
|
||||
signature_info=signature_info,
|
||||
form=form)
|
||||
|
||||
def __rich__(self):
|
||||
|
||||
# Filer Panel
|
||||
filer_table = Table("Company", "CIK", box=box.SIMPLE)
|
||||
if self.filer_information.period:
|
||||
filer_table.add_column("Period")
|
||||
filer_table.add_row(self.filer_information.company.name, self.filer_information.cik,
|
||||
FormC.format_date(self.filer_information.period))
|
||||
else:
|
||||
filer_table.add_row(self.filer_information.company.name, self.filer_information.cik)
|
||||
filer_panel = Panel(filer_table, title=Text("Filer", style="bold deep_sky_blue1"), box=box.ROUNDED)
|
||||
|
||||
# Issuers
|
||||
issuer_table = Table(Column("Issuer", style="bold"), "Legal Status", "Incorporated", "Jurisdiction",
|
||||
box=box.SIMPLE)
|
||||
issuer_table.add_row(self.issuer_information.name,
|
||||
self.issuer_information.legal_status,
|
||||
FormC.format_date(self.issuer_information.date_of_incorporation),
|
||||
states.get(self.issuer_information.jurisdiction, self.issuer_information.jurisdiction))
|
||||
|
||||
# Address Panel
|
||||
address_panel = Panel(
|
||||
Text(str(self.issuer_information.address)),
|
||||
title='\U0001F3E2 Business Address', width=40)
|
||||
|
||||
# Address and website
|
||||
contact_columns = Columns([address_panel, Panel(Text(self.issuer_information.website), title="Website")])
|
||||
|
||||
issuer_panel = Panel(
|
||||
Group(*[issuer_table, contact_columns]),
|
||||
title=Text("Issuer", style="bold deep_sky_blue1"),
|
||||
box=box.ROUNDED
|
||||
)
|
||||
|
||||
# Funding Portal
|
||||
funding_portal_panel = None
|
||||
if self.issuer_information.funding_portal is not None:
|
||||
intermediary_table = Table(Column("Name", style="bold"), "CIK", "CRD Number", "File Number", box=box.SIMPLE)
|
||||
intermediary_table.add_row(
|
||||
self.issuer_information.funding_portal.name,
|
||||
self.issuer_information.funding_portal.cik,
|
||||
self.issuer_information.funding_portal.crd or "",
|
||||
self.issuer_information.funding_portal.file_number)
|
||||
funding_portal_panel = Panel(
|
||||
intermediary_table,
|
||||
title=Text("CrowdFunding Portal", style="bold deep_sky_blue1"),
|
||||
box=box.ROUNDED
|
||||
)
|
||||
|
||||
offering_panel = None
|
||||
|
||||
if self.offering_information:
|
||||
# Offering Information
|
||||
offering_table = Table(Column("", style='bold'), "", box=box.SIMPLE, row_styles=["", "bold"])
|
||||
offering_table.add_row("Compensation Amount", self.offering_information.compensation_amount)
|
||||
offering_table.add_row("Financial Interest", self.offering_information.financial_interest)
|
||||
offering_table.add_row("Type of Security", self.offering_information.security_offered_type)
|
||||
offering_table.add_row("Number of Securities", self.offering_information.no_of_security_offered)
|
||||
offering_table.add_row("Price", self.offering_information.price)
|
||||
offering_table.add_row("Price (or Method for Determining Price)",
|
||||
self.offering_information.price_determination_method)
|
||||
offering_table.add_row("Target Offering Amount", f"${self.offering_information.offering_amount:,.2f}")
|
||||
offering_table.add_row("Maximum Offering Amount",
|
||||
f"${self.offering_information.maximum_offering_amount:,.2f}")
|
||||
offering_table.add_row("Over-Subscription Accepted",
|
||||
yes_no(self.offering_information.over_subscription_accepted == "Y")),
|
||||
offering_table.add_row("How will over-subscriptions be allocated",
|
||||
self.offering_information.over_subscription_allocation_type)
|
||||
offering_table.add_row("Describe over-subscription plan",
|
||||
self.offering_information.desc_over_subscription)
|
||||
offering_table.add_row("Deadline Date", FormC.format_date(
|
||||
self.offering_information.deadline_date) if self.offering_information.deadline_date else "")
|
||||
|
||||
offering_panel = Panel(
|
||||
offering_table,
|
||||
title=Text("Offering Information", style="bold deep_sky_blue1"),
|
||||
box=box.ROUNDED
|
||||
)
|
||||
|
||||
# Annual Report Disclosure
|
||||
if self.annual_report_disclosure:
|
||||
annual_report_panel = Panel(
|
||||
self.annual_report_disclosure.__rich__(),
|
||||
title=Text("Annual Report Disclosures", style="bold deep_sky_blue1"),
|
||||
box=box.ROUNDED
|
||||
)
|
||||
else:
|
||||
annual_report_panel = None
|
||||
|
||||
# Signature Info
|
||||
# The signature of the issuer
|
||||
issuer_signature_table = Table(Column("Signature", style="bold"), "Title", "Issuer", box=box.SIMPLE)
|
||||
issuer_signature_table.add_row(self.signature_info.issuer_signature.signature,
|
||||
self.signature_info.issuer_signature.title,
|
||||
self.signature_info.issuer_signature.issuer)
|
||||
|
||||
# Person Signatures
|
||||
person_signature_table = Table(Column("Signature", style="bold"), "Title", "Date", box=box.SIMPLE,
|
||||
row_styles=["", "bold"])
|
||||
for signature in self.signature_info.signatures:
|
||||
person_signature_table.add_row(signature.signature, signature.title,
|
||||
FormC.format_date(signature.date))
|
||||
|
||||
signature_panel = Panel(
|
||||
Group(
|
||||
Panel(issuer_signature_table, box=box.ROUNDED, title="Issuer"),
|
||||
Panel(person_signature_table, box=box.ROUNDED, title="Persons")
|
||||
),
|
||||
title=Text("Signatures", style="bold deep_sky_blue1"),
|
||||
box=box.ROUNDED
|
||||
)
|
||||
|
||||
renderables = [
|
||||
filer_panel,
|
||||
issuer_panel,
|
||||
]
|
||||
if funding_portal_panel is not None:
|
||||
renderables.append(funding_portal_panel)
|
||||
if self.offering_information is not None:
|
||||
renderables.append(offering_panel)
|
||||
if self.annual_report_disclosure is not None:
|
||||
renderables.append(annual_report_panel)
|
||||
renderables.append(signature_panel)
|
||||
|
||||
panel = Panel(
|
||||
Group(*renderables),
|
||||
title=Text(self.description, style="bold dark_sea_green4"),
|
||||
)
|
||||
return panel
|
||||
|
||||
def __repr__(self):
|
||||
return repr_rich(self.__rich__())
|
||||
473
venv/lib/python3.10/site-packages/edgar/offerings/formd.py
Normal file
473
venv/lib/python3.10/site-packages/edgar/offerings/formd.py
Normal file
@@ -0,0 +1,473 @@
|
||||
import re
|
||||
from typing import List, Optional
|
||||
|
||||
from bs4 import BeautifulSoup, Tag
|
||||
from pydantic import BaseModel
|
||||
from rich import box
|
||||
from rich.columns import Columns
|
||||
from rich.console import Group, RenderableType, Text
|
||||
from rich.panel import Panel
|
||||
from rich.table import Table
|
||||
|
||||
from edgar._party import Address, Issuer, Person
|
||||
from edgar.richtools import repr_rich
|
||||
from edgar.xmltools import child_text, child_value
|
||||
|
||||
__all__ = [
|
||||
'FormD',
|
||||
]
|
||||
|
||||
|
||||
class Filer(BaseModel):
|
||||
cik: str
|
||||
ccc: str
|
||||
|
||||
|
||||
class BusinessCombinationTransaction(BaseModel):
|
||||
is_business_combination: bool
|
||||
clarification_of_response: Optional[str]
|
||||
|
||||
|
||||
class OfferingSalesAmounts(BaseModel):
|
||||
total_offering_amount: object
|
||||
total_amount_sold: object
|
||||
total_remaining: object
|
||||
clarification_of_response: Optional[str]
|
||||
|
||||
|
||||
class Investors(BaseModel):
|
||||
has_non_accredited_investors: bool
|
||||
total_already_invested: object
|
||||
|
||||
|
||||
class SalesCommissionFindersFees(BaseModel):
|
||||
sales_commission: object
|
||||
finders_fees: object
|
||||
clarification_of_response: Optional[str]
|
||||
|
||||
|
||||
class InvestmentFundInfo(BaseModel):
|
||||
investment_fund_type: str
|
||||
is_40_act: bool
|
||||
|
||||
|
||||
class IndustryGroup(BaseModel):
|
||||
industry_group_type: str
|
||||
investment_fund_info: Optional[InvestmentFundInfo] = None
|
||||
|
||||
|
||||
class UseOfProceeds(BaseModel):
|
||||
gross_proceeds_used: object
|
||||
clarification_of_response: Optional[str]
|
||||
|
||||
|
||||
class Signature(BaseModel):
|
||||
issuer_name: str
|
||||
signature_name: str
|
||||
name_of_signer: str
|
||||
title: Optional[str] = None
|
||||
date: Optional[str] = None
|
||||
|
||||
|
||||
class SignatureBlock(BaseModel):
|
||||
authorized_representative: bool
|
||||
signatures: List[Signature]
|
||||
|
||||
|
||||
class SalesCompensationRecipient:
|
||||
"""
|
||||
<recipient>
|
||||
<recipientName>Charles Harrison</recipientName>
|
||||
<recipientCRDNumber>3071551</recipientCRDNumber>
|
||||
<associatedBDName>H & L Equities, LLC</associatedBDName>
|
||||
<associatedBDCRDNumber>113794</associatedBDCRDNumber>
|
||||
<recipientAddress>
|
||||
<street1>1175 Peachtree St. NE, Suite 2200</street1>
|
||||
<city>Atlanta</city>
|
||||
<stateOrCountry>GA</stateOrCountry>
|
||||
<stateOrCountryDescription>GEORGIA</stateOrCountryDescription>
|
||||
<zipCode>30361</zipCode>
|
||||
</recipientAddress>
|
||||
<statesOfSolicitationList>
|
||||
<state>FL</state>
|
||||
<description>FLORIDA</description>
|
||||
<state>GA</state>
|
||||
<description>GEORGIA</description>
|
||||
<state>TX</state>
|
||||
<description>TEXAS</description>
|
||||
</statesOfSolicitationList>
|
||||
<foreignSolicitation>false</foreignSolicitation>
|
||||
</recipient>
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
name: str,
|
||||
crd: str,
|
||||
associated_bd_name: str,
|
||||
associated_bd_crd: str,
|
||||
address: Address,
|
||||
states_of_solicitation: List[str] = None):
|
||||
self.name: str = name
|
||||
self.crd: str = crd
|
||||
self.associated_bd_name: associated_bd_name
|
||||
self.associated_bd_crd: str = associated_bd_crd
|
||||
self.address: Address = address
|
||||
self.states_of_solicitation: List[str] = states_of_solicitation
|
||||
|
||||
@classmethod
|
||||
def from_xml(cls,
|
||||
recipient_tag: Tag):
|
||||
# Name and Crd can be "None"
|
||||
name = re.sub("None", "", child_text(recipient_tag, "recipientName") or "")
|
||||
crd = re.sub("None", "", child_text(recipient_tag, "recipientCRDNumber") or "")
|
||||
associated_bd_name = re.sub("None", "", child_text(recipient_tag, "associatedBDName") or "", flags=re.IGNORECASE)
|
||||
associated_bd_crd = re.sub("None", "", child_text(recipient_tag, "associatedBDCRDNumber") or "", flags=re.IGNORECASE)
|
||||
|
||||
address_tag = recipient_tag.find("recipientAddress")
|
||||
address = Address(
|
||||
street1=child_text(address_tag, "street1"),
|
||||
street2=child_text(address_tag, "street2"),
|
||||
city=child_text(address_tag, "city"),
|
||||
state_or_country=child_text(address_tag, "stateOrCountry"),
|
||||
state_or_country_description=child_text(address_tag, "stateOrCountryDescription"),
|
||||
zipcode=child_text(address_tag, "30361")
|
||||
) if address_tag else None
|
||||
|
||||
# States of Solicitation List
|
||||
states_of_solicitation_tag = recipient_tag.find("statesOfSolicitationList")
|
||||
# Add individual states
|
||||
states_of_solicitation = [el.text for el in
|
||||
states_of_solicitation_tag.find_all("state")] if states_of_solicitation_tag else []
|
||||
# Sometimes there are no states but there are values e.g. <value>All States</value>
|
||||
solicitation_values = [el.text for el in
|
||||
states_of_solicitation_tag.find_all("value")] if states_of_solicitation_tag else []
|
||||
states_of_solicitation += solicitation_values
|
||||
|
||||
return cls(
|
||||
name=name,
|
||||
crd=crd,
|
||||
associated_bd_name=associated_bd_name,
|
||||
associated_bd_crd=associated_bd_crd,
|
||||
address=address,
|
||||
states_of_solicitation=states_of_solicitation
|
||||
)
|
||||
|
||||
|
||||
class OfferingData:
|
||||
|
||||
def __init__(self,
|
||||
industry_group: IndustryGroup,
|
||||
revenue_range: str,
|
||||
federal_exemptions: List[str],
|
||||
is_new: bool,
|
||||
date_of_first_sale: str,
|
||||
more_than_one_year: bool,
|
||||
is_equity: bool,
|
||||
is_pooled_investment: bool,
|
||||
business_combination_transaction: BusinessCombinationTransaction,
|
||||
minimum_investment: str,
|
||||
sales_compensation_recipients: List[SalesCompensationRecipient] = None,
|
||||
offering_sales_amounts: OfferingSalesAmounts = None,
|
||||
investors: Investors = None,
|
||||
sales_commission_finders_fees: SalesCommissionFindersFees = None,
|
||||
use_of_proceeds: UseOfProceeds = None):
|
||||
self.industry_group: IndustryGroup = industry_group
|
||||
self.revenue_range: str = revenue_range
|
||||
self.federal_exemptions: List[str] = federal_exemptions
|
||||
self.is_new: bool = is_new
|
||||
self.date_of_first_sale: str = date_of_first_sale
|
||||
self.more_than_one_year: bool = more_than_one_year
|
||||
self.is_equity = is_equity
|
||||
self.is_pooled_investment = is_pooled_investment
|
||||
self.business_combination_transaction: BusinessCombinationTransaction = business_combination_transaction
|
||||
self.minimum_investment = minimum_investment
|
||||
self.sales_compensation_recipients: List[SalesCompensationRecipient] = sales_compensation_recipients or []
|
||||
self.offering_sales_amounts = offering_sales_amounts
|
||||
self.investors: Investors = investors
|
||||
self.sales_commission_finders_fees: SalesCommissionFindersFees = sales_commission_finders_fees
|
||||
self.use_of_proceeds: UseOfProceeds = use_of_proceeds
|
||||
|
||||
@classmethod
|
||||
def from_xml(cls, offering_data_el: Tag):
|
||||
# industryGroup
|
||||
industry_group_el = offering_data_el.find("industryGroup")
|
||||
industry_group_type = child_text(industry_group_el, "industryGroupType") if industry_group_el else ""
|
||||
investment_fund_info_el = industry_group_el.find("investmentFundInfo")
|
||||
investment_fund_info = InvestmentFundInfo(
|
||||
investment_fund_type=child_text(investment_fund_info_el, "investmentFundType"),
|
||||
is_40_act=child_text(investment_fund_info_el, "is40Act") == "true"
|
||||
) if investment_fund_info_el else None
|
||||
|
||||
industry_group = IndustryGroup(industry_group_type=industry_group_type,
|
||||
investment_fund_info=investment_fund_info)
|
||||
|
||||
issuer_size_el = offering_data_el.find("issuerSize")
|
||||
revenue_range = child_text(issuer_size_el, "revenueRange")
|
||||
|
||||
fed_exemptions_el = offering_data_el.find("federalExemptionsExclusions")
|
||||
federal_exemptions = [item_el.text
|
||||
for item_el
|
||||
in fed_exemptions_el.find_all("item")] if fed_exemptions_el else []
|
||||
|
||||
# type of filing
|
||||
type_of_filing_el = offering_data_el.find("typeOfFiling")
|
||||
new_or_amendment_el = type_of_filing_el.find("newOrAmendment")
|
||||
new_or_amendment = new_or_amendment_el and child_text(new_or_amendment_el, "isAmendment") == "true"
|
||||
date_of_first_sale = child_value(type_of_filing_el, "dateOfFirstSale")
|
||||
|
||||
# Duration of transaction
|
||||
duration_of_offering_el = offering_data_el.find("durationOfOffering")
|
||||
more_than_one_year = duration_of_offering_el and child_text(duration_of_offering_el,
|
||||
"moreThanOneYear") == "true"
|
||||
|
||||
# Type of security
|
||||
type_of_seurity_el = offering_data_el.find("typesOfSecuritiesOffered")
|
||||
is_equity = child_text(type_of_seurity_el, "isEquityType") == "true"
|
||||
is_pooled_investment = child_text(type_of_seurity_el, "isPooledInvestmentFundType") == "true"
|
||||
|
||||
# Businss combination
|
||||
bus_combination_el = offering_data_el.find("businessCombinationTransaction")
|
||||
business_combination_transaction = BusinessCombinationTransaction(
|
||||
is_business_combination=bus_combination_el and child_text(bus_combination_el,
|
||||
"isBusinessCombinationTransaction") == "true",
|
||||
clarification_of_response=child_text(bus_combination_el, "clarificationOfResponse")
|
||||
) if bus_combination_el else None
|
||||
|
||||
# Minimum investment
|
||||
minimum_investment = child_text(offering_data_el, "minimumInvestmentAccepted")
|
||||
|
||||
# Sales Compensation List
|
||||
sales_compensation_tag = offering_data_el.find("salesCompensationList")
|
||||
sales_compensation_recipients = [
|
||||
SalesCompensationRecipient.from_xml(el)
|
||||
for el in sales_compensation_tag.find_all("recipient")
|
||||
] if sales_compensation_tag else []
|
||||
|
||||
# Offering Sales Amount
|
||||
offering_sales_amount_tag: Optional[Tag] = offering_data_el.find("offeringSalesAmounts")
|
||||
offering_sales_amounts = OfferingSalesAmounts(
|
||||
total_offering_amount=child_text(offering_sales_amount_tag, "totalOfferingAmount"),
|
||||
total_amount_sold=child_text(offering_sales_amount_tag, "totalAmountSold"),
|
||||
total_remaining=child_text(offering_sales_amount_tag, "totalRemaining"),
|
||||
clarification_of_response=child_text(offering_sales_amount_tag, "clarificationOfResponse")
|
||||
) if offering_sales_amount_tag else None
|
||||
|
||||
# investors
|
||||
investors_tag: Optional[Tag] = offering_data_el.find("investors")
|
||||
investors = Investors(
|
||||
has_non_accredited_investors=child_text(investors_tag, "hasNonAccreditedInvestors") == "true",
|
||||
total_already_invested=child_text(investors_tag, "totalNumberAlreadyInvested")
|
||||
) if investors_tag else None
|
||||
|
||||
# salesCommissionsFindersFees
|
||||
sales_commission_finders_tag: Optional[Tag] = offering_data_el.find("salesCommissionsFindersFees")
|
||||
sales_commission_finders_fees = SalesCommissionFindersFees(
|
||||
sales_commission=child_text(sales_commission_finders_tag.find("salesCommissions"), "dollarAmount"),
|
||||
finders_fees=child_text(sales_commission_finders_tag.find("findersFees"), "dollarAmount"),
|
||||
clarification_of_response=child_text(sales_commission_finders_tag, "clarificationOfResponse")
|
||||
) if sales_commission_finders_tag else None
|
||||
|
||||
# useOfProceeds
|
||||
use_of_proceeds_tag = offering_data_el.find("useOfProceeds")
|
||||
use_of_proceeds = UseOfProceeds(
|
||||
gross_proceeds_used=child_text(use_of_proceeds_tag.find("grossProceedsUsed"), "dollarAmount"),
|
||||
clarification_of_response=child_text(use_of_proceeds_tag, "clarificationOfResponse")
|
||||
)
|
||||
|
||||
return cls(industry_group=industry_group,
|
||||
revenue_range=revenue_range,
|
||||
federal_exemptions=federal_exemptions,
|
||||
is_new=new_or_amendment,
|
||||
date_of_first_sale=date_of_first_sale,
|
||||
more_than_one_year=more_than_one_year,
|
||||
is_equity=is_equity,
|
||||
is_pooled_investment=is_pooled_investment,
|
||||
business_combination_transaction=business_combination_transaction,
|
||||
minimum_investment=minimum_investment,
|
||||
sales_compensation_recipients=sales_compensation_recipients,
|
||||
offering_sales_amounts=offering_sales_amounts,
|
||||
investors=investors,
|
||||
sales_commission_finders_fees=sales_commission_finders_fees,
|
||||
use_of_proceeds=use_of_proceeds)
|
||||
|
||||
def __rich__(self):
|
||||
base_info_table = Table("amount offered", "amount sold", "investors", "minimum investment")
|
||||
base_info_table.add_row(self.offering_sales_amounts.total_offering_amount,
|
||||
self.offering_sales_amounts.total_amount_sold,
|
||||
self.investors.total_already_invested,
|
||||
self.minimum_investment or "")
|
||||
return Group(
|
||||
Panel.fit(base_info_table, title="Offering Info", title_align="left", box=box.SIMPLE)
|
||||
)
|
||||
|
||||
def __repr__(self):
|
||||
return repr_rich(self.__rich__())
|
||||
|
||||
|
||||
class FormD:
|
||||
"""
|
||||
Represents a Form D Offering. Might require a name change to FormD
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
submission_type: str,
|
||||
is_live: bool,
|
||||
primary_issuer: Issuer,
|
||||
related_persons: List[Person],
|
||||
offering_data: OfferingData,
|
||||
signature_block: SignatureBlock):
|
||||
self.submission_type: str = submission_type
|
||||
self.is_live: bool = is_live
|
||||
self.primary_issuer = primary_issuer
|
||||
self.related_persons: List = related_persons
|
||||
self.offering_data = offering_data
|
||||
self.signature_block = signature_block
|
||||
|
||||
@property
|
||||
def is_new(self):
|
||||
return self.offering_data.is_new
|
||||
|
||||
@classmethod
|
||||
def from_xml(cls, offering_xml: str):
|
||||
soup = BeautifulSoup(offering_xml, "xml")
|
||||
root = soup.find("edgarSubmission")
|
||||
|
||||
# Parse the issuer
|
||||
primary_issuer_el = root.find("primaryIssuer")
|
||||
primary_issuer:Optional[Tag] = Issuer.from_xml(primary_issuer_el)
|
||||
is_live = child_text(root, 'testOrLive') == 'LIVE'
|
||||
|
||||
# Parse the related party names
|
||||
related_party_list = root.find("relatedPersonsList")
|
||||
related_persons = []
|
||||
for related_person_el in related_party_list.find_all("relatedPersonInfo"):
|
||||
related_person_name_el = related_person_el.find("relatedPersonName")
|
||||
first_name = child_text(related_person_name_el, "firstName")
|
||||
last_name = child_text(related_person_name_el, "lastName")
|
||||
|
||||
related_person_address_el = related_person_el.find("relatedPersonAddress")
|
||||
address: Address = Address(
|
||||
street1=child_text(related_person_address_el, "street1"),
|
||||
street2=child_text(related_person_address_el, "street2"),
|
||||
city=child_text(related_person_address_el, "city"),
|
||||
state_or_country=child_text(related_person_address_el, "stateOrCountry"),
|
||||
state_or_country_description=child_text(related_person_address_el, "stateOrCountryDescription"),
|
||||
zipcode=child_text(related_person_address_el, "zipCode")
|
||||
)
|
||||
related_persons.append(Person(first_name=first_name, last_name=last_name, address=address))
|
||||
|
||||
# Get the offering data
|
||||
offering_data = OfferingData.from_xml(root.find("offeringData"))
|
||||
|
||||
# Get the signature
|
||||
signature_block_tag = root.find("signatureBlock")
|
||||
signatures = [Signature(
|
||||
issuer_name=child_text(sig_el, "issuerName") or "",
|
||||
signature_name=child_text(sig_el, "signatureName") or "",
|
||||
name_of_signer=child_text(sig_el, "nameOfSigner") or "",
|
||||
title=child_text(sig_el, "signatureTitle"),
|
||||
date=child_text(sig_el, "signatureDate"))
|
||||
for sig_el in signature_block_tag.find_all("signature")
|
||||
]
|
||||
signature_block = SignatureBlock(
|
||||
authorized_representative=child_text(signature_block_tag, "authorizedRepresentative") == "true",
|
||||
signatures=signatures
|
||||
)
|
||||
|
||||
return cls(submission_type=child_text(root, 'submissionType'),
|
||||
is_live=is_live,
|
||||
primary_issuer=primary_issuer,
|
||||
related_persons=related_persons,
|
||||
offering_data=offering_data,
|
||||
signature_block=signature_block)
|
||||
|
||||
def __rich__(self):
|
||||
highlight_col_style = "deep_sky_blue1 bold"
|
||||
# Issuer Table
|
||||
issuer_table = Table(box=box.SIMPLE)
|
||||
issuer_table.add_column("entity", style=highlight_col_style)
|
||||
issuer_table.add_column("cik")
|
||||
issuer_table.add_column("incorporated")
|
||||
|
||||
issuer_table.add_row(self.primary_issuer.entity_name,
|
||||
self.primary_issuer.cik,
|
||||
f"{self.primary_issuer.year_of_incorporation} ({self.primary_issuer.jurisdiction})",
|
||||
)
|
||||
# Offering info table
|
||||
offering_detail_table = Table(box=box.SIMPLE)
|
||||
offering_detail_table.add_column("amount offered", style=highlight_col_style)
|
||||
offering_detail_table.add_column("amount sold")
|
||||
offering_detail_table.add_column("investors")
|
||||
offering_detail_table.add_column("minimum investment")
|
||||
offering_detail_table.add_row(str(self.offering_data.offering_sales_amounts.total_offering_amount),
|
||||
str(self.offering_data.offering_sales_amounts.total_amount_sold),
|
||||
str(self.offering_data.investors.total_already_invested),
|
||||
self.offering_data.minimum_investment or "")
|
||||
|
||||
# related person table
|
||||
related_persons_table = Table(box=box.SIMPLE)
|
||||
related_persons_table.add_column("related person", style=highlight_col_style)
|
||||
|
||||
for index, person in enumerate(self.related_persons):
|
||||
related_persons_table.add_row(f"{person.first_name} {person.last_name}")
|
||||
|
||||
# Sales compensation recipients
|
||||
sales_recipients_table = Table(box=box.SIMPLE)
|
||||
sales_recipients_table.add_column("name", style=highlight_col_style)
|
||||
sales_recipients_table.add_column("crd")
|
||||
sales_recipients_table.add_column("states")
|
||||
|
||||
# dislay for states
|
||||
|
||||
for index, sales_recipient in enumerate(self.offering_data.sales_compensation_recipients):
|
||||
max_states_to_display = 10
|
||||
if len(sales_recipient.states_of_solicitation) > max_states_to_display:
|
||||
states = ",".join(sales_recipient.states_of_solicitation[:max_states_to_display]) + " ..."
|
||||
else:
|
||||
states = ",".join(sales_recipient.states_of_solicitation)
|
||||
sales_recipients_table.add_row(sales_recipient.name or "",
|
||||
sales_recipient.crd or "",
|
||||
states)
|
||||
|
||||
# Signature Block
|
||||
signature_table = Table(box=box.SIMPLE)
|
||||
signature_table.add_column(" ")
|
||||
signature_table.add_column("signature", style=highlight_col_style)
|
||||
signature_table.add_column("signer")
|
||||
signature_table.add_column("title")
|
||||
signature_table.add_column("date")
|
||||
signature_table.add_column("issuer")
|
||||
|
||||
for index, signature in enumerate(self.signature_block.signatures):
|
||||
signature_table.add_row(str(index + 1),
|
||||
signature.signature_name,
|
||||
signature.name_of_signer,
|
||||
signature.title,
|
||||
signature.date,
|
||||
signature.issuer_name
|
||||
)
|
||||
|
||||
def panel_fit(renderable: RenderableType, title: Optional[str] = None):
|
||||
if title:
|
||||
return Panel.fit(renderable, title=title, title_align="left", box=box.SIMPLE,
|
||||
style="bold")
|
||||
else:
|
||||
return Panel.fit(renderable, box=box.SIMPLE, style="bold")
|
||||
|
||||
# This is the final group of rich renderables
|
||||
|
||||
return Group(
|
||||
panel_fit(issuer_table, title=f"Form {self.submission_type} Offering"),
|
||||
panel_fit(offering_detail_table, title="Offering Detail"),
|
||||
panel_fit(Columns([Group(Text("Related Persons"), related_persons_table),
|
||||
Group(Text("Sales Compensation"), sales_recipients_table)]
|
||||
)),
|
||||
panel_fit(signature_table, title="Signatures")
|
||||
)
|
||||
|
||||
def __repr__(self):
|
||||
"""
|
||||
Render __rich__ to a string and use that as __repr__
|
||||
:return:
|
||||
"""
|
||||
return repr_rich(self.__rich__())
|
||||
Reference in New Issue
Block a user